├── packages ├── census │ ├── test │ │ └── mocha-hooks.ts │ ├── .gitignore │ ├── src │ │ ├── index.ts │ │ ├── onchain.ts │ │ ├── offchain.ts │ │ └── blind.ts │ ├── tsconfig.json │ ├── CHANGELOG.md │ ├── scripts │ │ └── wipe-snarkjs-package-module.js │ └── package.json ├── client │ ├── test │ │ ├── mocha-hooks.ts │ │ ├── helpers │ │ │ ├── example.ts │ │ │ └── all-services.ts │ │ ├── builders │ │ │ ├── storage-proofs.ts │ │ │ ├── namespace.ts │ │ │ ├── results.ts │ │ │ ├── ens-resolver.ts │ │ │ └── genesis.ts │ │ └── integration │ │ │ └── discovery.ts │ ├── src │ │ ├── apis │ │ │ ├── index.ts │ │ │ └── tokens.ts │ │ ├── errors │ │ │ ├── index.ts │ │ │ ├── archive.ts │ │ │ ├── results.ts │ │ │ └── discovery.ts │ │ ├── index.ts │ │ ├── net │ │ │ ├── ipfs.ts │ │ │ ├── providers.ts │ │ │ └── ens.ts │ │ ├── wrappers │ │ │ ├── content-uri.ts │ │ │ ├── gateway-info.ts │ │ │ └── content-hashed-uri.ts │ │ └── util │ │ │ └── uint8array.ts │ ├── .gitignore │ ├── tsconfig.json │ ├── CHANGELOG.md │ ├── README.md │ └── package.json ├── common │ ├── test │ │ ├── mocha-hooks.ts │ │ └── unit │ │ │ ├── random.ts │ │ │ └── normalization.ts │ ├── .gitignore │ ├── src │ │ ├── index.ts │ │ ├── normalization.ts │ │ ├── types.ts │ │ ├── timeout.ts │ │ ├── encoding.ts │ │ ├── promise.ts │ │ └── random.ts │ ├── tsconfig.json │ ├── CHANGELOG.md │ ├── package.json │ └── README.md ├── hashing │ ├── test │ │ ├── mocha-hooks.ts │ │ └── unit │ │ │ ├── keccak256.ts │ │ │ └── poseidon.ts │ ├── src │ │ ├── index.ts │ │ ├── keccak256.ts │ │ └── poseidon.ts │ ├── .gitignore │ ├── CHANGELOG.md │ ├── tsconfig.json │ ├── package.json │ └── README.md ├── signing │ ├── test │ │ ├── mocha-hooks.ts │ │ └── unit │ │ │ └── sorting.ts │ ├── src │ │ └── index.ts │ ├── .gitignore │ ├── tsconfig.json │ ├── CHANGELOG.md │ └── package.json ├── voting │ ├── test │ │ └── mocha-hooks.ts │ ├── .gitignore │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ ├── package.json │ └── CHANGELOG.md ├── wallets │ ├── test │ │ ├── mocha-hooks.ts │ │ └── unit │ │ │ └── common.ts │ ├── .gitignore │ ├── src │ │ ├── index.ts │ │ ├── signer-util.ts │ │ ├── wallet-util.ts │ │ └── util │ │ │ └── helpers.ts │ ├── CHANGELOG.md │ ├── tsconfig.json │ ├── package.json │ └── README.md ├── data-models │ ├── test │ │ ├── mocha-hooks.ts │ │ ├── builders │ │ │ └── process-metadata.ts │ │ └── unit │ │ │ └── entities.ts │ ├── .npmignore │ ├── .gitignore │ ├── .gitmodules │ ├── src │ │ ├── index.ts │ │ ├── raw-tx.ts │ │ ├── protobuf.ts │ │ ├── templates │ │ │ ├── entity.ts │ │ │ ├── json-feed.ts │ │ │ └── process.ts │ │ ├── voting-params.ts │ │ └── json-feed.ts │ ├── CHANGELOG.md │ ├── tsconfig.json │ ├── package.json │ └── README.md ├── encryption │ ├── test │ │ └── mocha-hooks.ts │ ├── src │ │ └── index.ts │ ├── .gitignore │ ├── CHANGELOG.md │ ├── tsconfig.json │ ├── README.md │ └── package.json └── contract-wrappers │ ├── test │ ├── mocha-hooks.ts │ └── unit │ │ └── ens-hash.ts │ ├── .gitignore │ ├── CHANGELOG.md │ ├── tsconfig.json │ ├── README.md │ ├── package.json │ └── src │ └── index.ts ├── example ├── web │ ├── .eslintrc │ ├── next.config.js │ ├── public │ │ └── favicon.ico │ ├── pages │ │ ├── _app.tsx │ │ ├── network.tsx │ │ ├── index.tsx │ │ └── metadata.tsx │ ├── next-env.d.ts │ ├── .npmignore │ ├── styles │ │ ├── globals.css │ │ └── Page.module.css │ ├── lib │ │ └── net.ts │ ├── .gitignore │ ├── tsconfig.json │ ├── package.json │ └── README.md ├── signed-erc20 │ ├── .gitignore │ ├── package.json │ ├── census.ts │ ├── net.ts │ ├── config.template.yaml │ ├── util.ts │ ├── config.ts │ └── index.ts ├── signed-erc20-signal │ ├── .gitignore │ ├── package.json │ ├── census.ts │ ├── net.ts │ ├── config.template.yaml │ ├── util.ts │ ├── config.ts │ └── index.ts ├── signed-blind │ ├── .gitignore │ ├── package.json │ ├── net.ts │ ├── config.template.yaml │ ├── entity.ts │ ├── util.ts │ └── index.ts ├── anonymous-off-chain │ ├── .gitignore │ ├── package.json │ ├── net.ts │ ├── config.template.yaml │ ├── entity.ts │ ├── util.ts │ ├── config.ts │ └── census.ts ├── signed-off-chain │ ├── .gitignore │ ├── package.json │ ├── net.ts │ ├── config.template.yaml │ ├── entity.ts │ ├── util.ts │ ├── config.ts │ └── census.ts └── other │ └── ethers-js.ts ├── .npmignore ├── .gitignore ├── .gitmodules ├── .editorconfig ├── AUTHORS ├── .github ├── workflows │ └── main.yml └── ISSUE_TEMPLATE │ └── bug_report.md ├── src └── index.ts ├── tsconfig.json ├── tslint.json ├── shared └── test │ └── mocha-hooks.ts └── package.json /packages/census/test/mocha-hooks.ts: -------------------------------------------------------------------------------- 1 | ../../../shared/test/mocha-hooks.ts -------------------------------------------------------------------------------- /packages/client/test/mocha-hooks.ts: -------------------------------------------------------------------------------- 1 | ../../../shared/test/mocha-hooks.ts -------------------------------------------------------------------------------- /packages/common/test/mocha-hooks.ts: -------------------------------------------------------------------------------- 1 | ../../../shared/test/mocha-hooks.ts -------------------------------------------------------------------------------- /packages/hashing/test/mocha-hooks.ts: -------------------------------------------------------------------------------- 1 | ../../../shared/test/mocha-hooks.ts -------------------------------------------------------------------------------- /packages/signing/test/mocha-hooks.ts: -------------------------------------------------------------------------------- 1 | ../../../shared/test/mocha-hooks.ts -------------------------------------------------------------------------------- /packages/voting/test/mocha-hooks.ts: -------------------------------------------------------------------------------- 1 | ../../../shared/test/mocha-hooks.ts -------------------------------------------------------------------------------- /packages/wallets/test/mocha-hooks.ts: -------------------------------------------------------------------------------- 1 | ../../../shared/test/mocha-hooks.ts -------------------------------------------------------------------------------- /packages/data-models/test/mocha-hooks.ts: -------------------------------------------------------------------------------- 1 | ../../../shared/test/mocha-hooks.ts -------------------------------------------------------------------------------- /packages/encryption/test/mocha-hooks.ts: -------------------------------------------------------------------------------- 1 | ../../../shared/test/mocha-hooks.ts -------------------------------------------------------------------------------- /example/web/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next", "next/core-web-vitals"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/contract-wrappers/test/mocha-hooks.ts: -------------------------------------------------------------------------------- 1 | ../../../shared/test/mocha-hooks.ts -------------------------------------------------------------------------------- /example/web/next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reactStrictMode: true, 3 | } 4 | -------------------------------------------------------------------------------- /example/signed-erc20/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | config.yaml 3 | cached-process-info.json 4 | -------------------------------------------------------------------------------- /packages/data-models/.npmignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules 3 | dist 4 | .env 5 | .cache 6 | src -------------------------------------------------------------------------------- /packages/hashing/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './keccak256' 2 | export * from './poseidon' 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | example 3 | test 4 | packages 5 | .env 6 | .github 7 | .vscode 8 | -------------------------------------------------------------------------------- /packages/encryption/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./asymmetric" 2 | export * from "./symmetric" 3 | -------------------------------------------------------------------------------- /example/signed-erc20-signal/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | config.yaml 3 | cached-process-info.json 4 | -------------------------------------------------------------------------------- /packages/signing/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./bytes-signature" 2 | export * from "./json-signature" 3 | -------------------------------------------------------------------------------- /example/web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vocdoni/dvote-js/HEAD/example/web/public/favicon.ico -------------------------------------------------------------------------------- /example/signed-blind/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | config.yaml 3 | old 4 | cached-accounts.json 5 | cached-process-info.json -------------------------------------------------------------------------------- /packages/client/src/apis/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./definition" 2 | export * from "./file" 3 | export * from "./tokens" 4 | -------------------------------------------------------------------------------- /example/anonymous-off-chain/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | config.yaml 3 | old 4 | cached-accounts.json 5 | cached-process-info.json -------------------------------------------------------------------------------- /example/signed-off-chain/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | config.yaml 3 | old 4 | cached-accounts.json 5 | cached-process-info.json -------------------------------------------------------------------------------- /packages/census/.gitignore: -------------------------------------------------------------------------------- 1 | # Covers JetBrains IDEs 2 | .idea/ 3 | # Project 4 | node_modules 5 | dist 6 | .env 7 | .cache 8 | -------------------------------------------------------------------------------- /packages/client/.gitignore: -------------------------------------------------------------------------------- 1 | # Covers JetBrains IDEs 2 | .idea/ 3 | # Project 4 | node_modules 5 | dist 6 | .env 7 | .cache 8 | -------------------------------------------------------------------------------- /packages/client/src/errors/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./archive" 2 | export * from "./discovery" 3 | export * from "./results" 4 | -------------------------------------------------------------------------------- /packages/common/.gitignore: -------------------------------------------------------------------------------- 1 | # Covers JetBrains IDEs 2 | .idea/ 3 | # Project 4 | node_modules 5 | dist 6 | .env 7 | .cache 8 | -------------------------------------------------------------------------------- /packages/hashing/.gitignore: -------------------------------------------------------------------------------- 1 | # Covers JetBrains IDEs 2 | .idea/ 3 | # Project 4 | node_modules 5 | dist 6 | .env 7 | .cache 8 | -------------------------------------------------------------------------------- /packages/signing/.gitignore: -------------------------------------------------------------------------------- 1 | # Covers JetBrains IDEs 2 | .idea/ 3 | # Project 4 | node_modules 5 | dist 6 | .env 7 | .cache 8 | -------------------------------------------------------------------------------- /packages/voting/.gitignore: -------------------------------------------------------------------------------- 1 | # Covers JetBrains IDEs 2 | .idea/ 3 | # Project 4 | node_modules 5 | dist 6 | .env 7 | .cache 8 | -------------------------------------------------------------------------------- /packages/wallets/.gitignore: -------------------------------------------------------------------------------- 1 | # Covers JetBrains IDEs 2 | .idea/ 3 | # Project 4 | node_modules 5 | dist 6 | .env 7 | .cache 8 | -------------------------------------------------------------------------------- /packages/wallets/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./wallet-util" 2 | export * from "./signer-util" 3 | export * from "./baby-jub" 4 | -------------------------------------------------------------------------------- /packages/data-models/.gitignore: -------------------------------------------------------------------------------- 1 | # Covers JetBrains IDEs 2 | .idea/ 3 | # Project 4 | node_modules 5 | dist 6 | .env 7 | .cache 8 | -------------------------------------------------------------------------------- /packages/encryption/.gitignore: -------------------------------------------------------------------------------- 1 | # Covers JetBrains IDEs 2 | .idea/ 3 | # Project 4 | node_modules 5 | dist 6 | .env 7 | .cache 8 | -------------------------------------------------------------------------------- /packages/contract-wrappers/.gitignore: -------------------------------------------------------------------------------- 1 | # Covers JetBrains IDEs 2 | .idea/ 3 | # Project 4 | node_modules 5 | dist 6 | .env 7 | .cache 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Covers JetBrains IDEs 2 | .idea/ 3 | # Project 4 | node_modules 5 | dist 6 | .env 7 | .cache 8 | *.tgz 9 | .DS_Store 10 | -------------------------------------------------------------------------------- /packages/data-models/.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/protobuf"] 2 | path = src/protobuf 3 | url = https://github.com/vocdoni/dvote-protobuf.git 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "packages/data-models/src/protobuf"] 2 | path = packages/data-models/src/protobuf 3 | url = https://github.com/vocdoni/dvote-protobuf.git 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /packages/voting/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./voting" 2 | export * from "./voting-calls" 3 | export * from "./voting-oracle-calls" 4 | export * from "./entity-calls" 5 | 6 | export * from "./types" 7 | -------------------------------------------------------------------------------- /packages/client/src/errors/archive.ts: -------------------------------------------------------------------------------- 1 | export class GatewayArchiveError extends Error { 2 | constructor(message?: string) { 3 | super(message ? message : "Fetching archive data failed"); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/client/src/errors/results.ts: -------------------------------------------------------------------------------- 1 | export class ResultsNotAvailableError extends Error { 2 | constructor(message?: string) { 3 | super(message ? message : "The results are not available"); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/encryption/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @vocdoni/encryption - Changelog 2 | 3 | ## 1.14.1 4 | 5 | - Add the readme file 6 | 7 | ## 1.14.0 8 | 9 | - First version of the package, starting from dvote-js version 1.13.2 10 | -------------------------------------------------------------------------------- /example/web/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css' 2 | import type { AppProps } from 'next/app' 3 | 4 | function MyApp({ Component, pageProps }: AppProps) { 5 | return 6 | } 7 | export default MyApp 8 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Below is a list of people and organizations that have contributed 2 | # to the Flutter project. Names should be added to the list like so: 3 | # 4 | # Name/Organization 5 | 6 | Vocdoni Roots MTU 7 | The Vocdoni Team 8 | -------------------------------------------------------------------------------- /example/web/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /packages/common/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './encoding' 3 | export * from './random' 4 | export * from './timeout' 5 | export * from './promise' 6 | export * from './constants' 7 | export * from './normalization' 8 | -------------------------------------------------------------------------------- /packages/data-models/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './backup' 2 | export * from './entity' 3 | export * from './json-feed' 4 | export * from './voting-meta' 5 | export * from './voting-params' 6 | export * from './raw-tx' 7 | export * from './protobuf' 8 | -------------------------------------------------------------------------------- /packages/census/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./blind" 2 | export * from "./erc20-calls" 3 | export * from "./offchain-calls" 4 | export * from "./offchain" 5 | export * from "./onchain" 6 | export * from "./onchain-calls" 7 | export * from "./zk-snarks" 8 | -------------------------------------------------------------------------------- /packages/hashing/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @vocdoni/hashing - Changelog 2 | 3 | ## 1.15.0 4 | 5 | - Moving `getNullifier` to the `@vocdoni/voting` package 6 | 7 | ## 1.14.0 8 | 9 | - First version of the package, starting from dvote-js version 1.13.2 10 | -------------------------------------------------------------------------------- /example/web/.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .cache 3 | .next 4 | dist 5 | build 6 | .DS_Store 7 | 8 | # debug 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | .env.local 13 | .env.development.local 14 | .env.test.local 15 | .env.production.local 16 | 17 | *.tgz 18 | -------------------------------------------------------------------------------- /packages/contract-wrappers/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @vocdoni/contract-wrappers - Changelog 2 | 3 | ## 1.15.0 4 | 5 | - Upgrading dvote-solidity 6 | 7 | ## 1.14.1 8 | 9 | - Added `@ethersproject/contracts` dependency 10 | 11 | ## 1.14.0 12 | 13 | - First version of the package, starting from dvote-js version 1.13.2 14 | -------------------------------------------------------------------------------- /example/web/styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 10px; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | } 8 | 9 | a { 10 | color: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Main 4 | 5 | on: [push, pull_request] 6 | 7 | jobs: 8 | node-tests: 9 | runs-on: ubuntu-latest 10 | steps: 11 | 12 | - name: Checkout code 13 | uses: actions/checkout@v2 14 | 15 | - name: Use Node.js ${{ matrix.node-version }} 16 | uses: actions/setup-node@v2 17 | with: 18 | node-version: 16.x 19 | 20 | - name: Build and test 21 | run: npm test 22 | -------------------------------------------------------------------------------- /example/signed-erc20/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "signed-erc20", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "author": "", 7 | "scripts": { 8 | "start": "ts-node index.ts" 9 | }, 10 | "license": "ISC", 11 | "homepage": "https://vocdoni.io", 12 | "devDependencies": { 13 | "bluebird": "^3.7.2", 14 | "ts-node": "^10.2.1", 15 | "typescript": "^4.4.2", 16 | "yaml": "^1.9.2" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/common/src/normalization.ts: -------------------------------------------------------------------------------- 1 | import * as latinize from "latinize" 2 | 3 | export function normalizeText(text: string): string { 4 | if (!text) return text 5 | else if (typeof text != "string") return null 6 | 7 | const result = text 8 | .trim() 9 | .replace(/\s+/g, ' ') 10 | .replace(/[\.·:]/g, ".") 11 | .replace(/[`´]/g, "'") 12 | .toLowerCase() 13 | 14 | return latinize(result) 15 | } 16 | -------------------------------------------------------------------------------- /example/signed-off-chain/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "signed-off-chain", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "author": "", 7 | "scripts": { 8 | "start": "ts-node index.ts" 9 | }, 10 | "license": "ISC", 11 | "homepage": "https://vocdoni.io", 12 | "devDependencies": { 13 | "bluebird": "^3.7.2", 14 | "ts-node": "^10.2.1", 15 | "typescript": "^4.4.2", 16 | "yaml": "^1.9.2" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /example/anonymous-off-chain/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "anonymous-off-chain", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "author": "", 7 | "scripts": { 8 | "start": "ts-node index.ts" 9 | }, 10 | "license": "ISC", 11 | "homepage": "https://vocdoni.io", 12 | "devDependencies": { 13 | "bluebird": "^3.7.2", 14 | "ts-node": "^10.2.1", 15 | "typescript": "^4.4.2", 16 | "yaml": "^1.9.2" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /example/signed-erc20-signal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "signed-erc20-signal", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "author": "", 7 | "scripts": { 8 | "start": "ts-node index.ts" 9 | }, 10 | "license": "ISC", 11 | "homepage": "https://vocdoni.io", 12 | "devDependencies": { 13 | "bluebird": "^3.7.2", 14 | "ts-node": "^10.2.1", 15 | "typescript": "^4.4.2", 16 | "yaml": "^1.9.2" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/wallets/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @vocdoni/wallets - Changelog 2 | 3 | ## 1.16.0 4 | 5 | - Version bump (dependency upgrades) 6 | 7 | ## 1.15.0 8 | 9 | - Allowing to create random BabyJubJub wallets 10 | 11 | ## 1.14.1 12 | 13 | - Removed `ethers` dependency and added minor ones from `@ethersproject` 14 | - Removed internal dependency with `@vocdoni/signing` package 15 | 16 | ## 1.14.0 17 | 18 | - First version of the package, starting from dvote-js version 1.13.2 19 | -------------------------------------------------------------------------------- /example/signed-blind/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "signed-blind", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "author": "", 7 | "scripts": { 8 | "start": "ts-node index.ts" 9 | }, 10 | "license": "ISC", 11 | "homepage": "https://vocdoni.io", 12 | "devDependencies": { 13 | "axios": "^0.24.0", 14 | "bluebird": "^3.7.2", 15 | "ts-node": "^10.4.0", 16 | "typescript": "^4.5.2", 17 | "yaml": "^1.10.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // COMMON 2 | export * from "@vocdoni/common" 3 | 4 | // API 5 | export * from "@vocdoni/census" 6 | export * from "@vocdoni/voting" 7 | 8 | // MODELS 9 | export * from "@vocdoni/data-models" 10 | export * from "@vocdoni/contract-wrappers" 11 | 12 | // CLIENT 13 | export * from "@vocdoni/client" 14 | 15 | // CRYPTO 16 | export * from "@vocdoni/signing" 17 | export * from "@vocdoni/encryption" 18 | export * from "@vocdoni/hashing" 19 | export * from "@vocdoni/wallets" 20 | -------------------------------------------------------------------------------- /packages/hashing/src/keccak256.ts: -------------------------------------------------------------------------------- 1 | import { keccak256 } from "@ethersproject/keccak256" 2 | 3 | export namespace Keccak256 { 4 | export function hashText(value: string): string { 5 | return keccak256(Buffer.from(value, "utf8")) 6 | } 7 | export function hashHexString(value: string): string { 8 | return keccak256(Buffer.from(value, "hex")) 9 | } 10 | export function hashBytes(value: Uint8Array): string { 11 | return keccak256(value) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/census/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "resolveJsonModule": true, 6 | "pretty": true, 7 | "declaration": true, 8 | "sourceMap": true, 9 | "target": "es6", 10 | "outDir": "dist", 11 | "skipLibCheck": true, 12 | "baseUrl": "src" 13 | }, 14 | "include": [ 15 | "src/*.ts" 16 | ], 17 | "exclude": [ 18 | "node_modules" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /packages/client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "resolveJsonModule": true, 6 | "pretty": true, 7 | "declaration": true, 8 | "sourceMap": true, 9 | "target": "es6", 10 | "outDir": "dist", 11 | "skipLibCheck": true, 12 | "baseUrl": "src" 13 | }, 14 | "include": [ 15 | "src/*.ts" 16 | ], 17 | "exclude": [ 18 | "node_modules" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /packages/common/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "resolveJsonModule": true, 6 | "pretty": true, 7 | "declaration": true, 8 | "sourceMap": true, 9 | "target": "es6", 10 | "outDir": "dist", 11 | "skipLibCheck": true, 12 | "baseUrl": "src" 13 | }, 14 | "include": [ 15 | "src/*.ts" 16 | ], 17 | "exclude": [ 18 | "node_modules" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /packages/hashing/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "resolveJsonModule": true, 6 | "pretty": true, 7 | "declaration": true, 8 | "sourceMap": true, 9 | "target": "es6", 10 | "outDir": "dist", 11 | "skipLibCheck": true, 12 | "baseUrl": "src" 13 | }, 14 | "include": [ 15 | "src/*.ts" 16 | ], 17 | "exclude": [ 18 | "node_modules" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /packages/signing/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "resolveJsonModule": true, 6 | "pretty": true, 7 | "declaration": true, 8 | "sourceMap": true, 9 | "target": "es6", 10 | "outDir": "dist", 11 | "skipLibCheck": true, 12 | "baseUrl": "src" 13 | }, 14 | "include": [ 15 | "src/*.ts" 16 | ], 17 | "exclude": [ 18 | "node_modules" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /packages/voting/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "resolveJsonModule": true, 6 | "pretty": true, 7 | "declaration": true, 8 | "sourceMap": true, 9 | "target": "es6", 10 | "outDir": "dist", 11 | "skipLibCheck": true, 12 | "baseUrl": "src" 13 | }, 14 | "include": [ 15 | "src/*.ts" 16 | ], 17 | "exclude": [ 18 | "node_modules" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /packages/wallets/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "resolveJsonModule": true, 6 | "pretty": true, 7 | "declaration": true, 8 | "sourceMap": true, 9 | "target": "es6", 10 | "outDir": "dist", 11 | "skipLibCheck": true, 12 | "baseUrl": "src" 13 | }, 14 | "include": [ 15 | "src/*.ts" 16 | ], 17 | "exclude": [ 18 | "node_modules" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /packages/encryption/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "resolveJsonModule": true, 6 | "pretty": true, 7 | "declaration": true, 8 | "sourceMap": true, 9 | "target": "es6", 10 | "outDir": "dist", 11 | "skipLibCheck": true, 12 | "baseUrl": "src" 13 | }, 14 | "include": [ 15 | "src/*.ts" 16 | ], 17 | "exclude": [ 18 | "node_modules" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /packages/contract-wrappers/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "resolveJsonModule": true, 6 | "pretty": true, 7 | "declaration": true, 8 | "sourceMap": true, 9 | "target": "es6", 10 | "outDir": "dist", 11 | "skipLibCheck": true, 12 | "baseUrl": "src" 13 | }, 14 | "include": [ 15 | "src/*.ts" 16 | ], 17 | "exclude": [ 18 | "node_modules" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /packages/data-models/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @vocdoni/data-models - Changelog 2 | 3 | ## 1.15.3 4 | 5 | - Minor typedef change 6 | 7 | ## 1.15.2 8 | 9 | - Adding `wrapRawTransaction` 10 | 11 | ## 1.15.0 12 | 13 | - Adding support for anonymous voting (zkSnarks) 14 | 15 | ## 1.14.2 16 | 17 | - Moving the gateway API definitions to the client package 18 | - Moving `TextRecordKeys` to `common` 19 | 20 | ## 1.14.1 21 | 22 | - Lighter set of artifacts on NPM 23 | 24 | ## 1.14.0 25 | 26 | - First version of the package, starting from dvote-js version 1.13.2 27 | -------------------------------------------------------------------------------- /packages/data-models/src/raw-tx.ts: -------------------------------------------------------------------------------- 1 | import { SignedTx } from "./protobuf/build/ts/vochain/vochain" 2 | 3 | /** Wraps the given bytes and salted signature into a raw transaction */ 4 | export function wrapRawTransaction(txBytes: Uint8Array, saltedSignature = new Uint8Array()) { 5 | const signedTx = SignedTx.encode({ tx: txBytes, signature: saltedSignature }) 6 | const signedTxBytes = signedTx.finish() 7 | 8 | const base64Payload = Buffer.from(signedTxBytes).toString("base64") 9 | return { method: "submitRawTx" as const, payload: base64Payload } 10 | } 11 | -------------------------------------------------------------------------------- /example/web/lib/net.ts: -------------------------------------------------------------------------------- 1 | import { GatewayDiscovery, GatewayPool } from "@vocdoni/client" 2 | 3 | const BOOTNODE_URI = "https://bootnodes.vocdoni.net/gateways.dev.json" 4 | const ENVIRONMENT = "dev" 5 | const NETWORK_ID = "rinkeby" 6 | const discoveryParams = { 7 | bootnodesContentUri: BOOTNODE_URI, 8 | networkId: NETWORK_ID as any, 9 | environment: ENVIRONMENT as any 10 | } 11 | 12 | export function getClient() { 13 | return GatewayDiscovery.run(discoveryParams) 14 | .then(result => { 15 | return new GatewayPool(result, discoveryParams) 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /example/web/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | *.tgz 37 | -------------------------------------------------------------------------------- /example/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": false, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve" 16 | }, 17 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 18 | "exclude": ["node_modules"] 19 | } 20 | -------------------------------------------------------------------------------- /packages/wallets/src/signer-util.ts: -------------------------------------------------------------------------------- 1 | import { Web3Provider } from "@ethersproject/providers" 2 | 3 | export namespace SignerUtil { 4 | /** 5 | * Returns a Web3 signer if the browser supports it or if Metamask is available 6 | * Returns null otherwise 7 | */ 8 | export function fromInjectedWeb3() { 9 | if (typeof window == "undefined" || typeof window["web3"] == "undefined") return null 10 | 11 | const provider = new Web3Provider(window["web3"].currentProvider) 12 | if (!provider.getSigner) return null 13 | return provider.getSigner() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/common/src/types.ts: -------------------------------------------------------------------------------- 1 | export type VocdoniEnvironment = "prod" | "stg" | "dev" 2 | export type EthNetworkID = "homestead" | "mainnet" | "rinkeby" | "goerli" | 3 | "xdai" | "sokol" | "avalanche" | "fuji" | "matic" // | "mumbai" 4 | 5 | export type HexString = string 6 | export type ContractAddress = HexString // e.g. 0x1234567890123456789012345678901234567890 7 | 8 | export type MultiLanguage = { 9 | default: T 10 | [lang: string]: T // Indexed by language { en: value, fr: value, ... } 11 | } 12 | 13 | export type URI = string 14 | 15 | export type ContentUriString = string 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "resolveJsonModule": true, 6 | "pretty": true, 7 | "declaration": true, 8 | "sourceMap": true, 9 | "target": "es6", 10 | "outDir": "dist", 11 | "skipLibCheck": true, 12 | "baseUrl": "src" 13 | }, 14 | "include": [ 15 | "src/**/*.ts" 16 | ], 17 | "exclude": [ 18 | "node_modules", 19 | "src/protobuf/src", 20 | "src/protobuf/build/dart", 21 | "src/protobuf/build/go" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /packages/data-models/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "resolveJsonModule": true, 6 | "pretty": true, 7 | "declaration": true, 8 | "sourceMap": true, 9 | "target": "es6", 10 | "outDir": "dist", 11 | "skipLibCheck": true, 12 | "baseUrl": "src" 13 | }, 14 | "include": [ 15 | "src/*.ts" 16 | ], 17 | "exclude": [ 18 | "node_modules", 19 | "src/protobuf/src", 20 | "src/protobuf/build/dart", 21 | "src/protobuf/build/go" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /example/signed-erc20/census.ts: -------------------------------------------------------------------------------- 1 | import { utils, Wallet } from "ethers" 2 | import { getConfig } from "./config" 3 | 4 | const config = getConfig() 5 | 6 | export type TestAccount = { 7 | idx: number, 8 | privateKey: string 9 | publicKey: string 10 | } 11 | 12 | export function getAccounts() { 13 | const accounts = config.privKeys.map((key, i) => { 14 | const wallet = new Wallet(key) 15 | return { 16 | idx: i, 17 | privateKey: key, 18 | publicKey: utils.computePublicKey(wallet.publicKey, true) 19 | } 20 | }) 21 | 22 | console.log() 23 | return accounts 24 | } 25 | -------------------------------------------------------------------------------- /example/signed-erc20-signal/census.ts: -------------------------------------------------------------------------------- 1 | import { utils, Wallet } from "ethers" 2 | import { getConfig } from "./config" 3 | 4 | const config = getConfig() 5 | 6 | export type TestAccount = { 7 | idx: number, 8 | privateKey: string 9 | publicKey: string 10 | } 11 | 12 | export function getAccounts() { 13 | const accounts = config.privKeys.map((key, i) => { 14 | const wallet = new Wallet(key) 15 | return { 16 | idx: i, 17 | privateKey: key, 18 | publicKey: utils.computePublicKey(wallet.publicKey, true) 19 | } 20 | }) 21 | 22 | console.log() 23 | return accounts 24 | } 25 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": {}, 7 | "rules": { 8 | "indent": [ 9 | true, 10 | "spaces", 11 | 4 12 | ], 13 | "semicolon": [ 14 | false, 15 | "always" 16 | ] 17 | }, 18 | "rulesDirectory": [], 19 | "linterOptions": { 20 | "exclude": [ 21 | "node_modules/**", 22 | "src/protobuf", 23 | "src/protobuf/src", 24 | "src/protobuf/build/dart", 25 | "src/protobuf/build/go" 26 | ] 27 | } 28 | } -------------------------------------------------------------------------------- /packages/client/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./apis" 2 | 3 | export * from "./gateway" 4 | export * from "./gateway-archive" 5 | export * from "./gateway-bootnode" 6 | export * from "./gateway-discovery" 7 | export * from "./gateway-dvote" 8 | export * from "./gateway-web3" 9 | export * from "./gateway-pool" 10 | 11 | export * from "./wrappers/content-hashed-uri" 12 | export * from "./wrappers/content-uri" 13 | export * from "./wrappers/gateway-info" 14 | 15 | export * from "./util/uint8array" 16 | 17 | export * from "./net/ens" 18 | export * from "./net/ipfs" 19 | export * from "./net/providers" 20 | 21 | export * from "./interfaces" 22 | 23 | export * from "./errors" 24 | -------------------------------------------------------------------------------- /packages/common/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @vocdoni/common - Changelog 2 | 3 | ## 1.15.4 4 | 5 | - Added `Promise.any` polyfill as `promiseAny` function 6 | 7 | ## 1.15.3 8 | 9 | - Updating env vars 10 | 11 | ## 1.15.2 12 | 13 | - Adding support for Polygon mainnet ~~and testnet networks~~ 14 | 15 | ## 1.15.1 16 | 17 | - Fixed zkey file name `ZK_VOTING_ZKEY_FILE_NAME` 18 | - Adding support for Avax and Fuji networks 19 | 20 | ## 1.15.0 21 | 22 | - Adding support for Little Endian bigint buffer encoding and decoding 23 | 24 | ## 1.14.1 25 | 26 | - Moving `IPFS_GATEWAY_LIST_URI` to the client package 27 | - Moving `extractUint8ArrayJSONValue` to the client package 28 | - Adding `TextRecordKeys` from `data-models` 29 | 30 | ## 1.14.0 31 | 32 | - First version of the package, starting from dvote-js version 1.13.2 33 | -------------------------------------------------------------------------------- /packages/hashing/src/poseidon.ts: -------------------------------------------------------------------------------- 1 | import { poseidon } from "circomlib" 2 | 3 | export namespace Poseidon { 4 | export const Q = BigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617") 5 | 6 | /** Computes the raw poseidon hash of an array of big integers */ 7 | export function hash(inputs: bigint[]): bigint { 8 | if (inputs.some(value => value >= Q || value < BigInt("0"))) { 9 | throw new Error("One or more inputs are out of the Poseidon field") 10 | } 11 | return poseidon(inputs) 12 | } 13 | 14 | /** Computes the poseidon hash of the uncompressed coordinates of a 15 | * Baby JubJub public key 16 | */ 17 | export function hashBabyJubJubPublicKey(x: bigint, y: bigint) { 18 | return Poseidon.hash([x, y]) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/contract-wrappers/README.md: -------------------------------------------------------------------------------- 1 | # @vocdoni/contract-wrappers 2 | 3 | @vocdoni/contract-wrappers contains JS wrappers for the solidity contract artifacts from [dvote-solidity library](https://github.com/vocdoni/dvote-solidity/) 4 | 5 | ## Installation 6 | 7 | Use [npm](https://www.npmjs.com/) to install @vocdoni/contract-wrappers. 8 | 9 | ```bash 10 | npm install @vocdoni/contract-wrappers 11 | ``` 12 | 13 | ## Usage 14 | 15 | #### ENS hash address 16 | 17 | ```ts 18 | import { ensHashAddress } from "@vocdoni/contract-wrappers" 19 | 20 | const addr = "0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1" 21 | ensHashAddress(addr) 22 | // returns '0xe7fb8f3e702fd22bf02391cc16c6b4bc465084468f1627747e6e21e2005f880e' 23 | ``` 24 | 25 | ## Testing 26 | 27 | To execute library tests just run 28 | 29 | ```bash 30 | npm run test 31 | ``` 32 | -------------------------------------------------------------------------------- /packages/census/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @vocdoni/census - Changelog 2 | 3 | ## 1.16.0 4 | 5 | - Using salted signatures 6 | 7 | ## 1.15.2 8 | 9 | - Added `postinstall` script for `snarkjs` dependency for browser compatibility 10 | 11 | ## 1.15.1 12 | 13 | - Fixed weight values when adding claims in `CensusOffChainApi.addClaim` and `CensusOffChainApi.addClaimChunk` 14 | 15 | ## 1.15.0 16 | 17 | - Adding support for Anonymous voting (zkSnarks) 18 | - `CensusOnChainApi.registerVoterKey`, `CensusOnChainApi.generateProof` 19 | - Breaking: all census keys are assumed to be undigested 20 | 21 | ## 1.14.1 22 | 23 | - Moving `registerVoterKey` to `CensusOnChainApi.registerVoterKey` 24 | - Dependency bump 25 | 26 | ## 1.14.0 27 | 28 | - First version of the package, starting from dvote-js version 1.13.2 29 | - Renaming `CensusCaApi` to `CensusBlind` 30 | -------------------------------------------------------------------------------- /shared/test/mocha-hooks.ts: -------------------------------------------------------------------------------- 1 | let testSuites = 0 2 | let failedTests = 0 3 | 4 | // A new test file starts executing 5 | function started() { 6 | testSuites++ 7 | } 8 | 9 | // A test file completes its execution 10 | function completed() { 11 | if (--testSuites <= 0) { 12 | // Exit, keeping the status code 13 | setImmediate(() => process.exit(Math.min(failedTests, 255))) 14 | } 15 | } 16 | 17 | // An assertion failed 18 | function failed() { 19 | failedTests++ 20 | } 21 | 22 | export function addCompletionHooks() { 23 | // Mocha hooks will run on the context of 24 | // every spec file calling this function 25 | before(started) 26 | 27 | after(completed) 28 | 29 | afterEach(function () { 30 | if (this.currentTest.state === 'failed') { 31 | failed() 32 | } 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /example/signed-blind/net.ts: -------------------------------------------------------------------------------- 1 | import { GatewayPool, IGatewayDiscoveryParameters } from "@vocdoni/client" 2 | import { EthNetworkID } from "@vocdoni/common" 3 | import { getConfig } from "./config" 4 | 5 | const config = getConfig() 6 | 7 | export async function connectGateways(): Promise { 8 | console.log("Connecting to the gateways") 9 | const options: IGatewayDiscoveryParameters = { 10 | networkId: config.ethNetworkId as EthNetworkID, 11 | environment: config.vocdoniEnvironment, 12 | bootnodesContentUri: config.bootnodesUrlRw, 13 | // numberOfGateways: 2, 14 | // timeout: 10000, 15 | } 16 | const pool = await GatewayPool.discover(options) 17 | 18 | console.log("Connected to", pool.dvoteUri) 19 | console.log("Connected to", pool.provider["connection"].url) 20 | 21 | return pool 22 | } 23 | -------------------------------------------------------------------------------- /example/signed-erc20/net.ts: -------------------------------------------------------------------------------- 1 | import { GatewayPool, IGatewayDiscoveryParameters } from "@vocdoni/client" 2 | import { EthNetworkID } from "@vocdoni/common" 3 | import { getConfig } from "./config" 4 | 5 | const config = getConfig() 6 | 7 | export async function connectGateways(): Promise { 8 | console.log("Connecting to the gateways") 9 | const options: IGatewayDiscoveryParameters = { 10 | networkId: config.ethNetworkId as EthNetworkID, 11 | environment: config.vocdoniEnvironment, 12 | bootnodesContentUri: config.bootnodesUrlRw, 13 | // numberOfGateways: 2, 14 | // timeout: 10000, 15 | } 16 | const pool = await GatewayPool.discover(options) 17 | 18 | console.log("Connected to", pool.dvoteUri) 19 | console.log("Connected to", pool.provider["connection"].url) 20 | 21 | return pool 22 | } 23 | -------------------------------------------------------------------------------- /example/anonymous-off-chain/net.ts: -------------------------------------------------------------------------------- 1 | import { GatewayPool, IGatewayDiscoveryParameters } from "@vocdoni/client" 2 | import { EthNetworkID } from "@vocdoni/common" 3 | import { getConfig } from "./config" 4 | 5 | const config = getConfig() 6 | 7 | export async function connectGateways(): Promise { 8 | console.log("Connecting to the gateways") 9 | const options: IGatewayDiscoveryParameters = { 10 | networkId: config.ethNetworkId as EthNetworkID, 11 | environment: config.vocdoniEnvironment, 12 | bootnodesContentUri: config.bootnodesUrlRw, 13 | numberOfGateways: 2, 14 | // timeout: 10000, 15 | } 16 | const pool = await GatewayPool.discover(options) 17 | 18 | console.log("Connected to", pool.dvoteUri) 19 | console.log("Connected to", pool.provider["connection"].url) 20 | 21 | return pool 22 | } 23 | -------------------------------------------------------------------------------- /example/signed-off-chain/net.ts: -------------------------------------------------------------------------------- 1 | import { GatewayPool, IGatewayDiscoveryParameters } from "@vocdoni/client" 2 | import { EthNetworkID } from "@vocdoni/common" 3 | import { getConfig } from "./config" 4 | 5 | const config = getConfig() 6 | 7 | export async function connectGateways(): Promise { 8 | console.log("Connecting to the gateways") 9 | const options: IGatewayDiscoveryParameters = { 10 | networkId: config.ethNetworkId as EthNetworkID, 11 | environment: config.vocdoniEnvironment, 12 | bootnodesContentUri: config.bootnodesUrlRw, 13 | // numberOfGateways: 2, 14 | // timeout: 10000, 15 | } 16 | const pool = await GatewayPool.discover(options) 17 | 18 | console.log("Connected to", pool.dvoteUri) 19 | console.log("Connected to", pool.provider["connection"].url) 20 | 21 | return pool 22 | } 23 | -------------------------------------------------------------------------------- /example/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "description": "Frontend for the Vocdoni Bridge voting client", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "next dev -p 8080", 8 | "build": "next build", 9 | "export": "next build && next export -o build" 10 | }, 11 | "homepage": "https://github.com/vocdoni/bridge-ui#readme", 12 | "dependencies": { 13 | "@vocdoni/client": "^1.15.0", 14 | "@vocdoni/data-models": "^1.15.2", 15 | "@vocdoni/signing": "^1.16.2", 16 | "@vocdoni/voting": "^1.15.5", 17 | "@walletconnect/web3-provider": "^1.7.1", 18 | "ethers": "^5.5.3", 19 | "next": "^12.0.8", 20 | "react": "^17.0.1", 21 | "react-dom": "^17.0.1", 22 | "web3modal": "^1.9.5" 23 | }, 24 | "devDependencies": { 25 | "@types/react": "^17.0.14", 26 | "typescript": "^4.3.5" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/data-models/src/protobuf.ts: -------------------------------------------------------------------------------- 1 | export { 2 | VoteEnvelope, 3 | Proof, 4 | ProofCA, 5 | ProofCA_Type, 6 | ProofArbo, 7 | ProofIden3, 8 | ProofEthereumStorage, 9 | ProofEthereumAccount, 10 | ProofZkSNARK, 11 | CAbundle, 12 | ProcessStatus as VochainProcessStatus, 13 | Tx, 14 | SignedTx, 15 | RegisterKeyTx, 16 | CensusOrigin as VochainCensusOrigin, 17 | SourceNetworkId, 18 | Census_Type, 19 | ProofArbo_Type 20 | } from "./protobuf/build/ts/vochain/vochain" 21 | 22 | export { 23 | Wallet, 24 | Wallet_AuthMethod, 25 | } from "./protobuf/build/ts/client-store/wallet" 26 | 27 | export { 28 | WalletBackup, 29 | WalletBackup_Recovery, 30 | WalletBackup_Recovery_QuestionEnum, 31 | } from "./protobuf/build/ts/client-store/backup" 32 | 33 | export { 34 | Account, 35 | AccountsStore, 36 | } from "./protobuf/build/ts/client-store/account" 37 | -------------------------------------------------------------------------------- /packages/data-models/src/templates/entity.ts: -------------------------------------------------------------------------------- 1 | import { EntityMetadata } from "../entity" 2 | 3 | export const EntityMetadataTemplate: EntityMetadata = { 4 | version: "1.0", 5 | languages: [ 6 | "default" 7 | ], 8 | name: { 9 | default: "My entity", 10 | // fr: "Ma communauté" 11 | }, 12 | description: { 13 | default: "The description of my entity goes here", 14 | // fr: "La description officielle de ma communauté est ici" 15 | }, 16 | newsFeed: { 17 | default: "ipfs://QmWybQwdBwF81Dt71bNTDDr8PBpW9kNbWtQ64arswaBz1C", 18 | // fr: "https://feed2json.org/convert?url=http://www.intertwingly.net/blog/index.atom" 19 | }, 20 | media: { 21 | avatar: "https://source.unsplash.com/random/800x600", 22 | header: "https://source.unsplash.com/random/800x600", 23 | logo: "https://source.unsplash.com/random/800x600" 24 | }, 25 | meta: {}, 26 | actions: [] 27 | } 28 | -------------------------------------------------------------------------------- /packages/client/src/net/ipfs.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios" 2 | import { Buffer } from 'buffer/' 3 | import { Random } from "@vocdoni/common" 4 | 5 | export const IPFS_GATEWAY_LIST_URI = "https://ipfs.github.io/public-gateway-checker/gateways.json" 6 | 7 | export namespace IPFS { 8 | /** 9 | * Attempt to fetch a file from the list of well-known IPFS gateways 10 | * @param hash IPFS raw hash (no leading protocol) 11 | */ 12 | export function fetchHash(hash: string): Promise { 13 | return axios.get(IPFS_GATEWAY_LIST_URI) 14 | .then(response => { 15 | if (!Array.isArray(response.data)) throw new Error("Could not fetch the IPFS gateway list") 16 | const gwSelection = Random.shuffle(response.data).slice(0, 3) 17 | 18 | return Promise.race(gwSelection.map(gwUri => { 19 | return axios.get(gwUri.replace(/:hash$/g, hash)) 20 | .then(res => Buffer.from(res.data)) 21 | })) 22 | }) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report to help us on reinventing digital voting 4 | title: 'bug: ' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A short summary of what the bug is. Please be clear and concise. 12 | 13 | **To Reproduce (please complete the following information)** 14 | - Config and flags: [e.g. mnemonic="xyz"] 15 | - Steps to reproduce the behavior: 16 | 1. node '...' 17 | 2. make request with '....' 18 | 3. '...' 19 | 4. See error 20 | 21 | **Current behavior** 22 | In depth explanation, if required, or a clear and concise description of what actually happens. 23 | 24 | **Expected behavior** 25 | A clear and concise description of what you expected to happen. 26 | 27 | **System (please complete the following information):** 28 | - OS: [e.g. Manjaro 20.1] 29 | - Software version [e.g. Docker 8, Node 10.15.1] 30 | - Commit hash [e.g. e84617d] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /packages/hashing/test/unit/keccak256.ts: -------------------------------------------------------------------------------- 1 | import "mocha" // using @types/mocha 2 | import { expect } from "chai" 3 | import { addCompletionHooks } from "../mocha-hooks" 4 | 5 | import { Keccak256 } from "../../src" 6 | 7 | addCompletionHooks() 8 | 9 | describe("Keccak256 hashing", () => { 10 | it("Should hash a text", () => { 11 | const text = "This is an example" 12 | 13 | const hash = Keccak256.hashText(text) 14 | expect(hash).to.eq("0x041a34ca22b57f8355a7995e261fded7a10f6b2c634fb9f6bfdbdafcbf556840") 15 | }) 16 | it("Should hash a hex text", () => { 17 | const hex = "0xAAAA" 18 | 19 | const hash = Keccak256.hashHexString(hex) 20 | expect(hash).to.eq("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") 21 | }) 22 | it("Should hash bytes", () => { 23 | const bytes = new Uint8Array([1, 2, 3, 4, 5]) 24 | 25 | const hash = Keccak256.hashBytes(bytes) 26 | expect(hash).to.eq("0x7d87c5ea75f7378bb701e404c50639161af3eff66293e9f375b5f17eb50476f4") 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /packages/signing/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @vocdoni/signing - Changelog 2 | 3 | ## 1.16.2 4 | 5 | - `JsonSignature.sort` changed for accepting `undefined` values 6 | 7 | ## 1.16.1 8 | 9 | - `JsonSignature.sort` now returns the same type as the input parameter, not `JsonLike` 10 | 11 | ## 1.16.0 12 | 13 | - `sortJson` is now exported as `JsonSignature.sort` 14 | - `JsonSignature` and `BytesSignature` now have `signMessage`, `signTransaction`, `isValidMessage`, `isValidTransaction`, `recoverMessagePublicKey` and `recoverTransactionPublicKey` 15 | - Breaking: 16 | - `sign`, `isValid` and `recoverPublicKey` no longer exist on `JsonSignature` or `BytesSignature` 17 | - The signature of `isValidMessage`/`isValidTransaction` is now consistent with `recoverMessagePublicKey`/`recoverTransactionPublicKey` 18 | 19 | ## 1.15.1 20 | 21 | - Exposing `sortJson`, `digestVocdoniSignedPayload` and `normalizeJsonToString` 22 | 23 | ## 1.15.0 24 | 25 | - Adding support for Vocdoni salted signatures 26 | 27 | ## 1.14.0 28 | 29 | - First version of the package, starting from dvote-js version 1.13.2 30 | -------------------------------------------------------------------------------- /packages/hashing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vocdoni/hashing", 3 | "version": "1.15.0", 4 | "description": "JavaScript/TypeScript hashing package", 5 | "main": "dist/index", 6 | "types": "dist/index", 7 | "repository": "https://github.com/vocdoni/dvote-js.git", 8 | "author": "Vocdoni ", 9 | "license": "GPL-3.0-or-later", 10 | "private": false, 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "scripts": { 15 | "prepublishOnly": "npm run build", 16 | "clean": "rimraf dist", 17 | "build": "npm run clean && tsc", 18 | "watch": "tsc -w -p .", 19 | "test": "npm run build && mocha -r ts-node/register test/**/*.ts" 20 | }, 21 | "dependencies": { 22 | "@ethersproject/keccak256": "^5.5.0", 23 | "circomlib": "^0.5.2" 24 | }, 25 | "devDependencies": { 26 | "@types/chai": "^4.1.7", 27 | "@types/mocha": "^9.0.0", 28 | "chai": "^4.2.0", 29 | "mocha": "^9.1.1", 30 | "rimraf": "^3.0.2", 31 | "ts-node": "^10.2.1", 32 | "tslint": "^6.1.3", 33 | "typescript": "^4.4.3" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/contract-wrappers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vocdoni/contract-wrappers", 3 | "version": "1.15.0", 4 | "description": "JavaScript/TypeScript contract wrappers package", 5 | "main": "dist/index", 6 | "types": "dist/index", 7 | "repository": "https://github.com/vocdoni/dvote-js.git", 8 | "author": "Vocdoni ", 9 | "license": "GPL-3.0-or-later", 10 | "private": false, 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "scripts": { 15 | "prepublishOnly": "npm run build", 16 | "clean": "rimraf dist", 17 | "build": "npm run clean && tsc", 18 | "watch": "tsc -w -p .", 19 | "test": "npm run build && mocha -r ts-node/register test/**/*.ts" 20 | }, 21 | "dependencies": { 22 | "@ethersproject/contracts": "^5.5.0", 23 | "dvote-solidity": "^1.4.0" 24 | }, 25 | "devDependencies": { 26 | "@types/chai": "^4.1.7", 27 | "@types/mocha": "^9.0.0", 28 | "chai": "^4.2.0", 29 | "mocha": "^9.1.1", 30 | "rimraf": "^3.0.2", 31 | "ts-node": "^10.2.1", 32 | "tslint": "^6.1.3", 33 | "typescript": "^4.4.3" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/data-models/src/templates/json-feed.ts: -------------------------------------------------------------------------------- 1 | import { JsonFeed } from "../json-feed" 2 | 3 | 4 | export const JsonFeedTemplate: JsonFeed = { 5 | version: "1.0", 6 | title: "My Entity", 7 | home_page_url: "", // http://www.com 8 | description: "This is the description", 9 | feed_url: "", // http://www.com/item.json 10 | icon: "", // http://www.com/icon.png 11 | favicon: "", // http://www.com/favicon.ico 12 | expired: false, 13 | 14 | items: [{ 15 | id: "1234", 16 | title: "Hello world", 17 | summary: "This is a placeholder post", 18 | content_text: "Once upon a time, there was a JSON Feed...", 19 | content_html: "

Once upon a time, there was a JSON Feed...

", 20 | url: "", // http://link.item/1234 21 | image: "https://source.unsplash.com/random/800x600", // http://www.com/image.jpg 22 | tags: ["welcome"], 23 | date_published: "2010-02-07T14:04:00-05:00", 24 | date_modified: "2010-02-07T14:04:00-05:00", 25 | author: { 26 | name: "John Smith", 27 | url: "http://john.smith" 28 | } 29 | }] 30 | } 31 | -------------------------------------------------------------------------------- /example/signed-erc20/config.template.yaml: -------------------------------------------------------------------------------- 1 | readExistingProcess: false # if set, will read the voteMetadata from the JSON instead of creating a new one 2 | stopOnError: false 3 | 4 | processInfoFilePath: "./cached-process-info.json" 5 | 6 | ethNetworkId: "rinkeby" 7 | vocdoniEnvironment: "dev" 8 | tokenAddress: "0x2b7222146a805bba0dbb61869c4b3a03209dffba" 9 | tokenBalanceMappingPosition: 2 10 | privKeys: 11 | - "0x...." 12 | - "0x...." 13 | - "0x...." 14 | - "0x...." 15 | - "0x...." 16 | 17 | bootnodesUrlRw: "https://server/gateways.json" # Bootnode used by default 18 | dvoteGatewayUri: # If set, overrides bootnodesUrlRw 19 | dvoteGatewayPublicKey: # If set, overrides bootnodesUrlRw 20 | web3Uri: 21 | 22 | encryptedVote: true 23 | votesPattern: "all-0" # Vote the first option on all questions 24 | #votesPattern: "all-1" # Vote the second option on all questions 25 | #votesPattern: "all-2" # Vote the third option on all questions 26 | #votesPattern: "all-even" # Vote 0 on even voters and 1 on odd voters 27 | #votesPattern: "incremental" # Vote 0 on question 0, 1 on question 1, 2 on question 2, etc 28 | -------------------------------------------------------------------------------- /example/signed-erc20-signal/net.ts: -------------------------------------------------------------------------------- 1 | import { DVoteGateway, GatewayPool, IGatewayDiscoveryParameters } from "@vocdoni/client" 2 | import { EthNetworkID } from "@vocdoni/common" 3 | import { getConfig } from "./config" 4 | 5 | const config = getConfig() 6 | 7 | export async function connectGateways(): Promise { 8 | console.log("Connecting to the gateways") 9 | const options: IGatewayDiscoveryParameters = { 10 | networkId: config.ethNetworkId as EthNetworkID, 11 | environment: config.vocdoniEnvironment, 12 | bootnodesContentUri: config.bootnodesUrlRw, 13 | // numberOfGateways: 2, 14 | // timeout: 10000, 15 | } 16 | const pool = await GatewayPool.discover(options) 17 | 18 | console.log("Connected to", pool.dvoteUri) 19 | console.log("Connected to", pool.provider["connection"].url) 20 | 21 | return pool 22 | } 23 | 24 | export async function getOracleClient() { 25 | const oracleClient = new DVoteGateway({ 26 | uri: config.oracleUri, 27 | supportedApis: ["oracle"] 28 | }) 29 | await oracleClient.init() 30 | 31 | console.log("Connected to", config.oracleUri) 32 | return oracleClient 33 | } -------------------------------------------------------------------------------- /packages/client/test/helpers/example.ts: -------------------------------------------------------------------------------- 1 | import DevServices from "./all-services" 2 | 3 | async function example() { 4 | const services = new DevServices() 5 | await services.start() 6 | 7 | // DVote client 8 | const dvoteGw = services.dvote.client 9 | await dvoteGw.init() 10 | console.log("DVote ready:", dvoteGw.isReady) 11 | 12 | // By default, a `getBlockStatus` dummy response is prepared on the GW mock, 13 | // so that `connect()` will succeed 14 | 15 | services.dvote.addResponse({ ok: true, message: "Hello, John" }) 16 | const res = await dvoteGw.sendRequest({ method: "publish", name: "John" }) 17 | console.log("Received", res) 18 | 19 | // Web3 client 20 | const entityResolverAddr = "0x1234" // optional 21 | const namespaceAddr = "0x1234" // optional 22 | const processAddr = "0x1234" // optional 23 | 24 | const web3Gw = await services.web3.getClient(entityResolverAddr, namespaceAddr, processAddr) 25 | const myInstance = web3Gw.attach("0x0", []) 26 | const tx = await myInstance.myMethod() 27 | await tx.wait() 28 | 29 | // Do your logic here 30 | 31 | await services.stop() 32 | } 33 | 34 | // example() 35 | -------------------------------------------------------------------------------- /packages/common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vocdoni/common", 3 | "version": "1.15.4", 4 | "description": "JavaScript/TypeScript common package", 5 | "main": "dist/index", 6 | "types": "dist/index", 7 | "repository": "https://github.com/vocdoni/dvote-js.git", 8 | "author": "Vocdoni ", 9 | "license": "GPL-3.0-or-later", 10 | "private": false, 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "scripts": { 15 | "prepublishOnly": "npm run build", 16 | "clean": "rimraf dist", 17 | "build": "npm run clean && tsc", 18 | "watch": "tsc -w -p .", 19 | "test": "npm run build && mocha -r ts-node/register test/**/*.ts" 20 | }, 21 | "dependencies": { 22 | "@ethersproject/bignumber": "^5.5.0", 23 | "@ethersproject/keccak256": "^5.5.0", 24 | "@ethersproject/units": "^5.5.0", 25 | "buffer": "^6.0.3", 26 | "latinize": "^0.5.0" 27 | }, 28 | "devDependencies": { 29 | "@types/chai": "^4.1.7", 30 | "@types/mocha": "^9.0.0", 31 | "chai": "^4.2.0", 32 | "mocha": "^9.1.1", 33 | "rimraf": "^3.0.2", 34 | "ts-node": "^10.2.1", 35 | "tslint": "^6.1.3", 36 | "typescript": "^4.4.3" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/client/src/errors/discovery.ts: -------------------------------------------------------------------------------- 1 | export class GatewayDiscoveryError extends Error { 2 | 3 | public static BOOTNODE_FETCH_ERROR: string = "Could not fetch the bootnode details" 4 | public static BOOTNODE_TIMEOUT_ERROR: string = "Timeout fetching the bootnode details" 5 | public static BOOTNODE_NOT_ENOUGH_GATEWAYS: string = "Not enough gateways found in the bootnode" 6 | public static NO_CANDIDATES_READY: string = "None of the candidates is ready" 7 | 8 | constructor(message?: string) { 9 | super(message ? message : "No working gateways found"); 10 | } 11 | } 12 | 13 | export class GatewayDiscoveryValidationError extends GatewayDiscoveryError { 14 | 15 | public static INVALID_NETWORK_ID: string = "Invalid network ID" 16 | public static INVALID_ENVIRONMENT: string = "Invalid environment" 17 | public static INVALID_BOOTNODE_URI: string = "Invalid bootnode URI" 18 | public static INVALID_NUMBER_GATEWAYS: string = "Invalid number of gateways" 19 | public static INVALID_TIMEOUT: string = "Invalid timeout" 20 | 21 | constructor(message?: string) { 22 | super(message ? "Invalid parameters: " + message : "Invalid parameters"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/client/test/builders/storage-proofs.ts: -------------------------------------------------------------------------------- 1 | // NOTE: This code is borrowed from dvote-solidity 2 | 3 | import { Erc20StorageProofContractMethods, Erc20StorageProofContractDefinition } from "@vocdoni/contract-wrappers" 4 | import { Contract } from "@ethersproject/contracts" 5 | import { TestAccount } from "../helpers/all-services" 6 | import { Web3Gateway } from "../../src" 7 | 8 | 9 | // BUILDER 10 | export default class StorageProofsBuilder { 11 | accounts: TestAccount[] 12 | 13 | entityAccount: TestAccount 14 | 15 | constructor(devAccounts: TestAccount[]) { 16 | this.accounts = devAccounts 17 | this.entityAccount = this.accounts[1] 18 | } 19 | 20 | async build(): Promise { 21 | const deployAccount = this.accounts[0] 22 | const gw = new Web3Gateway(deployAccount.provider) 23 | const contractInstance = await gw.deploy(Erc20StorageProofContractDefinition.abi, Erc20StorageProofContractDefinition.bytecode, { wallet: deployAccount.wallet }) 24 | 25 | return contractInstance.connect(this.entityAccount.wallet) as Contract & Erc20StorageProofContractMethods 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/encryption/README.md: -------------------------------------------------------------------------------- 1 | # @vocdoni/encryption 2 | 3 | @vocdoni/encryption contains encryption helpers for the [dvote-js library](https://github.com/vocdoni/dvote-js/) 4 | 5 | ## Installation 6 | 7 | Use [npm](https://www.npmjs.com/) to install @vocdoni/encryption. 8 | 9 | ```bash 10 | npm install @vocdoni/encryption 11 | ``` 12 | 13 | ## Usage 14 | 15 | #### Asymmetric 16 | 17 | ```ts 18 | import { Asymmetric } from "@vocdoni/encryption" 19 | 20 | // See also: Asymmetric.encryptBytes, Asymmetric.encryptRaw 21 | const encrypted = Asymmetric.encryptString("super secret", publicKey) 22 | const decrypted = Asymmetric.decryptString(encrypted, privateKey) 23 | 24 | console.log(decrypted) 25 | // Prints "super secret 26 | ``` 27 | 28 | #### Symmetric 29 | 30 | ```ts 31 | import { Asymmetric } from "@vocdoni/encryption" 32 | 33 | // See also: Symmetric.encryptBytes, Symmetric.encryptRaw 34 | const encrypted = Symmetric.encryptString("super secret", "my-passphrase") 35 | const decrypted = Symmetric.decryptString(encrypted, "my-passphrase") 36 | 37 | console.log(decrypted) 38 | // Prints "super secret 39 | ``` 40 | 41 | ## Testing 42 | 43 | To execute library tests just run 44 | 45 | ```bash 46 | npm run test 47 | ``` 48 | -------------------------------------------------------------------------------- /packages/encryption/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vocdoni/encryption", 3 | "version": "1.14.1", 4 | "description": "JavaScript/TypeScript encryption package", 5 | "main": "dist/index", 6 | "types": "dist/index", 7 | "repository": "https://github.com/vocdoni/dvote-js.git", 8 | "author": "Vocdoni ", 9 | "license": "GPL-3.0-or-later", 10 | "private": false, 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "scripts": { 15 | "prepublishOnly": "npm run build", 16 | "clean": "rimraf dist", 17 | "build": "npm run clean && tsc", 18 | "watch": "tsc -w -p .", 19 | "test": "npm run build && mocha -r ts-node/register test/**/*.ts" 20 | }, 21 | "dependencies": { 22 | "@ethersproject/sha2": "^5.5.0", 23 | "@vocdoni/common": "^1.15.1", 24 | "buffer": "^6.0.3", 25 | "tweetnacl": "^1.0.3", 26 | "tweetnacl-sealedbox-js": "^1.2.0" 27 | }, 28 | "devDependencies": { 29 | "@ethersproject/wallet": "^5.5.0", 30 | "@types/chai": "^4.1.7", 31 | "@types/mocha": "^9.0.0", 32 | "chai": "^4.2.0", 33 | "mocha": "^9.1.1", 34 | "rimraf": "^3.0.2", 35 | "ts-node": "^10.2.1", 36 | "tslint": "^6.1.3", 37 | "typescript": "^4.4.3" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/census/scripts/wipe-snarkjs-package-module.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | 4 | try { 5 | const startPath = path.dirname(require.resolve("snarkjs")); 6 | const packagePath = findSnarkJsPackageFile(startPath); 7 | const packageJson = require(packagePath); 8 | 9 | if (packageJson.name !== "snarkjs") { 10 | throw new Error("Cannot find the snarkjs package.json file"); 11 | } else if (!packageJson.module) { 12 | process.exit(0); 13 | } 14 | 15 | delete packageJson.module; 16 | fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2)); 17 | } catch (err) { 18 | console.error(err); 19 | process.exit(1); 20 | } 21 | 22 | // Helpers 23 | 24 | function findSnarkJsPackageFile(basePath) { 25 | while (!fs.existsSync(path.join(basePath, "package.json"))) { 26 | const basePathItems = basePath.split(path.sep); 27 | basePathItems.pop(); 28 | 29 | if (basePathItems.length === 0 || !basePathItems.includes("snarkjs")) { 30 | throw new Error("Cannot find the snarkjs package.json file"); 31 | } 32 | basePath = basePathItems.join(path.sep); 33 | } 34 | return path.join(basePath, "package.json"); 35 | } 36 | -------------------------------------------------------------------------------- /packages/common/test/unit/random.ts: -------------------------------------------------------------------------------- 1 | import "mocha" // using @types/mocha 2 | import { expect } from "chai" 3 | import { addCompletionHooks } from "../mocha-hooks" 4 | 5 | import { Random } from "../../src" 6 | 7 | addCompletionHooks() 8 | 9 | describe("Random generation", () => { 10 | it("Should generate a random buffer from given length", () => { 11 | const bytes = Random.getBytes(8) 12 | 13 | expect(bytes).to.be.instanceof(Uint8Array) 14 | expect(bytes.length).to.eq(8) 15 | }) 16 | it("Should generate a random hex", () => { 17 | const hex = Random.getHex() 18 | 19 | expect(hex).to.be.a("string") 20 | expect(hex.substring(0, 2)).to.eq("0x") 21 | expect(hex.slice(2).length).to.eq(64) 22 | }) 23 | it("Should generate a random bigint", () => { 24 | const bigint = Random.getBigInt(BigInt(256)) 25 | 26 | expect(bigint).to.be.a("bigint") 27 | }) 28 | it("Should shuffle an array in random order", () => { 29 | const nums = [1, 2, 3, 4] 30 | const shuffle = Random.shuffle(nums) 31 | 32 | expect(shuffle).to.be.a("array") 33 | expect(shuffle.length).to.eq(nums.length) 34 | expect(shuffle).to.have.members(nums) 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /packages/client/test/builders/namespace.ts: -------------------------------------------------------------------------------- 1 | // NOTE: This code is borrowed from dvote-solidity 2 | 3 | 4 | import { 5 | NamespacesContractMethods, 6 | NamespacesContractDefinition 7 | } from "@vocdoni/contract-wrappers" 8 | import { Contract, ContractFactory } from "@ethersproject/contracts" 9 | import { TestAccount } from "../helpers/all-services" 10 | 11 | export const DEFAULT_NAMESPACE = 1 // The id that the first process contract will be assigned to 12 | 13 | // BUILDER 14 | export default class NamespaceBuilder { 15 | accounts: TestAccount[] 16 | 17 | entityAccount: TestAccount 18 | 19 | constructor(devAccounts: TestAccount[]) { 20 | this.accounts = devAccounts 21 | this.entityAccount = this.accounts[1] 22 | } 23 | 24 | async build(): Promise { 25 | const deployAccount = this.accounts[0] 26 | const contractFactory = new ContractFactory(NamespacesContractDefinition.abi, NamespacesContractDefinition.bytecode, deployAccount.wallet) 27 | let contractInstance = await contractFactory.deploy() as Contract & NamespacesContractMethods 28 | 29 | return contractInstance.connect(this.entityAccount.wallet) as Contract & NamespacesContractMethods 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/client/src/wrappers/content-uri.ts: -------------------------------------------------------------------------------- 1 | export class ContentUri { 2 | protected _contentUri: string[]; 3 | 4 | /** Parses the given string into a Content URI */ 5 | constructor(contentUri: string) { 6 | if (typeof contentUri != "string") throw new Error("Invalid contentUri"); 7 | this._contentUri = contentUri.split(","); 8 | } 9 | 10 | /** Returns the Content URI as a string representation */ 11 | public toString(): string { 12 | return this._contentUri.join(",") 13 | } 14 | 15 | /** Returns the individual URI's contained in the Content URI */ 16 | public get items(): string[] { return this._contentUri } 17 | 18 | /** The hash of all IPFS items */ 19 | public get ipfsHash(): string { 20 | const item = this._contentUri.find(i => i.indexOf("ipfs://") == 0) 21 | if (!item) return null 22 | return item.replace(/^ipfs:\/\//, "") 23 | } 24 | 25 | /** The https endpoints */ 26 | public get httpsItems(): string[] { 27 | return this._contentUri.filter(i => i.indexOf("https://") == 0) 28 | } 29 | 30 | /** The http endpoints */ 31 | public get httpItems(): string[] { 32 | return this._contentUri.filter(i => i.indexOf("http://") == 0) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/voting/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vocdoni/voting", 3 | "version": "1.16.4", 4 | "description": "JavaScript/TypeScript voting package", 5 | "main": "dist/index", 6 | "types": "dist/index", 7 | "repository": "https://github.com/vocdoni/dvote-js.git", 8 | "author": "Vocdoni ", 9 | "license": "GPL-3.0-or-later", 10 | "private": false, 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "scripts": { 15 | "prepublishOnly": "npm run build", 16 | "clean": "rimraf dist", 17 | "build": "npm run clean && tsc", 18 | "watch": "tsc -w -p .", 19 | "test": "npm run build && mocha -r ts-node/register test/**/*.ts" 20 | }, 21 | "dependencies": { 22 | "@vocdoni/client": "^1.16.6", 23 | "@vocdoni/common": "^1.15.1", 24 | "@vocdoni/contract-wrappers": "^1.15.0", 25 | "@vocdoni/data-models": "^1.15.3", 26 | "@vocdoni/hashing": "^1.15.0", 27 | "@vocdoni/signing": "^1.16.2", 28 | "buffer": "^6.0.3" 29 | }, 30 | "devDependencies": { 31 | "@types/chai": "^4.1.7", 32 | "@types/mocha": "^9.0.0", 33 | "chai": "^4.2.0", 34 | "mocha": "^9.1.1", 35 | "rimraf": "^3.0.2", 36 | "ts-node": "^10.2.1", 37 | "tslint": "^6.1.3", 38 | "typescript": "^4.4.3" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /example/signed-blind/config.template.yaml: -------------------------------------------------------------------------------- 1 | readExistingProcess: false # if set, will read the voteMetadata from the JSON instead of creating a new one 2 | stopOnError: false 3 | 4 | processInfoFilePath: "./cached-process-info.json" 5 | 6 | cspPublicKey: 032a8da91187785109404da8ecdb293110a2ee9decdf049a145fe3b9ac86b4fee7 7 | cspUri: https://server/uri 8 | 9 | # Either the private key or the wallet/hdPath of the entity 10 | privateKey: 11 | mnemonic: "" 12 | hdPath: "m/44'/60'/0'/0/1" 13 | ethNetworkId: "rinkeby" 14 | 15 | vocdoniEnvironment: "dev" # "stg" or "prod" 16 | bootnodesUrlRw: "https://server/gateways.json" # Bootnode used by default 17 | dvoteGatewayUri: # If set, overrides bootnodesUrlRw 18 | dvoteGatewayPublicKey: # If set, overrides bootnodesUrlRw 19 | 20 | numAccounts: 10000 21 | maxConcurrency: 4500 22 | 23 | encryptedVote: true 24 | votesPattern: "all-0" # Vote the first option on all questions 25 | #votesPattern: "all-1" # Vote the second option on all questions 26 | #votesPattern: "all-2" # Vote the third option on all questions 27 | #votesPattern: "all-even" # Vote 0 on even voters and 1 on odd voters 28 | #votesPattern: "incremental" # Vote 0 on question 0, 1 on question 1, 2 on question 2, etc -------------------------------------------------------------------------------- /example/anonymous-off-chain/config.template.yaml: -------------------------------------------------------------------------------- 1 | readExistingAccounts: false # if set, will use the wallets from the JSON file instead of creating them 2 | readExistingProcess: false # if set, will read the voteMetadata from the JSON instead of creating a new one 3 | stopOnError: false 4 | 5 | accountListFilePath: "./cached-accounts.json" 6 | processInfoFilePath: "./cached-process-info.json" 7 | 8 | mnemonic: "" # The mnemonic of the entity 9 | ethPath: "m/44'/60'/0'/0/1" 10 | ethNetworkId: "rinkeby" 11 | 12 | vocdoniEnvironment: "dev" # "stg" or "prod" 13 | bootnodesUrlRw: "https://server/gateways.json" # Bootnode used by default 14 | dvoteGatewayUri: # If set, overrides bootnodesUrlRw 15 | dvoteGatewayPublicKey: # If set, overrides bootnodesUrlRw 16 | 17 | numAccounts: 1000 18 | maxConcurrency: 450 19 | 20 | encryptedVote: true 21 | votesPattern: "all-0" # Vote the first option on all questions 22 | #votesPattern: "all-1" # Vote the second option on all questions 23 | #votesPattern: "all-2" # Vote the third option on all questions 24 | #votesPattern: "all-even" # Vote 0 on even voters and 1 on odd voters 25 | #votesPattern: "incremental" # Vote 0 on question 0, 1 on question 1, 2 on question 2, etc -------------------------------------------------------------------------------- /example/signed-off-chain/config.template.yaml: -------------------------------------------------------------------------------- 1 | readExistingAccounts: false # if set, will use the wallets from the JSON file instead of creating them 2 | readExistingProcess: false # if set, will read the voteMetadata from the JSON instead of creating a new one 3 | stopOnError: false 4 | 5 | accountListFilePath: "./cached-accounts.json" 6 | processInfoFilePath: "./cached-process-info.json" 7 | 8 | mnemonic: "" # The mnemonic of the entity 9 | ethPath: "m/44'/60'/0'/0/1" 10 | ethNetworkId: "rinkeby" 11 | 12 | vocdoniEnvironment: "dev" # "stg" or "prod" 13 | bootnodesUrlRw: "https://server/gateways.json" # Bootnode used by default 14 | dvoteGatewayUri: # If set, overrides bootnodesUrlRw 15 | dvoteGatewayPublicKey: # If set, overrides bootnodesUrlRw 16 | 17 | numAccounts: 10000 18 | maxConcurrency: 4500 19 | 20 | encryptedVote: true 21 | votesPattern: "all-0" # Vote the first option on all questions 22 | #votesPattern: "all-1" # Vote the second option on all questions 23 | #votesPattern: "all-2" # Vote the third option on all questions 24 | #votesPattern: "all-even" # Vote 0 on even voters and 1 on odd voters 25 | #votesPattern: "incremental" # Vote 0 on question 0, 1 on question 1, 2 on question 2, etc -------------------------------------------------------------------------------- /packages/hashing/README.md: -------------------------------------------------------------------------------- 1 | # @vocdoni/hashing 2 | 3 | @vocdoni/hashing contains hashing helpers for the [dvote-js library](https://github.com/vocdoni/dvote-js/) 4 | 5 | ## Installation 6 | 7 | Use [npm](https://www.npmjs.com/) to install @vocdoni/hashing. 8 | 9 | ```bash 10 | npm install @vocdoni/hashing 11 | ``` 12 | 13 | ## Usage 14 | 15 | #### Keccak256 16 | 17 | ```ts 18 | import { Keccak256 } from "@vocdoni/hashing" 19 | 20 | const text = "This is an example" 21 | Keccak256.hashText(text) 22 | // returns '0x041a34ca22b57f8355a7995e261fded7a10f6b2c634fb9f6bfdbdafcbf556840' 23 | 24 | const hex = "0xAAAA" 25 | Keccak256.hashHexString(hex) 26 | // returns '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470' 27 | 28 | const bytes = new Uint8Array([1, 2, 3, 4, 5]) 29 | Keccak256.hashBytes(bytes) 30 | // returns '0x7d87c5ea75f7378bb701e404c50639161af3eff66293e9f375b5f17eb50476f4' 31 | ``` 32 | 33 | #### Poseidon 34 | 35 | ```ts 36 | import { Poseidon } from "@vocdoni/hashing" 37 | 38 | const BI_1 = BigInt("1") 39 | const BI_2 = BigInt("2") 40 | 41 | Poseidon.hash([BI_1, BI_2]) 42 | // returns '7853200120776062878684798364095072458815029376092732009249414926327459813530' 43 | ``` 44 | 45 | ## Testing 46 | 47 | To execute library tests just run 48 | 49 | ```bash 50 | npm run test 51 | ``` 52 | -------------------------------------------------------------------------------- /example/signed-erc20-signal/config.template.yaml: -------------------------------------------------------------------------------- 1 | readExistingProcess: false # if set, will read the voteMetadata from the JSON instead of creating a new one 2 | stopOnError: false 3 | 4 | processInfoFilePath: "./cached-process-info.json" 5 | 6 | ethNetworkId: "rinkeby" 7 | vocdoniEnvironment: "dev" 8 | tokenAddress: "0x2b7222146a805bba0dbb61869c4b3a03209dffba" 9 | tokenBalanceMappingPosition: 2 10 | privKeys: 11 | - "0x...." 12 | - "0x...." 13 | - "0x...." 14 | - "0x...." 15 | - "0x...." 16 | 17 | bootnodesUrlRw: "https://server/gateways.json" # Bootnode used by default 18 | dvoteGatewayUri: # If set, overrides bootnodesUrlRw 19 | dvoteGatewayPublicKey: # If set, overrides bootnodesUrlRw 20 | web3Uri: # If set, overrides bootnodesUrlRw 21 | oracleUri: "https://signaling-oracle.dev.vocdoni.net/dvote" 22 | 23 | encryptedVote: true 24 | votesPattern: "all-0" # Vote the first option on all questions 25 | #votesPattern: "all-1" # Vote the second option on all questions 26 | #votesPattern: "all-2" # Vote the third option on all questions 27 | #votesPattern: "all-even" # Vote 0 on even voters and 1 on odd voters 28 | #votesPattern: "incremental" # Vote 0 on question 0, 1 on question 1, 2 on question 2, etc 29 | 30 | -------------------------------------------------------------------------------- /packages/data-models/src/templates/process.ts: -------------------------------------------------------------------------------- 1 | import { ProcessMetadata } from "../voting-meta" 2 | 3 | export const ProcessMetadataTemplate: ProcessMetadata = { 4 | version: "1.1", 5 | title: { 6 | default: "" // Universal Basic Income 7 | }, 8 | description: { 9 | default: "" // ## Markdown text goes here\n### Abstract 10 | }, 11 | media: { 12 | header: "https://source.unsplash.com/random/800x600", // Content URI 13 | streamUri: "", 14 | }, 15 | meta: {}, 16 | questions: [ 17 | { 18 | title: { 19 | default: "" // Should universal basic income become a human right? 20 | }, 21 | description: { 22 | default: "" // ## Markdown text goes here\n### Abstract 23 | }, 24 | choices: [ 25 | { 26 | title: { 27 | default: "Yes", 28 | }, 29 | value: 0 30 | }, 31 | { 32 | title: { 33 | default: "No", 34 | }, 35 | value: 1 36 | } 37 | ] 38 | } 39 | ], 40 | results: { 41 | aggregation: "discrete-counting", 42 | display: "multiple-question" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/voting/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @vocdoni/voting - Changelog 2 | 3 | ## 1.16.3 4 | 5 | - Added archive support for `VotingApi.getProcessList` 6 | 7 | ## 1.16.2 8 | 9 | - Fixed archive results not depending on encryption keys and `GatewayArchive` improvements adding `startDate` and `endDate` 10 | 11 | ## 1.16.1 12 | 13 | - Amending the type of signature applied 14 | 15 | ## 1.16.0 16 | 17 | - Using salted signatures 18 | 19 | ## 1.15.5 20 | 21 | - Added `getAnonymousHexNullifier` for calculating the anonymous hexadecimal nullifier 22 | 23 | ## 1.15.4 24 | 25 | - Adding missing flags in `ProcessState` 26 | 27 | ## 1.15.3 28 | 29 | - Fixing the double source of truth problem with the process status 30 | 31 | ## 1.15.2 32 | 33 | - Removing the unneeded `walletOrSigner` parameter from `packageSignedEnvelope` 34 | 35 | ## 1.15.1 36 | ## 1.15.0 37 | 38 | - Allowing to compute ZK Proofs for anonymous voting 39 | - Splitting remote/local methods into `VotingApi.xxx` and `Voting.xxx` 40 | 41 | ## 1.14.0 42 | 43 | - First version of the package, starting from dvote-js version 1.13.2 44 | - Breaking change on `VotingOracleApi.newProcessErc20()` 45 | - Now expecting `tokenDetails` as a parameter 46 | - See `VotingOracleApi.newProcessErc20` comment in `packages > voting > src > voting.ts` 47 | - Moving `registerVoterKey` to `CensusOnChainApi.registerVoterKey` (`@vocdoni/census`) 48 | -------------------------------------------------------------------------------- /packages/client/test/integration/discovery.ts: -------------------------------------------------------------------------------- 1 | import "mocha" // using @types/mocha 2 | import { expect } from "chai" 3 | 4 | 5 | const DEFAULT_BOOTNODES_URL = "https://bootnodes.vocdoni.net/gateways.dev.json" 6 | 7 | describe("Discovery", () => { 8 | 9 | it("should be implemented") 10 | 11 | // it("getRandomGatewayInfo should provide a gateway for each network ID", async () => { 12 | // const gw = await getRandomGatewayInfo("goerli") 13 | 14 | // expect(gw["non-existing-network-id"]).to.be.undefined 15 | // expect(gw["goerli"]).to.be.ok 16 | // expect(typeof gw["goerli"].dvote).to.equal("string") 17 | // expect(typeof gw["goerli"].publicKey).to.equal("string") 18 | // expect(Array.isArray(gw["goerli"].supportedApis)).to.be.true 19 | // expect(typeof gw["goerli"].web3).to.equal("string") 20 | 21 | // const gw2 = await getRandomGatewayInfo("goerli", DEFAULT_BOOTNODES_URL) 22 | 23 | // expect(gw2["non-existing-network-id"]).to.be.undefined 24 | // expect(gw2["goerli"]).to.be.ok 25 | // expect(typeof gw2["goerli"].dvote).to.equal("string") 26 | // expect(typeof gw2["goerli"].publicKey).to.equal("string") 27 | // expect(Array.isArray(gw2["goerli"].supportedApis)).to.be.true 28 | // expect(typeof gw2["goerli"].web3).to.equal("string") 29 | // }).timeout(12000) 30 | 31 | 32 | }) 33 | -------------------------------------------------------------------------------- /packages/signing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vocdoni/signing", 3 | "version": "1.16.2", 4 | "description": "JavaScript/TypeScript signing package", 5 | "main": "dist/index", 6 | "types": "dist/index", 7 | "repository": "https://github.com/vocdoni/dvote-js.git", 8 | "author": "Vocdoni ", 9 | "license": "GPL-3.0-or-later", 10 | "private": false, 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "scripts": { 15 | "prepublishOnly": "npm run build", 16 | "clean": "rimraf dist", 17 | "build": "npm run clean && tsc", 18 | "watch": "tsc -w -p .", 19 | "test": "npm run build && mocha -r ts-node/register test/**/*.ts" 20 | }, 21 | "dependencies": { 22 | "@ethersproject/abstract-signer": "^5.5.0", 23 | "@ethersproject/bytes": "^5.5.0", 24 | "@ethersproject/hash": "^5.5.0", 25 | "@ethersproject/keccak256": "^5.5.0", 26 | "@ethersproject/providers": "^5.5.0", 27 | "@ethersproject/signing-key": "^5.5.0", 28 | "@ethersproject/transactions": "^5.5.0", 29 | "@ethersproject/wallet": "^5.5.0", 30 | "@vocdoni/common": "^1.15.3" 31 | }, 32 | "devDependencies": { 33 | "@types/chai": "^4.1.7", 34 | "@types/mocha": "^9.0.0", 35 | "chai": "^4.2.0", 36 | "mocha": "^9.1.1", 37 | "rimraf": "^3.0.2", 38 | "ts-node": "^10.2.1", 39 | "tslint": "^6.1.3", 40 | "typescript": "^4.4.3" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/data-models/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vocdoni/data-models", 3 | "version": "1.15.3", 4 | "description": "JavaScript/TypeScript models package", 5 | "main": "dist/index", 6 | "types": "dist/index", 7 | "repository": "https://github.com/vocdoni/dvote-js.git", 8 | "author": "Vocdoni ", 9 | "license": "GPL-3.0-or-later", 10 | "private": false, 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "scripts": { 15 | "prepublishOnly": "npm run build", 16 | "clean": "rimraf dist", 17 | "prebuild": "git submodule init && git submodule update", 18 | "build": "npm run clean && tsc", 19 | "watch": "tsc -w -p .", 20 | "test": "npm run build && mocha -r ts-node/register test/**/*.ts" 21 | }, 22 | "dependencies": { 23 | "@ethersproject/bignumber": "^5.5.0", 24 | "@vocdoni/common": "^1.15.1", 25 | "@vocdoni/contract-wrappers": "^1.15.0", 26 | "@vocdoni/encryption": "^1.14.0", 27 | "buffer": "^6.0.3", 28 | "iso-language-codes": "^1.0.6", 29 | "protobufjs": "^6.10.2", 30 | "yup": "^0.32.9" 31 | }, 32 | "devDependencies": { 33 | "@types/chai": "^4.1.7", 34 | "@types/mocha": "^9.0.0", 35 | "@types/yup": "^0.29.13", 36 | "chai": "^4.2.0", 37 | "mocha": "^9.1.1", 38 | "rimraf": "^3.0.2", 39 | "ts-node": "^10.2.1", 40 | "tslint": "^6.1.3", 41 | "typescript": "^4.4.3" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/wallets/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vocdoni/wallets", 3 | "version": "1.16.0", 4 | "description": "JavaScript/TypeScript wallets package", 5 | "main": "dist/index", 6 | "types": "dist/index", 7 | "repository": "https://github.com/vocdoni/dvote-js.git", 8 | "author": "Vocdoni ", 9 | "license": "GPL-3.0-or-later", 10 | "private": false, 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "scripts": { 15 | "prepublishOnly": "npm run build", 16 | "clean": "rimraf dist", 17 | "build": "npm run clean && tsc", 18 | "watch": "tsc -w -p .", 19 | "test": "npm run build && mocha -r ts-node/register test/**/*.ts" 20 | }, 21 | "dependencies": { 22 | "@ethersproject/bytes": "^5.5.0", 23 | "@ethersproject/keccak256": "^5.5.0", 24 | "@ethersproject/providers": "^5.5.0", 25 | "@ethersproject/strings": "^5.5.0", 26 | "@ethersproject/wallet": "^5.5.0", 27 | "@vocdoni/common": "^1.15.3", 28 | "blake-hash": "^2.0.0", 29 | "circomlib": "^0.5.2", 30 | "ffjavascript": "^0.2.38" 31 | }, 32 | "devDependencies": { 33 | "@ethersproject/signing-key": "^5.5.0", 34 | "@types/chai": "^4.1.7", 35 | "@types/mocha": "^9.0.0", 36 | "@vocdoni/signing": "^1.16.2", 37 | "chai": "^4.2.0", 38 | "mocha": "^9.1.1", 39 | "rimraf": "^3.0.2", 40 | "ts-node": "^10.2.1", 41 | "tslint": "^6.1.3", 42 | "typescript": "^4.4.3" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example/web/pages/network.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { useState } from 'react' 3 | import { FileApi } from "@vocdoni/client" 4 | import { getClient } from "../lib/net" 5 | 6 | const IPFS_URI = "ipfs://QmaPrHy12pkxthX8CHeYegGRgVdumv5TEFhUpGAvCyTXPt" 7 | 8 | const Page = () => { 9 | const [loading, setLoading] = useState(false) 10 | const [bytes, setBytes] = useState(new Uint8Array()) 11 | const [string, setString] = useState("") 12 | 13 | const loadFile = () => { 14 | setLoading(true) 15 | getClient() 16 | .then(gwPool => { 17 | return Promise.all([ 18 | FileApi.fetchBytes(IPFS_URI, gwPool), 19 | FileApi.fetchString(IPFS_URI, gwPool), 20 | ]) 21 | }) 22 | .then(results => { 23 | setLoading(false) 24 | 25 | setBytes(results[0]) 26 | setString(results[1]) 27 | }) 28 | .catch(err => { 29 | setLoading(false) 30 | 31 | alert("Could not connect to the network nodes: " + err.message) 32 | }) 33 | } 34 | 35 | return
36 |

File API

37 |

Status: ({loading ? "loading" : "ready"})

38 |

URI: {IPFS_URI}

39 |

String value

40 |
{string}
41 |

Bytes value

42 |
{bytes.slice(0, 32).join(", ")}...
43 | {!loading && !string ?

: null} 44 |
45 | } 46 | 47 | export default Page 48 | -------------------------------------------------------------------------------- /packages/wallets/test/unit/common.ts: -------------------------------------------------------------------------------- 1 | import "mocha" // using @types/mocha 2 | import { expect } from "chai" 3 | import { addCompletionHooks } from "../mocha-hooks" 4 | import { Random } from "@vocdoni/common" 5 | 6 | addCompletionHooks() 7 | 8 | describe("Standalone Ethereum wallets", () => { 9 | it("Should generate a new random seed for a standalone wallet", () => { 10 | const seed1 = Random.getHex() 11 | const seed2 = Random.getHex() 12 | const seed3 = Random.getHex() 13 | const seed4 = Random.getHex() 14 | 15 | expect(seed1.length).to.eq(66) 16 | expect(seed2.length).to.eq(66) 17 | expect(seed3.length).to.eq(66) 18 | expect(seed4.length).to.eq(66) 19 | 20 | expect(seed1.substring(0, 2)).to.eq('0x') 21 | expect(seed2.substring(0, 2)).to.eq('0x') 22 | expect(seed3.substring(0, 2)).to.eq('0x') 23 | expect(seed4.substring(0, 2)).to.eq('0x') 24 | 25 | expect(seed1.match(/^0x[0-9a-fA-F]{64}$/)).to.be.ok 26 | expect(seed2.match(/^0x[0-9a-fA-F]{64}$/)).to.be.ok 27 | expect(seed3.match(/^0x[0-9a-fA-F]{64}$/)).to.be.ok 28 | expect(seed4.match(/^0x[0-9a-fA-F]{64}$/)).to.be.ok 29 | 30 | expect(seed1).to.be.not.eq(seed2) 31 | expect(seed1).to.be.not.eq(seed3) 32 | expect(seed1).to.be.not.eq(seed4) 33 | expect(seed2).to.be.not.eq(seed3) 34 | expect(seed2).to.be.not.eq(seed4) 35 | expect(seed3).to.be.not.eq(seed4) 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /packages/client/test/builders/results.ts: -------------------------------------------------------------------------------- 1 | // NOTE: This code is borrowed from dvote-solidity 2 | 3 | import { ResultsContractMethods, ResultsContractDefinition } from "@vocdoni/contract-wrappers" 4 | import { Contract, ContractFactory } from "@ethersproject/contracts" 5 | import { TestAccount } from "../helpers/all-services" 6 | import GenesisBuilder from "./genesis" 7 | 8 | // BUILDER 9 | export default class ResultsBuilder { 10 | accounts: TestAccount[] 11 | 12 | entityAccount: TestAccount 13 | genesisAddress: string 14 | 15 | constructor(devAccounts: TestAccount[]) { 16 | this.accounts = devAccounts 17 | this.entityAccount = this.accounts[1] 18 | } 19 | async build(): Promise { 20 | if (!this.genesisAddress) { 21 | // Deploy one 22 | const genesisInstance = await new GenesisBuilder(this.accounts).build() 23 | this.genesisAddress = genesisInstance.address 24 | } 25 | 26 | const deployAccount = this.accounts[0] 27 | const contractFactory = new ContractFactory(ResultsContractDefinition.abi, ResultsContractDefinition.bytecode, deployAccount.wallet) 28 | let contractInstance = await contractFactory.deploy(this.genesisAddress) as Contract & ResultsContractMethods 29 | 30 | return contractInstance.connect(this.entityAccount.wallet) as Contract & ResultsContractMethods 31 | } 32 | 33 | withGenesisAddress(genesisAddr: string) { 34 | this.genesisAddress = genesisAddr 35 | 36 | return this 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/wallets/README.md: -------------------------------------------------------------------------------- 1 | # @vocdoni/wallets 2 | 3 | @vocdoni/wallets contains wallets helpers for the [dvote-js library](https://github.com/vocdoni/dvote-js/) 4 | 5 | ## Installation 6 | 7 | Use [npm](https://www.npmjs.com/) to install @vocdoni/wallets. 8 | 9 | ```bash 10 | npm install @vocdoni/wallets 11 | ``` 12 | 13 | ## Usage 14 | 15 | ### Metamask 16 | 17 | ```ts 18 | import { SignerUtil } from "@vocdoni/wallets" 19 | 20 | const signer = SignerUtil.fromInjectedWeb3() 21 | // wallet.signMessage(...) 22 | 23 | // See ethers.js > Signer 24 | ``` 25 | 26 | ### Ethereum 27 | 28 | ```ts 29 | import { WalletUtil } from "@vocdoni/wallets" 30 | 31 | const wallet = WalletUtil.fromSeededPassphrase("my-passphrase", hexSeed) 32 | // wallet.signMessage(...) 33 | 34 | // See ethers.js > Wallet 35 | ``` 36 | 37 | ### Baby JubJub 38 | 39 | ```ts 40 | import { WalletBabyJub } from "@vocdoni/wallets" 41 | import { Random } from "@vocdoni/common" 42 | 43 | const wallet1 = WalletBabyJub.fromProcessCredentials(loginKey, processId, chosenSecret) 44 | 45 | const seed = Random.getHex() 46 | const wallet2 = WalletBabyJub.fromHexSeed(seed) 47 | 48 | const privK = "123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0" 49 | const wallet3 = new WalletBabyJub(privK) 50 | 51 | // sign 52 | const msg = Buffer.from("my message", "utf8") 53 | const sign = wallet1.sign(msg) 54 | 55 | // verify 56 | const valid = WalletBabyJub.verify(msg, sig, wallet1.publicKey) 57 | // => true 58 | ``` 59 | 60 | ## Testing 61 | 62 | To execute library tests just run 63 | 64 | ```bash 65 | npm run test 66 | ``` 67 | -------------------------------------------------------------------------------- /example/signed-blind/entity.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert" 2 | import { Wallet } from "@ethersproject/wallet" 3 | import { EntityApi } from "@vocdoni/voting" 4 | import { IGatewayClient } from "@vocdoni/client" 5 | import { EntityMetadata, EntityMetadataTemplate } from "@vocdoni/data-models" 6 | 7 | export async function ensureEntityMetadata(entityWallet: Wallet, gwPool: IGatewayClient) { 8 | if ((await entityWallet.getBalance()).eq(0)) { 9 | throw new Error("The account has no ether") 10 | } 11 | 12 | const meta = await EntityApi.getMetadata(entityWallet.address, gwPool).catch(() => null) 13 | if (!!meta) return // already present 14 | 15 | console.log("Setting Metadata for entity", entityWallet.address) 16 | 17 | const metadata: EntityMetadata = JSON.parse(JSON.stringify(EntityMetadataTemplate)) 18 | 19 | metadata.name = { default: "Test Organization Name" } 20 | metadata.description = { default: "Description of the test organization goes here" } 21 | metadata.media = { 22 | avatar: "", 23 | header: "", 24 | logo: "" 25 | } 26 | 27 | await EntityApi.setMetadata(entityWallet.address, metadata, entityWallet, gwPool) 28 | console.log("Metadata updated") 29 | 30 | // Read back 31 | const entityMetaPost = await EntityApi.getMetadata(entityWallet.address, gwPool) 32 | 33 | assert(entityMetaPost) 34 | assert.strictEqual(entityMetaPost.name.default, metadata.name.default) 35 | assert.strictEqual(entityMetaPost.description.default, metadata.description.default) 36 | 37 | return entityMetaPost 38 | } 39 | -------------------------------------------------------------------------------- /example/anonymous-off-chain/entity.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert" 2 | import { Wallet } from "@ethersproject/wallet" 3 | import { EntityApi } from "@vocdoni/voting" 4 | import { IGatewayClient } from "@vocdoni/client" 5 | import { EntityMetadata, EntityMetadataTemplate } from "@vocdoni/data-models" 6 | 7 | export async function ensureEntityMetadata(entityWallet: Wallet, gwPool: IGatewayClient) { 8 | if ((await entityWallet.getBalance()).eq(0)) { 9 | throw new Error("The account has no ether") 10 | } 11 | 12 | const meta = await EntityApi.getMetadata(entityWallet.address, gwPool).catch(() => null) 13 | if (!!meta) return // already present 14 | 15 | console.log("Setting Metadata for entity", entityWallet.address) 16 | 17 | const metadata: EntityMetadata = JSON.parse(JSON.stringify(EntityMetadataTemplate)) 18 | 19 | metadata.name = { default: "Test Organization Name" } 20 | metadata.description = { default: "Description of the test organization goes here" } 21 | metadata.media = { 22 | avatar: "", 23 | header: "", 24 | logo: "" 25 | } 26 | 27 | await EntityApi.setMetadata(entityWallet.address, metadata, entityWallet, gwPool) 28 | console.log("Metadata updated") 29 | 30 | // Read back 31 | const entityMetaPost = await EntityApi.getMetadata(entityWallet.address, gwPool) 32 | 33 | assert(entityMetaPost) 34 | assert.strictEqual(entityMetaPost.name.default, metadata.name.default) 35 | assert.strictEqual(entityMetaPost.description.default, metadata.description.default) 36 | 37 | return entityMetaPost 38 | } 39 | -------------------------------------------------------------------------------- /example/signed-off-chain/entity.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert" 2 | import { Wallet } from "@ethersproject/wallet" 3 | import { EntityApi } from "@vocdoni/voting" 4 | import { IGatewayClient } from "@vocdoni/client" 5 | import { EntityMetadata, EntityMetadataTemplate } from "@vocdoni/data-models" 6 | 7 | export async function ensureEntityMetadata(entityWallet: Wallet, gwPool: IGatewayClient) { 8 | if ((await entityWallet.getBalance()).eq(0)) { 9 | throw new Error("The account has no ether") 10 | } 11 | 12 | const meta = await EntityApi.getMetadata(entityWallet.address, gwPool).catch(() => null) 13 | if (!!meta) return // already present 14 | 15 | console.log("Setting Metadata for entity", entityWallet.address) 16 | 17 | const metadata: EntityMetadata = JSON.parse(JSON.stringify(EntityMetadataTemplate)) 18 | 19 | metadata.name = { default: "Test Organization Name" } 20 | metadata.description = { default: "Description of the test organization goes here" } 21 | metadata.media = { 22 | avatar: "0123", 23 | header: "1234", 24 | logo: "2345" 25 | } 26 | 27 | await EntityApi.setMetadata(entityWallet.address, metadata, entityWallet, gwPool) 28 | console.log("Metadata updated") 29 | 30 | // Read back 31 | const entityMetaPost = await EntityApi.getMetadata(entityWallet.address, gwPool) 32 | 33 | assert(entityMetaPost) 34 | assert.strictEqual(entityMetaPost.name.default, metadata.name.default) 35 | assert.strictEqual(entityMetaPost.description.default, metadata.description.default) 36 | 37 | return entityMetaPost 38 | } 39 | -------------------------------------------------------------------------------- /packages/common/README.md: -------------------------------------------------------------------------------- 1 | # @vocdoni/common 2 | 3 | @vocdoni/common contains shared helpers, models and type definitions for the [dvote-js library](https://github.com/vocdoni/dvote-js/) 4 | 5 | ## Installation 6 | 7 | Use [npm](https://www.npmjs.com/) to install @vocdoni/common. 8 | 9 | ```bash 10 | npm install @vocdoni/common 11 | ``` 12 | 13 | ## Usage 14 | 15 | #### Encoding 16 | 17 | ```ts 18 | import { hexStringToBuffer, uintArrayToHex, bigIntToBuffer, bufferToBigInt } from "@vocdoni/common" 19 | 20 | hexStringToBuffer("AABBCC12") 21 | // returns '' 22 | 23 | const buffer = new Uint8Array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 250, 255]) 24 | uintArrayToHex(buffer) 25 | // returns '0a141e28323c46505a64c8faff' 26 | uintArrayToHex(buffer, true) 27 | // returns '0x0a141e28323c46505a64c8faff' 28 | 29 | const bigint = BigInt("123") 30 | bigIntToBuffer(bigint) 31 | // returns '' 32 | 33 | bufferToBigInt(Buffer.from("64", "hex")) 34 | // returns '100n' 35 | ``` 36 | 37 | #### Random 38 | 39 | ```ts 40 | import { Random } from "@vocdoni/common" 41 | 42 | const bytes = Random.getBytes(8) 43 | // returns '' (random) 44 | 45 | const hex = Random.getHex() 46 | // returns '0x64fa37b4d6139678787efebb8bbddcb104de323ccb980343dbfaceca0a49ac83' (32 byte hash (starting with "0x")) 47 | 48 | const bigint = Random.getBigInt(256n) 49 | // returns '28n' (random) 50 | 51 | const shuffle = Random.shuffle([1, 2, 3, 4]) 52 | // returns '[ 2, 4, 1, 3 ]' (random order) 53 | ``` 54 | 55 | ## Testing 56 | 57 | To execute library tests just run 58 | 59 | ```bash 60 | npm run test 61 | ``` 62 | -------------------------------------------------------------------------------- /packages/common/src/timeout.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @param func The promise-returning function to invoke 3 | * @param timeout Timeout (in seconds) to wait before failing 4 | * @param timeoutMessage (optional) Message to use when throwing a timeout error 5 | */ 6 | export function promiseFuncWithTimeout(func: () => Promise, timeout: number, timeoutMessage?: string): Promise { 7 | if (typeof func != "function") throw new Error("Invalid function") 8 | else if (isNaN(timeout) || timeout < 0) throw new Error("Invalid timeout") 9 | 10 | return new Promise((resolve, reject) => { 11 | setTimeout(() => reject(new Error(timeoutMessage || "Time out")), timeout) 12 | 13 | return func() 14 | .then(result => resolve(result)) 15 | .catch(err => reject(err)) 16 | }) 17 | } 18 | 19 | /** 20 | * @param prom The promise to track 21 | * @param timeout Timeout (in milliseconds) to wait before failing 22 | * @param timeoutMessage (optional) Message to use when throwing a timeout error. By default: `"Time out"` 23 | */ 24 | export function promiseWithTimeout(prom: Promise, timeout: number, timeoutMessage?: string): Promise { 25 | if (!prom || typeof prom.then !== "function" || typeof prom.catch != "function") throw new Error("Invalid promise") 26 | else if (isNaN(timeout) || timeout < 0) throw new Error("Invalid timeout") 27 | 28 | return new Promise((resolve, reject) => { 29 | setTimeout(() => reject(new Error(timeoutMessage || "Time out")), timeout) 30 | 31 | return prom 32 | .then(result => resolve(result)) 33 | .catch(err => reject(err)) 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /packages/client/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @vocdoni/client - Changelog 2 | 3 | ## 1.16.8 4 | 5 | - Accepting multiple bootnodes URLs in discovery process 6 | 7 | ## 1.16.7 8 | 9 | - Fixed `ethers` dependencies 10 | 11 | ## 1.16.6 12 | 13 | - Updated GWs methods: `getEntityCount`, `getProcessCount` and `sendContactMsg` 14 | 15 | ## 1.16.5 16 | 17 | - Added archive support for process listing 18 | 19 | ## 1.16.4 20 | 21 | - Adding support for Polygon mainnet and testnet networks 22 | 23 | ## 1.16.3 24 | 25 | - `GatewayArchive` improvements adding `startDate` and `endDate` 26 | 27 | ## 1.16.2 28 | 29 | - Preventing an empty timeout value to be passed 30 | 31 | ## 1.16.1 32 | 33 | - Adding a check to prevent empty networks from causing an unhandled error 34 | 35 | ## 1.16.0 36 | 37 | - Adding support to fetch the chainId 38 | - Using salted JSON signatures 39 | - Adding `dvoteGateway.getVocdoniChainId()` 40 | - BREAKING: 41 | - `web3Gateway.networkId` is now `web3Gateway.getEthNetworkId()` 42 | - `web3Gateway.chainId` is now `web3Gateway.getEthChainId()` 43 | - `dvoteGateway.getInfo()` is now `dvoteGateway.getVocdoniInfo()` 44 | - `getVocdoniInfo` now returns the `chainId` 45 | 46 | ## 1.15.1 47 | 48 | - Adding support for Avax and Fuji networks 49 | 50 | ## 1.15.0 51 | 52 | - Supporting anonymous voting (registerVoterKey added) 53 | - Allowing to verify content hashed URI's 54 | 55 | ## 1.14.2 56 | 57 | - Adding `Erc20TokensApi.isRegistered`, `Erc20TokensApi.getTokenAddressAt` and 58 | `Erc20TokensApi.getTokenCount` 59 | 60 | ## 1.14.1 61 | 62 | - Improved readme 63 | 64 | ## 1.14.0 65 | 66 | - First version of the package, starting from dvote-js version 1.13.2 67 | -------------------------------------------------------------------------------- /packages/client/README.md: -------------------------------------------------------------------------------- 1 | # @vocdoni/client 2 | 3 | @vocdoni/client contains shared helpers, models and type definitions for the [dvote-js library](https://github.com/vocdoni/dvote-js/) 4 | 5 | ## Installation 6 | 7 | Use [npm](https://www.npmjs.com/) to install @vocdoni/client. 8 | 9 | ```bash 10 | npm install @vocdoni/client 11 | ``` 12 | 13 | ## Usage 14 | 15 | ### Gateway discovery 16 | 17 | ```ts 18 | import { GatewayDiscovery } from "@vocdoni/client" 19 | 20 | const gateways = await GatewayDiscovery.run() 21 | ``` 22 | 23 | You can use any of the gateway objects to send requests to the Voting and Census service providers. 24 | 25 | ### Eth Provider 26 | 27 | ```ts 28 | import { ProviderUtil } from "@vocdoni/client" 29 | 30 | const provider1 = ProviderUtil.fromUri 31 | "https://my-web3-endpoint/rpc", 32 | "mainnet", // "homestead" | "mainnet" | "rinkeby" | "goerli" | "xdai" | "sokol" | "matic" 33 | "prod" // "prod" | "stg" | "dev" 34 | ) 35 | 36 | // In a web browser 37 | const provider2 = ProviderUtil.fromInjectedWeb3() 38 | ``` 39 | 40 | ### File API 41 | 42 | ```ts 43 | import { FileApi } from "@vocdoni/client" 44 | 45 | const buffData = Buffer.from("hello world") 46 | const result1 = await FileApi.add(buffData, "my-file.txt", myWallet, gw) 47 | // result1 => "ipfs://12346789..." 48 | ``` 49 | 50 | ### ENS 51 | 52 | ```ts 53 | import { getEnsTextRecord } from "@vocdoni/client" 54 | 55 | const value = await getEnsTextRecord(gateway, "vnd.vocdoni.meta", { environment: "prod", networkId: "mainnet" }) 56 | // result1 => "ipfs://12346789..." 57 | ``` 58 | 59 | ## Testing 60 | 61 | To execute library tests just run 62 | 63 | ```bash 64 | npm run test 65 | ``` 66 | -------------------------------------------------------------------------------- /packages/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vocdoni/client", 3 | "version": "1.16.8", 4 | "description": "JavaScript/TypeScript client package", 5 | "main": "dist/index", 6 | "types": "dist/index", 7 | "repository": "https://github.com/vocdoni/dvote-js.git", 8 | "author": "Vocdoni ", 9 | "license": "GPL-3.0-or-later", 10 | "private": false, 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "scripts": { 15 | "prepublishOnly": "npm run build", 16 | "clean": "rimraf dist", 17 | "build": "npm run clean && tsc", 18 | "watch": "tsc -w -p .", 19 | "test": "npm run build && mocha -r ts-node/register test/**/*.ts" 20 | }, 21 | "dependencies": { 22 | "@ethersproject/abi": "^5.5.0", 23 | "@ethersproject/abstract-signer": "^5.5.0", 24 | "@ethersproject/bignumber": "^5.5.0", 25 | "@ethersproject/contracts": "^5.5.0", 26 | "@ethersproject/keccak256": "^5.5.0", 27 | "@ethersproject/providers": "^5.5.0", 28 | "@ethersproject/wallet": "^5.5.0", 29 | "@vocdoni/common": "^1.15.3", 30 | "@vocdoni/data-models": "^1.15.3", 31 | "@vocdoni/signing": "^1.16.2", 32 | "axios": "^0.24.0", 33 | "buffer": "^6.0.3", 34 | "universal-parse-url": "^1.0.2" 35 | }, 36 | "devDependencies": { 37 | "@ethersproject/signing-key": "^5.5.0", 38 | "@types/chai": "^4.1.7", 39 | "@types/mocha": "^9.0.0", 40 | "body-parser": "^1.19.0", 41 | "chai": "^4.2.0", 42 | "express": "^4.17.1", 43 | "ganache-core": "^2.13.2", 44 | "mocha": "^9.1.1", 45 | "rimraf": "^3.0.2", 46 | "ts-node": "^10.2.1", 47 | "tslint": "^6.1.3", 48 | "typescript": "^4.4.3" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /example/web/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import Link from 'next/link' 3 | import styles from '../styles/Page.module.css' 4 | 5 | export default function Home() { 6 | return ( 7 |
8 | 9 | Vocdoni 10 | 11 | 12 |
13 |

14 | DVote JS examples 15 |

16 | 17 |

18 | Get started by editing{' '} 19 | pages/* 20 |

21 | 22 | 45 |
46 | 47 | 56 |
57 | ) 58 | } 59 | 60 | -------------------------------------------------------------------------------- /example/web/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. 16 | 17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.tsx`. 18 | 19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /packages/client/test/builders/ens-resolver.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from "@ethersproject/contracts" 2 | import { TestAccount } from "../helpers/all-services" 3 | import { Web3Gateway } from "../../src" 4 | import { ensHashAddress, EnsResolverContractMethods, PublicResolverContractDefinition } from "@vocdoni/contract-wrappers" 5 | 6 | // DEFAULT VALUES 7 | export const DEFAULT_NAME = "Entity Name" 8 | 9 | const nullAddress = "0x0000000000000000000000000000000000000000" 10 | 11 | // BUILDER 12 | export default class EntityResolverBuilder { 13 | accounts: TestAccount[] 14 | entityAccount: TestAccount 15 | 16 | name: string = DEFAULT_NAME 17 | 18 | constructor(devAccounts: TestAccount[]) { 19 | this.accounts = devAccounts 20 | this.entityAccount = this.accounts[1] 21 | } 22 | 23 | async build(): Promise { 24 | const gw = new Web3Gateway(this.entityAccount.provider) 25 | const newInstnace = await gw.deploy(PublicResolverContractDefinition.abi, PublicResolverContractDefinition.bytecode, { wallet: this.entityAccount.wallet }, [nullAddress]) 26 | 27 | const contractInstance = await gw.getEnsPublicResolverInstance(this.entityAccount.wallet, newInstnace.address) 28 | const entityNode = ensHashAddress(this.entityAccount.address) 29 | 30 | await contractInstance.setText(entityNode, "key-name", this.name) 31 | return contractInstance 32 | } 33 | 34 | // custom modifiers 35 | withEntityAccount(entityAccount: TestAccount) { 36 | this.entityAccount = entityAccount 37 | return this 38 | } 39 | withName(name: string) { 40 | this.name = name 41 | return this 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/census/src/onchain.ts: -------------------------------------------------------------------------------- 1 | import { bufferLeToBigInt } from "@vocdoni/common" 2 | 3 | export namespace CensusOnChain { 4 | const HASH_FUNCTION_LEN = 32 5 | 6 | export function unpackSiblings(siblings: Uint8Array): bigint[] { 7 | if (siblings.length < 4) throw new Error("Invalid siblings buffer") 8 | 9 | const fullLen = Number(bufferLeToBigInt(siblings.slice(0, 2))) 10 | if (siblings.length != fullLen) throw new Error("The expected length doesn't match the siblings size") 11 | 12 | const result: bigint[] = [] 13 | 14 | const bitmapBytesLength = Number(bufferLeToBigInt(siblings.slice(2, 4))) 15 | const bitmapBytes = siblings.slice(4, 4 + bitmapBytesLength) 16 | const bitmap = bytesToBitmap(bitmapBytes) 17 | 18 | const siblingsBytes = siblings.slice(4 + bitmapBytesLength) 19 | const emptySibling = BigInt("0") 20 | 21 | let siblingIdx = 0 22 | for (let i = 0; i < bitmap.length; i++) { 23 | if (siblingIdx >= siblingsBytes.length) break 24 | else if (bitmap[i]) { 25 | const v = siblingsBytes.slice(siblingIdx, siblingIdx + HASH_FUNCTION_LEN) 26 | result.push(bufferLeToBigInt(v)) 27 | siblingIdx += HASH_FUNCTION_LEN 28 | } 29 | else { 30 | result.push(emptySibling) 31 | } 32 | } 33 | return result 34 | } 35 | 36 | function bytesToBitmap(bytes: Uint8Array): boolean[] { 37 | const result: boolean[] = [] 38 | for (let i = 0; i < bytes.length; i++) { 39 | for (let j = 0; j < 8; j++) { 40 | result.push(!!(bytes[i] & (1 << j))) 41 | } 42 | } 43 | return result 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/census/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vocdoni/census", 3 | "version": "1.16.0", 4 | "description": "JavaScript/TypeScript census package", 5 | "main": "dist/index", 6 | "types": "dist/index", 7 | "repository": "https://github.com/vocdoni/dvote-js.git", 8 | "author": "Vocdoni ", 9 | "license": "GPL-3.0-or-later", 10 | "private": false, 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "scripts": { 15 | "prepublishOnly": "npm run build", 16 | "clean": "rimraf dist", 17 | "build": "npm run clean && tsc", 18 | "watch": "tsc -w -p .", 19 | "test": "npm run build && mocha -r ts-node/register test/**/*.ts", 20 | "postinstall": "node scripts/wipe-snarkjs-package-module" 21 | }, 22 | "dependencies": { 23 | "@ethersproject/abstract-signer": "^5.5.0", 24 | "@ethersproject/bignumber": "^5.5.0", 25 | "@ethersproject/bytes": "^5.5.0", 26 | "@ethersproject/contracts": "^5.5.0", 27 | "@ethersproject/providers": "^5.5.0", 28 | "@ethersproject/wallet": "^5.5.0", 29 | "@vocdoni/client": "^1.16.6", 30 | "@vocdoni/common": "^1.15.1", 31 | "@vocdoni/contract-wrappers": "^1.15.0", 32 | "@vocdoni/data-models": "^1.15.3", 33 | "@vocdoni/hashing": "^1.15.0", 34 | "@vocdoni/signing": "^1.16.2", 35 | "@vocdoni/storage-proofs-eth": "^0.4.1", 36 | "axios": "^0.21.1", 37 | "blindsecp256k1": "^0.0.6", 38 | "buffer": "^6.0.3", 39 | "ethers": "^5.4.1", 40 | "snarkjs": "^0.4.10", 41 | "universal-parse-url": "^1.0.2" 42 | }, 43 | "devDependencies": { 44 | "@types/chai": "^4.1.7", 45 | "@types/mocha": "^9.0.0", 46 | "chai": "^4.2.0", 47 | "mocha": "^9.1.1", 48 | "rimraf": "^3.0.2", 49 | "ts-node": "^10.2.1", 50 | "tslint": "^6.1.3", 51 | "typescript": "^4.4.3" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/wallets/src/wallet-util.ts: -------------------------------------------------------------------------------- 1 | import { Wallet } from "@ethersproject/wallet" 2 | import { Provider } from "@ethersproject/providers" 3 | import { isStrongPassphrase, digestSeededPassphrase } from "./util/helpers" 4 | 5 | export class WalletUtil { 6 | /** 7 | * Returns a standalone Ethers.js wallet and connects it to the given provider if one is set. It uses the given passphrase and 8 | * hexSeed to compute a deterministic private key along with some seed to salt the passphrase. Use `Random.getHex` 9 | * to generate a secure random seed. 10 | * @param passphrase 11 | * @param hexSeed 12 | * @param provider (optional) 13 | */ 14 | static fromSeededPassphrase(passphrase: string, hexSeed: string, provider?: Provider): Wallet { 15 | if (typeof passphrase != "string") throw new Error("The passphrase must be a string") 16 | else if (!isStrongPassphrase(passphrase)) throw new Error("The passphrase is not strong enough") 17 | else if (typeof hexSeed != "string") throw new Error("The hexSeed must be a hex string: use Random.getHex() to create a new one") 18 | 19 | const privateKey = digestSeededPassphrase(passphrase, hexSeed) 20 | 21 | return provider ? 22 | new Wallet(privateKey).connect(provider) : 23 | new Wallet(privateKey) 24 | } 25 | 26 | /** 27 | * Returns a standalone Ethers.js wallet and connects it to the given provider if one is set 28 | * @param mnemonic 29 | * @param mnemonicPath (optional) 30 | * @param provider (optional) 31 | */ 32 | static fromMnemonic(mnemonic: string, mnemonicPath: string = "m/44'/60'/0'/0/0", provider?: Provider) { 33 | return provider ? 34 | Wallet.fromMnemonic(mnemonic, mnemonicPath).connect(provider) : 35 | Wallet.fromMnemonic(mnemonic, mnemonicPath) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /example/web/pages/metadata.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { useState } from 'react' 3 | import { VotingApi, EntityApi, ProcessDetails } from "@vocdoni/voting" 4 | import { EntityMetadata } from "@vocdoni/data-models" 5 | import { getClient } from "../lib/net" 6 | 7 | const ENTITY_ID = "0x9b2dd5db2b5ba506453a832fffa886e10ec9ac71" 8 | const PROCESS_IDS = [ 9 | "0x0ff269fddb899671d1f54c81a906f6becd1a3770781c04f7b4f8fcdd96226af8", 10 | "0x8e4948bd579628b49d865705f07ea5d100bbf99d254c22649dea850feef62abe" 11 | ] 12 | 13 | const Page = () => { 14 | const [loading, setLoading] = useState(false) 15 | const [entity, setEntity] = useState(null as any) 16 | const [processDetails, setProcessDetails] = useState(null as any) 17 | 18 | const loadMetadata = () => { 19 | setLoading(true) 20 | getClient() 21 | .then(gwPool => { 22 | return Promise.all([ 23 | EntityApi.getMetadata(ENTITY_ID, gwPool), 24 | VotingApi.getProcess(PROCESS_IDS[0], gwPool) 25 | ]) 26 | }) 27 | .then(results => { 28 | setLoading(false) 29 | 30 | setEntity(results[0]) 31 | setProcessDetails(results[1]) 32 | }) 33 | .catch(err => { 34 | setLoading(false) 35 | 36 | alert("Could not connect to the network nodes: " + err.message) 37 | }) 38 | } 39 | 40 | return
41 |

Metadata and details

42 |

Status: ({loading ? "loading" : "ready"})

43 |

Entity

44 |

{ENTITY_ID}

45 |
{JSON.stringify(entity, null, 2)}
46 |

Process

47 |

{PROCESS_IDS[0]}

48 |
{JSON.stringify(processDetails, null, 2)}
49 | {!loading ?

: null} 50 |
51 | } 52 | 53 | export default Page 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dvote-js", 3 | "version": "1.16.2", 4 | "description": "JavaScript/TypeScript library to interact with Vocdoni voting processes", 5 | "main": "dist/index", 6 | "types": "dist/index", 7 | "repository": "https://github.com/vocdoni/dvote-js.git", 8 | "author": "Vocdoni ", 9 | "license": "GPL-3.0-or-later", 10 | "private": false, 11 | "scripts": { 12 | "prepublishOnly": "npm run build", 13 | "clean": "rimraf dist", 14 | "prebuild": "git submodule init && git submodule update", 15 | "build": "npm install && npm run build --ws && tsc -b", 16 | "watch": "tsc -w -p .", 17 | "test": "npm run build && npm test --ws" 18 | }, 19 | "workspaces": { 20 | "packages": [ 21 | "packages/common", 22 | "packages/contract-wrappers", 23 | "packages/encryption", 24 | "packages/signing", 25 | "packages/hashing", 26 | "packages/data-models", 27 | "packages/client", 28 | "packages/census", 29 | "packages/voting", 30 | "packages/wallets" 31 | ], 32 | "nohoist": [] 33 | }, 34 | "dependencies": { 35 | "@vocdoni/census": "^1.16.0", 36 | "@vocdoni/client": "^1.16.7", 37 | "@vocdoni/common": "^1.15.3", 38 | "@vocdoni/contract-wrappers": "^1.15.0", 39 | "@vocdoni/data-models": "^1.15.3", 40 | "@vocdoni/encryption": "^1.14.1", 41 | "@vocdoni/hashing": "^1.15.0", 42 | "@vocdoni/signing": "^1.16.2", 43 | "@vocdoni/voting": "^1.16.1", 44 | "@vocdoni/wallets": "^1.16.0" 45 | }, 46 | "devDependencies": { 47 | "@types/chai": "^4.1.7", 48 | "@types/mocha": "^9.0.0", 49 | "@types/node": "^16.11.11", 50 | "chai": "^4.2.0", 51 | "dotenv": "^10.0.0", 52 | "mocha": "^9.1.1", 53 | "rimraf": "^3.0.2", 54 | "ts-node": "^10.2.1", 55 | "tslint": "^6.1.3", 56 | "typescript": "^4.4.3" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/common/src/encoding.ts: -------------------------------------------------------------------------------- 1 | import { Buffer } from "buffer/" 2 | 3 | export function hexStringToBuffer(hexString: string): Buffer { 4 | if (!/^(0x)?[0-9a-fA-F]+$/.test(hexString)) throw new Error("Invalid hex string") 5 | else if (hexString.length % 2 != 0) throw new Error("The hex string contains an odd length") 6 | 7 | return Buffer.from(strip0x(hexString), "hex") 8 | } 9 | export function uintArrayToHex(buff: Uint8Array, prepend0x?: boolean): string { 10 | const bytes: string[] = [] 11 | for (let byte of buff) { 12 | if (byte >= 16) bytes.push(byte.toString(16)) 13 | else bytes.push("0" + byte.toString(16)) 14 | } 15 | if (prepend0x) return "0x" + bytes.join("") 16 | return bytes.join("") 17 | } 18 | 19 | /** Encodes the given big integer as a 32 byte big endian buffer */ 20 | export function bigIntToBuffer(number: bigint): Buffer { 21 | let hexNumber = number.toString(16) 22 | while (hexNumber.length < 64) hexNumber = "0" + hexNumber 23 | return Buffer.from(hexNumber, "hex") 24 | } 25 | 26 | /** Encodes the given big integer as a 32 byte little endian buffer */ 27 | export function bigIntToLeBuffer(number: bigint): Buffer { 28 | return bigIntToBuffer(number).reverse() 29 | } 30 | 31 | export function bufferToBigInt(bytes: Buffer | Uint8Array): bigint { 32 | // Ensure that it is a buffer 33 | bytes = Buffer.from(bytes) 34 | return BigInt(ensure0x(bytes.toString("hex"))) 35 | } 36 | 37 | export function bufferLeToBigInt(bytes: Buffer | Uint8Array): bigint { 38 | bytes = Buffer.from(bytes) 39 | return bufferToBigInt(bytes.reverse()) 40 | } 41 | 42 | export function ensure0x(value: string): string { 43 | return value.startsWith("0x") ? value : "0x" + value 44 | } 45 | 46 | export function strip0x(value: string): string { 47 | return value.startsWith("0x") ? value.substring(2) : value 48 | } 49 | -------------------------------------------------------------------------------- /packages/contract-wrappers/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from "@ethersproject/contracts" 2 | 3 | import { 4 | EnsResolverContractMethods, 5 | GenesisContractMethods, 6 | NamespacesContractMethods, 7 | ProcessesContractMethods, 8 | ResultsContractMethods, 9 | Erc20StorageProofContractMethods, 10 | } from "dvote-solidity" 11 | 12 | export { 13 | EnsResolver as PublicResolverContractDefinition, 14 | Genesis as GenesisContractDefinition, 15 | Namespaces as NamespacesContractDefinition, 16 | Processes as ProcessesContractDefinition, 17 | Results as ResultsContractDefinition, 18 | ERC20StorageProofs as Erc20StorageProofContractDefinition, 19 | EnsResolverContractMethods, 20 | GenesisContractMethods, 21 | NamespacesContractMethods, 22 | ProcessesContractMethods, 23 | ResultsContractMethods, 24 | Erc20StorageProofContractMethods, 25 | ensHashAddress, 26 | 27 | // Interfaces 28 | IMethodOverrides, 29 | IProcessCreateParams, 30 | IProcessMode, 31 | IProcessEnvelopeType, 32 | IProcessCensusOrigin, 33 | IProcessResults, 34 | IProcessStatus, 35 | 36 | // Wrappers 37 | ProcessMode, 38 | ProcessEnvelopeType, 39 | ProcessCensusOrigin, 40 | ProcessStatus, 41 | ProcessResults, 42 | ProcessContractParameters, 43 | } from "dvote-solidity" 44 | 45 | export interface IEnsPublicResolverContract extends Contract, EnsResolverContractMethods { } 46 | export interface IGenesisContract extends Contract, GenesisContractMethods { } 47 | export interface INamespacesContract extends Contract, NamespacesContractMethods { } 48 | export interface IProcessesContract extends Contract, ProcessesContractMethods { } 49 | export interface IResultsContract extends Contract, ResultsContractMethods { } 50 | export interface ITokenStorageProofContract extends Contract, Erc20StorageProofContractMethods { } 51 | -------------------------------------------------------------------------------- /packages/client/src/apis/tokens.ts: -------------------------------------------------------------------------------- 1 | import { allSettled } from "@vocdoni/common" 2 | import { IGatewayClient, IGatewayWeb3Client } from "../interfaces" 3 | 4 | export namespace Erc20TokensApi { 5 | /** 6 | * Retrieve the addresses of all the ERC20 tokens registered on the contract. 7 | * @param gateway A gateway client instance 8 | */ 9 | export async function getTokenList(gateway: IGatewayClient): Promise { 10 | return getTokenCount(gateway) 11 | .then(count => { 12 | const indexes = new Array(count).fill(0).map((_, i) => i) 13 | 14 | // TODO Promise.allSettled is the correct one, should be used when target = ES2020 is fixed 15 | return allSettled(indexes.map(idx => getTokenAddressAt(idx, gateway))) 16 | }).then(results => { 17 | return results 18 | .filter(item => item.status === "fulfilled") 19 | .map((item: { status: string, value: any }) => item.value) 20 | }) 21 | } 22 | 23 | export function isRegistered(tokenAddress: string, gw: IGatewayWeb3Client, customContractAddress?: string) { 24 | return gw.getTokenStorageProofInstance(null, customContractAddress) 25 | .then((contractInstance) => contractInstance.isRegistered(tokenAddress)) 26 | } 27 | 28 | export function getTokenAddressAt(index: number, gw: IGatewayWeb3Client, customContractAddress?: string): Promise { 29 | return gw.getTokenStorageProofInstance(null, customContractAddress) 30 | .then((contractInstance) => contractInstance.tokenAddresses(index)) 31 | } 32 | 33 | export function getTokenCount(gw: IGatewayWeb3Client, customContractAddress?: string): Promise { 34 | return gw.getTokenStorageProofInstance(null, customContractAddress) 35 | .then((contractInstance) => contractInstance.tokenCount()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /example/signed-blind/util.ts: -------------------------------------------------------------------------------- 1 | import { IGatewayClient } from "@vocdoni/client" 2 | import { VochainProcessStatus } from "@vocdoni/data-models" 3 | import { VochainWaiter, VotingApi } from "@vocdoni/voting" 4 | import * as assert from "assert" 5 | import { getConfig } from "./config" 6 | 7 | const config = getConfig() 8 | 9 | export async function waitUntilPresent(processId: string, gwPool: IGatewayClient) { 10 | assert(gwPool) 11 | assert(processId) 12 | 13 | let attempts = 6 14 | while (attempts >= 0) { 15 | console.log("Waiting for process", processId, "to be created") 16 | await VochainWaiter.wait(1, gwPool) 17 | 18 | const state = await VotingApi.getProcessState(processId, gwPool).catch(() => null) 19 | if (state?.entityId) break 20 | 21 | attempts-- 22 | } 23 | if (attempts < 0) throw new Error("The process still does not exist on the Vochain") 24 | } 25 | 26 | export async function waitUntilStarted(processId: string, startBlock: number, gwPool: IGatewayClient) { 27 | assert(gwPool) 28 | assert(processId) 29 | 30 | // start block 31 | await VochainWaiter.waitUntil(startBlock, gwPool, { verbose: true }) 32 | 33 | console.log("Waiting for the process to be ready") 34 | const state = await VotingApi.getProcessState(processId, gwPool) 35 | 36 | assert.strictEqual(state.status, VochainProcessStatus.READY, "Should be ready but is not") 37 | } 38 | 39 | export function getChoicesForVoter(questionCount: number, voterIdx: number) { 40 | const indexes = new Array(questionCount).fill(0).map((_, i) => i) 41 | 42 | return indexes.map((_, idx) => { 43 | switch (config.votesPattern) { 44 | case "all-0": return 0 45 | case "all-1": return 1 46 | case "all-2": return 2 47 | case "all-even": return (voterIdx % 2 == 0) ? 0 : 1 48 | case "incremental": return idx 49 | default: return 0 50 | } 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /example/signed-erc20/util.ts: -------------------------------------------------------------------------------- 1 | import { IGatewayClient } from "@vocdoni/client" 2 | import { VochainProcessStatus } from "@vocdoni/data-models" 3 | import { VochainWaiter, VotingApi } from "@vocdoni/voting" 4 | import * as assert from "assert" 5 | import { getConfig } from "./config" 6 | 7 | const config = getConfig() 8 | 9 | export async function waitUntilPresent(processId: string, gwPool: IGatewayClient) { 10 | assert(gwPool) 11 | assert(processId) 12 | 13 | let attempts = 6 14 | while (attempts >= 0) { 15 | console.log("Waiting for process", processId, "to be created") 16 | await VochainWaiter.wait(1, gwPool) 17 | 18 | const state = await VotingApi.getProcessState(processId, gwPool).catch(() => null) 19 | if (state?.entityId) break 20 | 21 | attempts-- 22 | } 23 | if (attempts < 0) throw new Error("The process still does not exist on the Vochain") 24 | } 25 | 26 | export async function waitUntilStarted(processId: string, startBlock: number, gwPool: IGatewayClient) { 27 | assert(gwPool) 28 | assert(processId) 29 | 30 | // start block 31 | await VochainWaiter.waitUntil(startBlock, gwPool, { verbose: true }) 32 | 33 | console.log("Waiting for the process to be ready") 34 | const state = await VotingApi.getProcessState(processId, gwPool) 35 | 36 | assert.strictEqual(state.status, VochainProcessStatus.READY, "Should be ready but is not") 37 | } 38 | 39 | export function getChoicesForVoter(questionCount: number, voterIdx: number) { 40 | const indexes = new Array(questionCount).fill(0).map((_, i) => i) 41 | 42 | return indexes.map((_, idx) => { 43 | switch (config.votesPattern) { 44 | case "all-0": return 0 45 | case "all-1": return 1 46 | case "all-2": return 2 47 | case "all-even": return (voterIdx % 2 == 0) ? 0 : 1 48 | case "incremental": return idx 49 | default: return 0 50 | } 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /example/signed-off-chain/util.ts: -------------------------------------------------------------------------------- 1 | import { IGatewayClient } from "@vocdoni/client" 2 | import { VochainProcessStatus } from "@vocdoni/data-models" 3 | import { VochainWaiter, VotingApi } from "@vocdoni/voting" 4 | import * as assert from "assert" 5 | import { getConfig } from "./config" 6 | 7 | const config = getConfig() 8 | 9 | export async function waitUntilPresent(processId: string, gwPool: IGatewayClient) { 10 | assert(gwPool) 11 | assert(processId) 12 | 13 | let attempts = 6 14 | while (attempts >= 0) { 15 | console.log("Waiting for process", processId, "to be created") 16 | await VochainWaiter.wait(1, gwPool) 17 | 18 | const state = await VotingApi.getProcessState(processId, gwPool).catch(() => null) 19 | if (state?.entityId) break 20 | 21 | attempts-- 22 | } 23 | if (attempts < 0) throw new Error("The process still does not exist on the Vochain") 24 | } 25 | 26 | export async function waitUntilStarted(processId: string, startBlock: number, gwPool: IGatewayClient) { 27 | assert(gwPool) 28 | assert(processId) 29 | 30 | // start block 31 | await VochainWaiter.waitUntil(startBlock, gwPool, { verbose: true }) 32 | 33 | console.log("Waiting for the process to be ready") 34 | const state = await VotingApi.getProcessState(processId, gwPool) 35 | 36 | assert.strictEqual(state.status, VochainProcessStatus.READY, "Should be ready but is not") 37 | } 38 | 39 | export function getChoicesForVoter(questionCount: number, voterIdx: number) { 40 | const indexes = new Array(questionCount).fill(0).map((_, i) => i) 41 | 42 | return indexes.map((_, idx) => { 43 | switch (config.votesPattern) { 44 | case "all-0": return 0 45 | case "all-1": return 1 46 | case "all-2": return 2 47 | case "all-even": return (voterIdx % 2 == 0) ? 0 : 1 48 | case "incremental": return idx 49 | default: return 0 50 | } 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /example/anonymous-off-chain/util.ts: -------------------------------------------------------------------------------- 1 | import { IGatewayClient } from "@vocdoni/client" 2 | import { VochainProcessStatus } from "@vocdoni/data-models" 3 | import { VochainWaiter, VotingApi } from "@vocdoni/voting" 4 | import * as assert from "assert" 5 | import { getConfig } from "./config" 6 | 7 | const config = getConfig() 8 | 9 | export async function waitUntilPresent(processId: string, gwPool: IGatewayClient) { 10 | assert(gwPool) 11 | assert(processId) 12 | 13 | let attempts = 6 14 | while (attempts >= 0) { 15 | console.log("Waiting for process", processId, "to be created") 16 | await VochainWaiter.wait(1, gwPool) 17 | 18 | const state = await VotingApi.getProcessState(processId, gwPool).catch(() => null) 19 | if (state?.entityId) break 20 | 21 | attempts-- 22 | } 23 | if (attempts < 0) throw new Error("The process still does not exist on the Vochain") 24 | } 25 | 26 | export async function waitUntilStarted(processId: string, startBlock: number, gwPool: IGatewayClient) { 27 | assert(gwPool) 28 | assert(processId) 29 | 30 | // start block 31 | await VochainWaiter.waitUntil(startBlock, gwPool, { verbose: true }) 32 | 33 | console.log("Waiting for the process to be ready") 34 | const state = await VotingApi.getProcessState(processId, gwPool) 35 | 36 | assert.strictEqual(state.status, VochainProcessStatus.READY, "Should be ready but is not") 37 | } 38 | 39 | export function getChoicesForVoter(questionCount: number, voterIdx: number) { 40 | const indexes = new Array(questionCount).fill(0).map((_, i) => i) 41 | 42 | return indexes.map((_, idx) => { 43 | switch (config.votesPattern) { 44 | case "all-0": return 0 45 | case "all-1": return 1 46 | case "all-2": return 2 47 | case "all-even": return (voterIdx % 2 == 0) ? 0 : 1 48 | case "incremental": return idx 49 | default: return 0 50 | } 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /example/signed-erc20-signal/util.ts: -------------------------------------------------------------------------------- 1 | import { IGatewayClient } from "@vocdoni/client" 2 | import { VochainProcessStatus } from "@vocdoni/data-models" 3 | import { VochainWaiter, VotingApi } from "@vocdoni/voting" 4 | import * as assert from "assert" 5 | import { getConfig } from "./config" 6 | 7 | const config = getConfig() 8 | 9 | export async function waitUntilPresent(processId: string, gwPool: IGatewayClient) { 10 | assert(gwPool) 11 | assert(processId) 12 | 13 | let attempts = 6 14 | while (attempts >= 0) { 15 | console.log("Waiting for process", processId, "to be created") 16 | await VochainWaiter.wait(1, gwPool) 17 | 18 | const state = await VotingApi.getProcessState(processId, gwPool).catch(() => null) 19 | if (state?.entityId) break 20 | 21 | attempts-- 22 | } 23 | if (attempts < 0) throw new Error("The process still does not exist on the Vochain") 24 | } 25 | 26 | export async function waitUntilStarted(processId: string, startBlock: number, gwPool: IGatewayClient) { 27 | assert(gwPool) 28 | assert(processId) 29 | 30 | // start block 31 | await VochainWaiter.waitUntil(startBlock, gwPool, { verbose: true }) 32 | 33 | console.log("Waiting for the process to be ready") 34 | const state = await VotingApi.getProcessState(processId, gwPool) 35 | 36 | assert.strictEqual(state.status, VochainProcessStatus.READY, "Should be ready but is not") 37 | } 38 | 39 | export function getChoicesForVoter(questionCount: number, voterIdx: number) { 40 | const indexes = new Array(questionCount).fill(0).map((_, i) => i) 41 | 42 | return indexes.map((_, idx) => { 43 | switch (config.votesPattern) { 44 | case "all-0": return 0 45 | case "all-1": return 1 46 | case "all-2": return 2 47 | case "all-even": return (voterIdx % 2 == 0) ? 0 : 1 48 | case "incremental": return idx 49 | default: return 0 50 | } 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /packages/contract-wrappers/test/unit/ens-hash.ts: -------------------------------------------------------------------------------- 1 | import "mocha" // using @types/mocha 2 | import { expect } from "chai" 3 | import { addCompletionHooks } from "../mocha-hooks" 4 | 5 | import { ensHashAddress } from "../../src" 6 | 7 | addCompletionHooks() 8 | 9 | describe("Entity Resolver", () => { 10 | it("Should allow to hash the entity's address", async () => { 11 | const data = [ 12 | { address: "0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1", node: "0xe7fb8f3e702fd22bf02391cc16c6b4bc465084468f1627747e6e21e2005f880e" }, 13 | { address: "0xffcf8fdee72ac11b5c542428b35eef5769c409f0", node: "0x92eba8bf099a58b316e6c8743101585f4a71b45d87c571440553b6e74671ac5a" }, 14 | { address: "0x22d491bde2303f2f43325b2108d26f1eaba1e32b", node: "0x9f225659836e74be7309b140ad0fee340ce09db633a8d42b85540955c987123b" }, 15 | { address: "0xe11ba2b4d45eaed5996cd0823791e0c93114882d", node: "0xd6604a251934bff7fe961c233a6f8dbd5fb55e6e98cf893237c9608e746e2807" }, 16 | { address: "0xd03ea8624c8c5987235048901fb614fdca89b117", node: "0xa6e02fa9ce046b7970daab05320d7355d28f9e9bc7889121b0d9d90b441f360c" }, 17 | { address: "0x95ced938f7991cd0dfcb48f0a06a40fa1af46ebc", node: "0xee97003d4805070a87a8bd486f4894fbfce48844710a9b46df867f7f64f9a174" }, 18 | { address: "0x3e5e9111ae8eb78fe1cc3bb8915d5d461f3ef9a9", node: "0xaca9367e5113a27f3873ddf78ada8a6af283849bb14cc313cadf63ae03ea52b3" }, 19 | { address: "0x28a8746e75304c0780e011bed21c72cd78cd535e", node: "0xa17b99060235fa80368a12707574ab6381d0fc9aa7cb3a6a116d0f04564980fe" }, 20 | { address: "0xaca94ef8bd5ffee41947b4585a84bda5a3d3da6e", node: "0xb1ec3484f6bdfce3b18264a4e83a5e99fc43641f8e15a3079a9e6872b5d6cace" }, 21 | { address: "0x1df62f291b2e969fb0849d99d9ce41e2f137006e", node: "0x0c3a882b5cad48337e5a74659c514c6e0d5490bdc7ec8898d7d2a924da96c720" }, 22 | ] 23 | 24 | for (let item of data) { 25 | expect(ensHashAddress(item.address)).to.equal(item.node) 26 | } 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /packages/common/src/promise.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This function emulates the Promise.allSettled() 3 | * 4 | * @param proms 5 | */ 6 | export function allSettled(proms: Promise[]): Promise<({ value: any, status: string } | { reason: Error, status: string })[]> { 7 | return Promise.all(proms.map(reflect)) 8 | } 9 | 10 | export function reflect(prom: Promise) { 11 | return prom 12 | .then((value: T) => ({ value, status: "fulfilled" })) 13 | .catch((reason: T) => ({ reason, status: "rejected" })) 14 | } 15 | 16 | export function promiseAny(values: Iterable>): Promise { 17 | return new Promise((resolve: (value: T) => void, reject: (reason?: any) => void): void => { 18 | let hasResolved: boolean = false; 19 | const promiseLikes: (T | PromiseLike)[] = []; 20 | let iterableCount: number = 0; 21 | const rejectionReasons: any[] = []; 22 | 23 | function resolveOnce(value: T): void { 24 | if (!hasResolved) { 25 | hasResolved = true; 26 | resolve(value); 27 | } 28 | } 29 | 30 | function rejectionCheck(reason?: any): void { 31 | rejectionReasons.push(reason); 32 | if (rejectionReasons.length >= iterableCount) reject(rejectionReasons); 33 | } 34 | 35 | for (const value of values) { 36 | iterableCount++; 37 | promiseLikes.push(value); 38 | } 39 | 40 | for (const promiseLike of promiseLikes) { 41 | if ((promiseLike as PromiseLike)?.then !== undefined || 42 | (promiseLike as Promise)?.catch !== undefined) { 43 | (promiseLike as Promise) 44 | ?.then((result: T): void => resolveOnce(result)) 45 | ?.catch((error?: any): void => undefined); 46 | (promiseLike as Promise)?.catch((reason?: any): void => rejectionCheck(reason)); 47 | } else resolveOnce(promiseLike as T); 48 | } 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /packages/client/src/wrappers/gateway-info.ts: -------------------------------------------------------------------------------- 1 | import { VocdoniEnvironment } from "@vocdoni/common" 2 | import { BackendApiName, GatewayApiName } from "../apis/definition" 3 | 4 | // const uriPattern = /^([a-z][a-z0-9+.-]+):(\/\/([^@]+@)?([a-z0-9.\-_~]+)(:\d+)?)?((?:[a-z0-9-._~]|%[a-f0-9]|[!$&'()*+,;=:@])+(?:\/(?:[a-z0-9-._~]|%[a-f0-9]|[!$&'()*+,;=:@])*)*|(?:\/(?:[a-z0-9-._~]|%[a-f0-9]|[!$&'()*+,;=:@])+)*)?(\?(?:[a-z0-9-._~]|%[a-f0-9]|[!$&'()*+,;=:@]|[/?])+)?(\#(?:[a-z0-9-._~]|%[a-f0-9]|[!$&'()*+,;=:@]|[/?])+)?$/i 5 | 6 | export class GatewayInfo { 7 | private dvoteUri: string 8 | private supportedApiList: GatewayApiName[] | BackendApiName[] 9 | private web3Uri: string 10 | private pubKey: string 11 | private _environment: VocdoniEnvironment 12 | 13 | public get dvote() { return this.dvoteUri } 14 | public get supportedApis() { return this.supportedApiList } 15 | public get web3() { return this.web3Uri } 16 | public get publicKey() { return this.pubKey } 17 | public get environment() { return this._environment } 18 | 19 | /** Bundles the given coordinates into an object containing the details of a Gateway */ 20 | constructor(dvoteUri: string = null, supportedApis: GatewayApiName[] | BackendApiName[] = [], web3Uri: string, pubKey?: string, environment?: VocdoniEnvironment) { 21 | if (!dvoteUri && !web3Uri) throw new Error("DVote URI or Web3 URI is required") 22 | 23 | if (dvoteUri) { 24 | if (typeof dvoteUri != "string") throw new Error("Invalid Gateway URI") 25 | else if (!Array.isArray(supportedApis) || !supportedApis.length) throw new Error("Supported API's should have at least one API") 26 | } 27 | if (web3Uri) { 28 | if (typeof web3Uri != "string") throw new Error("Invalid Web3 URI") 29 | } 30 | 31 | this.dvoteUri = dvoteUri || null 32 | this.supportedApiList = supportedApis || [] 33 | this.web3Uri = web3Uri || null 34 | this.pubKey = pubKey || "" 35 | this._environment = environment || "prod" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/data-models/src/voting-params.ts: -------------------------------------------------------------------------------- 1 | import { MultiLanguage } from "@vocdoni/common" 2 | import { IProcessCreateParams } from "@vocdoni/contract-wrappers" 3 | import { BigNumber } from "@ethersproject/bignumber" 4 | import { ProofCA_Type } from "./protobuf" 5 | import { ProcessMetadata } from "./voting-meta" 6 | 7 | export type INewProcessParams = Omit, "questionCount"> & { metadata: ProcessMetadata } 8 | export type INewProcessErc20Params = Omit, "censusOrigin"> 9 | 10 | // Envelope and proofs 11 | 12 | export type IProofArbo = { siblings: string, weight?: bigint } 13 | export type IProofCA = { type: number, voterAddress: string, signature: string, weight?: bigint } 14 | export type IProofEVM = { key: string, proof: string[], value: string, weight?: bigint } 15 | 16 | type IProofCaSignatureType = { 17 | UNKNOWN: number, 18 | ECDSA: number, 19 | ECDSA_PIDSALTED: number, 20 | ECDSA_BLIND: number, 21 | ECDSA_BLIND_PIDSALTED: number 22 | } 23 | const ProofCaSignatureTypes = ProofCA_Type as IProofCaSignatureType 24 | export { ProofCaSignatureTypes } 25 | 26 | // Anonymous voting 27 | 28 | export type ZkProof = { 29 | proof: { 30 | a: string[] 31 | b: string[][] 32 | c: string[] 33 | protocol: string 34 | // curve: proof.curve || "bn128", 35 | }, 36 | publicSignals: string[] 37 | } 38 | 39 | // Single choice results 40 | export interface ProcessResultsSingleChoice { 41 | totalVotes: number, 42 | questions: SingleChoiceQuestionResults[], 43 | } 44 | 45 | export interface SingleChoiceQuestionResults { 46 | title: MultiLanguage, 47 | voteResults: Array<{ 48 | title: MultiLanguage, 49 | votes: BigNumber, 50 | }>, 51 | } 52 | 53 | // Multiple choice results 54 | export interface ProcessResultsSingleQuestion { 55 | totalVotes: number, 56 | title: MultiLanguage, 57 | options: Array<{ 58 | title: MultiLanguage, 59 | votes: BigNumber 60 | }> 61 | } 62 | -------------------------------------------------------------------------------- /packages/common/test/unit/normalization.ts: -------------------------------------------------------------------------------- 1 | import "mocha" // using @types/mocha 2 | import { expect } from "chai" 3 | import { addCompletionHooks } from "../mocha-hooks" 4 | 5 | import { normalizeText } from "../../src" 6 | 7 | addCompletionHooks() 8 | 9 | describe("Text normalization", () => { 10 | it("Normalized texts should match latin characters", () => { 11 | const items = [ 12 | { original: "àèìòùáéíóúâêîôûäëïöü", normalized: "aeiouaeiouaeiouaeiou" }, 13 | { original: "ÀÈÌÒÙÁÉÍÓÚÂÊÎÔÛÄËÏÖÜ", normalized: "aeiouaeiouaeiouaeiou" }, 14 | { original: "çÇñÑ", normalized: "ccnn" }, 15 | { original: "", normalized: "" }, 16 | ] 17 | 18 | for (let item of items) { 19 | expect(normalizeText(item.original)).to.eq(item.normalized) 20 | } 21 | }) 22 | it("Normalized texts should be lowercase", () => { 23 | const items = [ 24 | { original: "ZXCVBNMASDFGHJKLQWERTYUIOP", normalized: "zxcvbnmasdfghjklqwertyuiop" }, 25 | { original: "aAbBcC", normalized: "aabbcc" }, 26 | ] 27 | 28 | for (let item of items) { 29 | expect(normalizeText(item.original)).to.eq(item.normalized) 30 | } 31 | }) 32 | it("Normalized texts should have no extra blank spaces", () => { 33 | const items = [ 34 | { original: " a b c ", normalized: "a b c" }, 35 | { original: " \t\t\t\t\n a \t\t\t\t\n b \t\t\t\t\n c \t\t\t\t\n ", normalized: "a b c" }, 36 | ] 37 | 38 | for (let item of items) { 39 | expect(normalizeText(item.original)).to.eq(item.normalized) 40 | } 41 | }) 42 | it("Normalized texts should skip symbol collisions", () => { 43 | const items = [ 44 | { original: "d'Angelo d`Angelo d´Angelo", normalized: "d'angelo d'angelo d'angelo" }, 45 | { original: "a.a·a:a", normalized: "a.a.a.a" }, 46 | ] 47 | 48 | for (let item of items) { 49 | expect(normalizeText(item.original)).to.eq(item.normalized) 50 | } 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /packages/client/src/net/providers.ts: -------------------------------------------------------------------------------- 1 | import { StaticJsonRpcProvider, Web3Provider } from "@ethersproject/providers" 2 | import { 3 | EthNetworkID, 4 | VocdoniEnvironment, 5 | XDAI_CHAIN_ID, 6 | XDAI_ENS_REGISTRY_ADDRESS, 7 | SOKOL_CHAIN_ID, 8 | SOKOL_ENS_REGISTRY_ADDRESS, 9 | XDAI_STG_ENS_REGISTRY_ADDRESS, 10 | // MUMBAI_CHAIN_ID, 11 | MATIC_CHAIN_ID, 12 | MATIC_ENS_REGISTRY_ADDRESS, 13 | // MUMBAI_ENS_REGISTRY_ADDRESS 14 | } from "@vocdoni/common" 15 | 16 | export class ProviderUtil { 17 | /** 18 | * Returns a JSON RPC provider using the given Gateway URI 19 | * @param uri 20 | */ 21 | static fromUri(uri: string, networkId?: EthNetworkID, environment: VocdoniEnvironment = 'prod') { 22 | switch (networkId) { 23 | case "xdai": 24 | if (environment === 'prod') 25 | return new StaticJsonRpcProvider(uri, { chainId: XDAI_CHAIN_ID, name: "xdai", ensAddress: XDAI_ENS_REGISTRY_ADDRESS }) 26 | return new StaticJsonRpcProvider(uri, { chainId: XDAI_CHAIN_ID, name: "xdai", ensAddress: XDAI_STG_ENS_REGISTRY_ADDRESS }) 27 | case "sokol": 28 | return new StaticJsonRpcProvider(uri, { chainId: SOKOL_CHAIN_ID, name: "sokol", ensAddress: SOKOL_ENS_REGISTRY_ADDRESS }) 29 | case "matic": 30 | return new StaticJsonRpcProvider(uri, { chainId: MATIC_CHAIN_ID, name: "matic", ensAddress: MATIC_ENS_REGISTRY_ADDRESS }) 31 | // case "mumbai": 32 | // return new StaticJsonRpcProvider(uri, { chainId: MUMBAI_CHAIN_ID, name: "mumbai", ensAddress: MUMBAI_ENS_REGISTRY_ADDRESS }) 33 | default: 34 | return new StaticJsonRpcProvider(uri) 35 | } 36 | } 37 | 38 | /** 39 | * Returns a signer from the web3 current provider (browser only) 40 | * Returns null if not available 41 | */ 42 | static fromInjectedWeb3() { 43 | if (typeof window == "undefined" || typeof window["web3"] == "undefined") return null 44 | return new Web3Provider(window["web3"].currentProvider) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/data-models/README.md: -------------------------------------------------------------------------------- 1 | # @vocdoni/data-models 2 | 3 | @vocdoni/data-models contains the data model definitions for the [dvote-js library](https://github.com/vocdoni/dvote-js/) 4 | 5 | ## Installation 6 | 7 | Use [npm](https://www.npmjs.com/) to install @vocdoni/data-models. 8 | 9 | ```bash 10 | npm install @vocdoni/data-models 11 | ``` 12 | 13 | ## Usage 14 | 15 | #### Account Backup 16 | 17 | ```ts 18 | import { AccountBackup } from "@vocdoni/data-models" 19 | 20 | const wallet = Wallet.createRandom() 21 | const originalPassphrase = Math.random().toString() 22 | const encryptedMnemonic = AccountBackup.encryptPayload(wallet.mnemonic.phrase, originalPassphrase) 23 | 24 | const backupBytes = AccountBackup.create({ 25 | backupName: "Hello world", 26 | questionIds: [1, 2, 3], 27 | answers: ["Answer 1", "Answer 2", "Answer 3"], 28 | accountWallet: { 29 | encryptedMnemonic, 30 | authMethod: Wallet_AuthMethod.PASS, 31 | hdPath: wallet.mnemonic.path, 32 | locale: wallet.mnemonic.locale 33 | }, 34 | currentPassphrase: originalPassphrase 35 | }) 36 | const decryptedPassphrase = AccountBackup.recoverPassphrase(backupBytes, ["Answer 1", "Answer 2", "Answer 3"]) 37 | 38 | ``` 39 | 40 | #### Entity Metadata 41 | 42 | ```ts 43 | import { checkValidEntityMetadata } from "@vocdoni/data-models" 44 | 45 | checkValidEntityMetadata({}) 46 | // throws an Error 47 | ``` 48 | 49 | #### Process Metadata 50 | 51 | ```ts 52 | import { checkValidProcessMetadata } from "@vocdoni/data-models" 53 | 54 | checkValidProcessMetadata({}) 55 | // throws an Error 56 | ``` 57 | 58 | #### Raw transactions 59 | 60 | ```ts 61 | import { Tx, wrapRawTransaction } from "@vocdoni/data-models" 62 | import { BytesSignature } from "@vocdoni/signing" 63 | 64 | const tx = Tx.encode(...) 65 | const txBytes = tx.finish() 66 | const signature = await BytesSignature.signTransaction(txBytes, chainId, signer) 67 | 68 | const result = wrapRawTransaction(txBytes, signature) 69 | // { method: "submitRawTx", payload: "base64..."} 70 | ``` 71 | 72 | ## Testing 73 | 74 | To execute library tests just run 75 | 76 | ```bash 77 | npm run test 78 | ``` 79 | -------------------------------------------------------------------------------- /packages/data-models/test/builders/process-metadata.ts: -------------------------------------------------------------------------------- 1 | import { ProcessMetadataTemplate, ProcessMetadata } from "../../src" 2 | import { URI } from "@vocdoni/common" 3 | 4 | // BUILDER 5 | export default class ProcessMetadataBuilder { 6 | private metadata: ProcessMetadata = null 7 | constructor() { 8 | this.metadata = JSON.parse(JSON.stringify(ProcessMetadataTemplate)) 9 | } 10 | 11 | get() { 12 | return this.metadata 13 | } 14 | 15 | build() { 16 | this.metadata.title = { default: "My Entity" } 17 | this.metadata.description = { default: "Description" } 18 | this.metadata.questions.forEach( 19 | (question, index) => { 20 | question.title = { default: "Question " + String(index) } 21 | question.description = { default: "Description " + String(index) } 22 | question.choices.map((option, index) => { option.title = { default: "Yes " + String(index) } }) 23 | } 24 | ) 25 | return this.metadata 26 | } 27 | 28 | withStreamUri(uri: string) { 29 | this.metadata.media.streamUri = uri as URI 30 | return this 31 | } 32 | 33 | withMeta(meta: any) { 34 | this.metadata.meta = meta 35 | return this 36 | } 37 | 38 | withNumberOfQuestions(size: number) { 39 | const questions = this.metadata.questions 40 | const questionTemplate = JSON.stringify(questions[0]) 41 | for (let i = 0; i < size - 1; i++) { 42 | questions.push(JSON.parse(questionTemplate)) 43 | } 44 | return this 45 | } 46 | 47 | withNumberOfChoices(size: number) { 48 | // to be used without build 49 | this.build() 50 | let choices = this.metadata.questions[0].choices 51 | const choiceTemplate = JSON.stringify(choices[0]) 52 | for (let i = 2; i < size; i++) { 53 | const choice = JSON.parse(choiceTemplate) 54 | choice.title = { default: "Yes " + String(i) } 55 | choice.value = i 56 | choices.push(choice) 57 | } 58 | return this.metadata 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/client/src/wrappers/content-hashed-uri.ts: -------------------------------------------------------------------------------- 1 | import { ContentUri } from "./content-uri" 2 | import { sha256 } from "@ethersproject/sha2" 3 | import { Buffer } from 'buffer/' 4 | import { strip0x } from "@vocdoni/common" 5 | 6 | export class ContentHashedUri extends ContentUri { 7 | protected _hash: string 8 | 9 | /** 10 | * Parses the given string into a Content Hashed URI. 11 | * If the hash term is not provided, a normal Content URI will be created 12 | * */ 13 | constructor(contentHashedUri: string) { 14 | if (typeof contentHashedUri != "string") throw new Error("Invalid contentHashedUri"); 15 | 16 | const terms = contentHashedUri.split("!") 17 | if (!terms || !terms.length) throw new Error("Invalid contentHashedUri") 18 | else if (terms.length > 2) throw new Error("Invalid contentHashedUri") 19 | 20 | super(terms[0]) // Set the Content URI 21 | 22 | if (terms[1]) { 23 | if (!/^(0x)?[0-9a-fA-F]+$/.test(terms[1])) throw new Error("Invalid hash hex string") 24 | else if (terms[1].length % 2 != 0) throw new Error("The hash contains an odd length") 25 | 26 | this._hash = strip0x(terms[1]) 27 | } 28 | } 29 | 30 | /** Returns the Content Hashed URI as a string representation */ 31 | public toString(): string { return super.toString() + "!" + this._hash } 32 | 33 | /** Returns the string representation of the Content URI without the hash term */ 34 | public toContentUriString(): string { return super.toString() } 35 | 36 | /** Returns the hash on the content referenced by the underlying Content URI */ 37 | public get hash(): string { return this._hash || null } 38 | 39 | /** Verifies that the given data produces the current hash */ 40 | public verify(data: Uint8Array | Buffer | number[]) { 41 | if (!this._hash) throw new Error("The Content URI hash is empty") 42 | 43 | return this._hash == ContentHashedUri.hash(data) 44 | } 45 | 46 | /** Computes the SHA256 hash of the content provided */ 47 | static hash(data: Uint8Array | Buffer | number[]): string { 48 | return strip0x(sha256(data)) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/census/src/offchain.ts: -------------------------------------------------------------------------------- 1 | import { utils } from "ethers" 2 | import { Keccak256, Poseidon } from "@vocdoni/hashing" 3 | import { hexStringToBuffer } from "@vocdoni/common" 4 | 5 | export namespace CensusOffChain { 6 | /** 7 | * A census ID consists of the Entity Address and the hash of the name. 8 | * This function returns the full Census ID 9 | */ 10 | export function generateCensusId(censusName: string, entityAddress: string) { 11 | const prefix = entityAddress.toLowerCase() 12 | const suffix = generateCensusIdSuffix(censusName) 13 | return prefix + "/" + suffix 14 | } 15 | 16 | /** 17 | * A census ID consists of the Entity Address and the hash of the name. 18 | * This function computes the second term 19 | */ 20 | export function generateCensusIdSuffix(censusName: string) { 21 | // A census ID consists of the Entity Address and the hash of the name 22 | // Now computing the second term 23 | return Keccak256.hashText(censusName.toLowerCase().trim()) 24 | } 25 | 26 | export namespace Public { 27 | /** 28 | * Returns a base64 representation of the given ECDSA public key 29 | */ 30 | export function encodePublicKey(publicKey: string | Uint8Array | Buffer | number[]): string { 31 | const compPubKey = utils.computePublicKey(publicKey, true) 32 | const pubKeyBytes = hexStringToBuffer(compPubKey) 33 | 34 | return pubKeyBytes.toString("base64") 35 | } 36 | } 37 | 38 | export namespace Anonymous { 39 | /** 40 | * Returns a base64 representation of the Poseidon hash of the given public key, 41 | * left padded to 32 bytes 42 | */ 43 | export function digestPublicKey(x: bigint, y: bigint): string { 44 | const hashedPubKey = Poseidon.hashBabyJubJubPublicKey(x, y) 45 | 46 | let hexHashedPubKey = hashedPubKey.toString(16) 47 | 48 | // Using big-endian, pad any missing bytes until we get 32 bytes 49 | while (hexHashedPubKey.length < 64) { 50 | hexHashedPubKey = "0" + hexHashedPubKey 51 | } 52 | 53 | return Buffer.from(hexHashedPubKey, "hex").toString("base64") 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/client/test/helpers/all-services.ts: -------------------------------------------------------------------------------- 1 | import { DevGatewayService, TestResponseBody } from "./dvote-service" 2 | import { DevWeb3Service } from "./web3-service" 3 | import { GatewayInfo, Gateway, Web3Gateway } from "../../src" 4 | 5 | export { DevGatewayService, TestResponse, TestResponseBody, MockedInteraction } from "./dvote-service" 6 | export { TestAccount } from "./web3-service" 7 | 8 | export default class DevServices { 9 | readonly dvote: DevGatewayService 10 | readonly web3: DevWeb3Service 11 | 12 | constructor(dvoteParams: { port?: number, responses?: TestResponseBody[] } = {}, web3Params: { port?: number, mnemonic?: string } = {}) { 13 | this.dvote = new DevGatewayService(dvoteParams) 14 | this.web3 = new DevWeb3Service(web3Params) 15 | } 16 | 17 | start(): Promise { 18 | return this.dvote.start() 19 | .then(() => this.web3.start()) 20 | } 21 | 22 | stop(): Promise { 23 | this.web3.stop() 24 | return this.dvote.stop() 25 | } 26 | 27 | // GETTERS 28 | get dvoteGateway() { return this.dvote.client } 29 | 30 | getWeb3Gateway(entityResolverAddress: string = "", namespaceAddress: string = "", storageProofAddress: string = "", processAddress: string = ""): Promise { 31 | return this.web3.getClient(entityResolverAddress, namespaceAddress, storageProofAddress, processAddress) 32 | } 33 | 34 | get gatewayInfo() { 35 | return new GatewayInfo(this.dvote.uri, ["file", "vote", "census", "results"], this.web3.uri, this.dvote.publicKey) 36 | } 37 | 38 | /** Returns a Gateway client for the dvote and Web3 local services. The Web3 gateway uses the given addresses as the resolved ones for the contracts */ 39 | getGateway(entityResolverAddress: string = "", namespaceAddress: string = "", storageProofAddress: string = "", processAddress: string = ""): Promise { 40 | const dvoteGw = this.dvote.client 41 | 42 | return this.web3.getClient(entityResolverAddress, namespaceAddress, storageProofAddress, processAddress) 43 | .then(web3Gw => new Gateway(dvoteGw, web3Gw)) 44 | } 45 | 46 | /** Returns accounts with funds on the in-memory ganacle blockchain */ 47 | get accounts() { 48 | return this.web3.accounts 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/census/src/blind.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BigInteger, 3 | blind as _blind, 4 | Point, 5 | pointFromHex as _pointFromHex, 6 | signatureFromHex as _signatureFromHex, 7 | signatureToHex as _signatureToHex, 8 | unblind as _unblind, 9 | UnblindedSignature, 10 | UserSecretData, 11 | verify as _verify 12 | } from "blindsecp256k1" 13 | import { hexZeroPad } from "@ethersproject/bytes" 14 | import { ensure0x } from "@vocdoni/common" 15 | 16 | export namespace CensusBlind { 17 | /** Decodes the given hex-encoded point */ 18 | export function decodePoint(hexPoint: string): Point { 19 | return _pointFromHex(hexPoint) 20 | } 21 | 22 | /** Blinds the given hex string using the given R point and returns the secret data to unblind an eventual blinded signature */ 23 | export function blind(hexMessage: string, signerR: Point): { hexBlinded: string, userSecretData: UserSecretData } { 24 | const msg = BigInteger.fromHex(hexMessage) 25 | const { mBlinded, userSecretData } = _blind(msg, signerR) 26 | 27 | return { hexBlinded: hexZeroPad(ensure0x(mBlinded.toString(16)), 32).slice(2), userSecretData } 28 | } 29 | 30 | /** Unblinds the given blinded signature and returns it as a hex string */ 31 | export function unblind(hexBlindedSignature: string, userSecretData: UserSecretData): string { 32 | const sBlind = BigInteger.fromHex(hexBlindedSignature) 33 | const unblindedSignature = _unblind(sBlind, userSecretData) 34 | 35 | return _signatureToHex(unblindedSignature) 36 | } 37 | 38 | /** Verifies that the given blind signature is valid */ 39 | export function verify(hexMsg: string, hexUnblindedSignature: string, pk: Point) { 40 | const msg = BigInteger.fromHex(hexMsg) 41 | 42 | const unblindedSignature = signatureFromHex(hexUnblindedSignature) 43 | 44 | return _verify(msg, unblindedSignature, pk) 45 | } 46 | 47 | /** Deserializes the given hex Signature */ 48 | export function signatureFromHex(hexSignature: string) { 49 | return _signatureFromHex(hexSignature) 50 | } 51 | 52 | /** Serializes the given signature into a hex string */ 53 | export function signatureToHex(signature: UnblindedSignature) { 54 | return _signatureToHex(signature) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /example/web/styles/Page.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | min-height: 100vh; 3 | padding: 0 0.5rem; 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: center; 7 | align-items: center; 8 | height: 100vh; 9 | } 10 | 11 | .main { 12 | padding: 5rem 0; 13 | flex: 1; 14 | display: flex; 15 | flex-direction: column; 16 | justify-content: center; 17 | align-items: center; 18 | } 19 | 20 | .footer { 21 | width: 100%; 22 | height: 100px; 23 | border-top: 1px solid #eaeaea; 24 | display: flex; 25 | justify-content: center; 26 | align-items: center; 27 | } 28 | 29 | .footer a { 30 | display: flex; 31 | justify-content: center; 32 | align-items: center; 33 | flex-grow: 1; 34 | } 35 | 36 | .title a { 37 | color: #0070f3; 38 | text-decoration: none; 39 | } 40 | 41 | .title a:hover, 42 | .title a:focus, 43 | .title a:active { 44 | text-decoration: underline; 45 | } 46 | 47 | .title { 48 | margin: 0; 49 | line-height: 1.15; 50 | font-size: 4rem; 51 | } 52 | 53 | .title, 54 | .description { 55 | text-align: center; 56 | } 57 | 58 | .description { 59 | line-height: 1.5; 60 | font-size: 1.5rem; 61 | } 62 | 63 | .code { 64 | background: #fafafa; 65 | border-radius: 5px; 66 | padding: 0.75rem; 67 | font-size: 1.1rem; 68 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, 69 | Bitstream Vera Sans Mono, Courier New, monospace; 70 | } 71 | 72 | .grid { 73 | display: flex; 74 | align-items: center; 75 | justify-content: center; 76 | flex-wrap: wrap; 77 | max-width: 800px; 78 | margin-top: 3rem; 79 | } 80 | 81 | .card { 82 | margin: 1rem; 83 | padding: 1.5rem; 84 | text-align: left; 85 | color: inherit; 86 | text-decoration: none; 87 | border: 1px solid #eaeaea; 88 | border-radius: 10px; 89 | transition: color 0.15s ease, border-color 0.15s ease; 90 | width: 45%; 91 | } 92 | 93 | .card:hover, 94 | .card:focus, 95 | .card:active { 96 | color: #0070f3; 97 | border-color: #0070f3; 98 | } 99 | 100 | .card h2 { 101 | margin: 0 0 1rem 0; 102 | font-size: 1.5rem; 103 | } 104 | 105 | .card p { 106 | margin: 0; 107 | font-size: 1.25rem; 108 | line-height: 1.5; 109 | } 110 | 111 | .logo { 112 | height: 1em; 113 | margin-left: 0.5rem; 114 | } 115 | 116 | @media (max-width: 600px) { 117 | .grid { 118 | width: 100%; 119 | flex-direction: column; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /packages/wallets/src/util/helpers.ts: -------------------------------------------------------------------------------- 1 | import { toUtf8Bytes } from "@ethersproject/strings" 2 | import { keccak256 } from "@ethersproject/keccak256" 3 | import { arrayify } from "@ethersproject/bytes" 4 | import { strip0x, ensure0x } from "@vocdoni/common" 5 | 6 | /** 7 | * Returns `false` if the passphrase is shorter than 8 characters, or it doesn't contain 8 | * at least one digit, one lowercase character and an uppercase one 9 | * @param passphrase 10 | */ 11 | export function isStrongPassphrase(passphrase: string): boolean { 12 | if (passphrase.length < 8) return false 13 | else if (!passphrase.match(/[a-z]+/)) return false 14 | else if (!passphrase.match(/[A-Z]+/)) return false 15 | else if (!passphrase.match(/[0-9]+/)) return false 16 | return true 17 | } 18 | 19 | /** 20 | * Generates a deterministic 32 byte payload from the given UTF8 passphrase and the given hexadecimal seed. 21 | * @param passphrase A UTF8 string 22 | * @param hexSeed A 32 byte hex string with the leading '0x' 23 | * @param rounds Number of hashing rounds to apply to the resulting payload (default: 10) 24 | */ 25 | export function digestSeededPassphrase(passphrase: string, hexSeed: string, rounds: number = 10): string { 26 | if (typeof passphrase != "string" || typeof hexSeed != "string") throw new Error("Invalid parameters") 27 | 28 | hexSeed = strip0x(hexSeed) 29 | if (hexSeed.length != 64) throw new Error("The hashed passphrase should be 64 characters long instead of " + hexSeed.length) 30 | 31 | // Conver the passphrase into UTF8 bytes and hash them 32 | const passphraseBytes = toUtf8Bytes(passphrase) 33 | const passphraseBytesHashed = strip0x(keccak256(passphraseBytes)) 34 | 35 | if (passphraseBytesHashed.length != 64) 36 | throw new Error("Internal error: The hashed passphrase should be 64 characters long instead of " + passphraseBytesHashed.length) 37 | 38 | // Concatenating the bytes of the hashed passphrase + the seed's 39 | const sourceBytes = arrayify(ensure0x(passphraseBytesHashed + hexSeed)) 40 | if (sourceBytes.length != 64) 41 | throw new Error("Internal error: The sourceBytes array should be 64 bytes long instead of " + sourceBytes.length) 42 | 43 | let result: string 44 | 45 | // Perform N rounds of keccak256 46 | for (let i = 0; i < rounds; i++) { 47 | if (typeof result == "undefined") result = keccak256(sourceBytes) 48 | else result = keccak256(result) 49 | } 50 | 51 | return result 52 | } 53 | -------------------------------------------------------------------------------- /packages/client/test/builders/genesis.ts: -------------------------------------------------------------------------------- 1 | // NOTE: This code is borrowed from dvote-solidity 2 | 3 | import { 4 | GenesisContractMethods, 5 | GenesisContractDefinition 6 | } from "@vocdoni/contract-wrappers" 7 | import { Contract, ContractFactory } from "@ethersproject/contracts" 8 | import { TestAccount } from "../helpers/all-services" 9 | 10 | export const DEFAULT_CHAIN_ID = 0 11 | export const DEFAULT_GENESIS = "{genesis-goes-here}" 12 | export const DEFAULT_VALIDATORS = ["0x03e3e79624fe1b1da829946b2269dfa01ef8ea3d99dc5032d8590ab71f8bd1b399", "0x031d04a26c158223550251366c2440601c3b297488e29dd4271610957b7394b66e", "0x023a91c59a377a0721a7afe044dbdd270b3df32ef70c8bf483db934f0170c5c8ae"] 13 | export const DEFAULT_ORACLES = ["0x16EBAAe4EEBC77662aF386cE0Cb0C9b53ACb8689", "0x6B902080Bea78A6dDB7025c6F5F8FD634fA2a3A8", "0xaD9b7B66bD14787a02285514c410627DAb5F6c14"] 14 | 15 | // BUILDER 16 | export default class GenesisBuilder { 17 | accounts: TestAccount[] 18 | 19 | entityAccount: TestAccount 20 | genesis: string = DEFAULT_GENESIS 21 | validators: string[] = DEFAULT_VALIDATORS 22 | oracles: string[] = DEFAULT_ORACLES 23 | 24 | constructor(devAccounts: TestAccount[]) { 25 | this.accounts = devAccounts 26 | this.entityAccount = this.accounts[1] 27 | } 28 | 29 | async build(): Promise { 30 | const deployAccount = this.accounts[0] 31 | const contractFactory = new ContractFactory(GenesisContractDefinition.abi, GenesisContractDefinition.bytecode, deployAccount.wallet) 32 | let contractInstance = await contractFactory.deploy() as Contract & GenesisContractMethods 33 | 34 | const tx = await contractInstance.newChain(this.genesis, this.validators, this.oracles) 35 | await tx.wait() 36 | 37 | return contractInstance.connect(this.entityAccount.wallet) as Contract & GenesisContractMethods 38 | } 39 | 40 | withGenesis(genesis: string) { 41 | if (typeof genesis != "string") throw new Error("Invalid string") 42 | 43 | this.genesis = genesis 44 | return this 45 | } 46 | withValidators(validators: string[]) { 47 | if (!Array.isArray(validators)) throw new Error("Invalid validators array") 48 | 49 | this.validators = validators 50 | return this 51 | } 52 | withOracles(oracles: string[]) { 53 | if (!Array.isArray(oracles)) throw new Error("Invalid oracles array") 54 | 55 | this.oracles = oracles 56 | return this 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/client/src/net/ens.ts: -------------------------------------------------------------------------------- 1 | import { keccak256 } from "@ethersproject/keccak256" 2 | import { 3 | EthNetworkID, 4 | VOCDONI_GOERLI_ENTITY_ID, 5 | VOCDONI_MAINNET_ENTITY_ID, 6 | VOCDONI_RINKEBY_ENTITY_ID, 7 | VOCDONI_SOKOL_ENTITY_ID, 8 | VOCDONI_XDAI_ENTITY_ID, 9 | VOCDONI_XDAI_STG_ENTITY_ID, 10 | VocdoniEnvironment, 11 | VOCDONI_AVAX_FUJI_ENTITY_ID, 12 | VOCDONI_MATIC_ENTITY_ID, 13 | // VOCDONI_MUMBAI_ENTITY_ID 14 | } from "@vocdoni/common" 15 | import { IEnsPublicResolverContract } from "@vocdoni/contract-wrappers" 16 | import { IGatewayWeb3Client } from "../interfaces" 17 | 18 | export function getEnsTextRecord( 19 | gateway: IGatewayWeb3Client, 20 | recordKey: string, 21 | params: { environment: VocdoniEnvironment, networkId: EthNetworkID } = { environment: "prod", networkId: "mainnet" }, 22 | ): Promise { 23 | return gateway.getEnsPublicResolverInstance() 24 | .then(async (instance: IEnsPublicResolverContract) => { 25 | let entityEnsNode = "" 26 | switch (params.networkId) { 27 | case "homestead": 28 | case "mainnet": 29 | entityEnsNode = keccak256(VOCDONI_MAINNET_ENTITY_ID) 30 | break 31 | case "goerli": 32 | entityEnsNode = keccak256(VOCDONI_GOERLI_ENTITY_ID) 33 | break 34 | case "rinkeby": 35 | entityEnsNode = keccak256(VOCDONI_RINKEBY_ENTITY_ID) 36 | break 37 | case "xdai": 38 | if (params.environment === "prod") { 39 | entityEnsNode = keccak256(VOCDONI_XDAI_ENTITY_ID) 40 | break 41 | } 42 | entityEnsNode = keccak256(VOCDONI_XDAI_STG_ENTITY_ID) 43 | break 44 | case "sokol": 45 | entityEnsNode = keccak256(VOCDONI_SOKOL_ENTITY_ID) 46 | break 47 | case "fuji": 48 | entityEnsNode = keccak256(VOCDONI_AVAX_FUJI_ENTITY_ID) 49 | break 50 | case "matic": 51 | entityEnsNode = keccak256(VOCDONI_MATIC_ENTITY_ID) 52 | break 53 | // case "mumbai": 54 | // entityEnsNode = keccak256(VOCDONI_MUMBAI_ENTITY_ID) 55 | // break 56 | default: 57 | throw new Error("Unsupported network ID") 58 | } 59 | return instance.text(entityEnsNode, recordKey) 60 | }) 61 | } 62 | -------------------------------------------------------------------------------- /packages/client/src/util/uint8array.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Extract in bytes (Uint8Array) the JSON value of a unique JSON key from a Uint8Array 3 | * @param array Uint8Array 4 | * @param regex RegExp that defines the begining of the JSON value to be extracted (that must bey a unique JSON key) 5 | */ 6 | export function extractUint8ArrayJSONValue(array: Uint8Array, field: string): Uint8Array { 7 | let c: number, char2: number, char3: number, lastChar: string 8 | let countEmbJSON = 0 9 | let responseStartByte = 0 10 | let responseEndByte = 0 11 | const responseStart = '"' + field + '":' 12 | let out = "" 13 | let i = 0; 14 | 15 | while (i < array.length) { 16 | c = array[i++]; 17 | switch (c >> 4) { 18 | case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: 19 | // 0xxxxxxx 20 | lastChar = String.fromCharCode(c) 21 | break; 22 | case 12: case 13: 23 | // 110x xxxx 10xx xxxx 24 | char2 = array[i++]; 25 | lastChar = String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F)) 26 | break; 27 | case 14: 28 | // 1110 xxxx 10xx xxxx 10xx xxxx 29 | char2 = array[i++]; 30 | char3 = array[i++]; 31 | lastChar = String.fromCharCode(((c & 0x0F) << 12) | 32 | ((char2 & 0x3F) << 6) | 33 | ((char3 & 0x3F) << 0)) 34 | break; 35 | } 36 | if (responseStartByte > 0 && responseEndByte == 0) { 37 | switch (lastChar) { 38 | case "{": 39 | // Found sub-JSON 40 | countEmbJSON++ 41 | break; 42 | case "}": 43 | if (countEmbJSON == 0) { 44 | // Finish search and return 45 | responseEndByte = i 46 | return array.slice(responseStartByte, responseEndByte) 47 | } else if (countEmbJSON > 0) { 48 | // Found closed sub-JSON 49 | countEmbJSON-- 50 | } else { 51 | // console.error("unexpected character") 52 | return new Uint8Array() 53 | } 54 | break; 55 | } 56 | } 57 | if (responseStartByte == 0 && out.lastIndexOf(responseStart) > -1) { 58 | responseStartByte = i - 1 59 | } 60 | 61 | out += lastChar 62 | } 63 | // no success 64 | return new Uint8Array() 65 | } -------------------------------------------------------------------------------- /example/signed-erc20/config.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert" 2 | import { readFileSync } from "fs" 3 | import * as YAML from 'yaml' 4 | import { VocdoniEnvironment } from "@vocdoni/common" 5 | 6 | const CONFIG_PATH = "./config.yaml" 7 | 8 | export function getConfig(): Config { 9 | const config: Config = YAML.parse(readFileSync(CONFIG_PATH).toString()) 10 | 11 | assert(typeof config == "object", "The config file appears to be invalid") 12 | assert(typeof config.readExistingProcess == "boolean", "config.yaml > readExistingProcess should be a boolean") 13 | assert(typeof config.stopOnError == "boolean", "config.yaml > stopOnError should be a boolean") 14 | assert(typeof config.processInfoFilePath == "string", "config.yaml > processInfoFilePath should be a string") 15 | assert(typeof config.ethNetworkId == "string", "config.yaml > ethNetworkId should be a string") 16 | assert(typeof config.vocdoniEnvironment == "string", "config.yaml > vocdoniEnvironment should be a string") 17 | assert(typeof config.tokenAddress == "string", "config.yaml > tokenAddress should be a string") 18 | // assert(typeof config.tokenBalanceMappingPosition == "number", "config.yaml > tokenBalanceMappingPosition should be a number") 19 | assert(Array.isArray(config.privKeys) && config.privKeys.length, "config.yaml > privKeys should be an array of strings") 20 | assert(typeof config.bootnodesUrlRw == "string", "config.yaml > bootnodesUrlRw should be a string") 21 | assert(!config.dvoteGatewayUri || typeof config.dvoteGatewayUri == "string", "config.yaml > dvoteGatewayUri should be a string") 22 | assert(!config.dvoteGatewayPublicKey || typeof config.dvoteGatewayPublicKey == "string", "config.yaml > dvoteGatewayPublicKey should be a string") 23 | assert(!config.web3Uri || typeof config.web3Uri == "string", "config.yaml > web3Uri should be a string") 24 | assert(typeof config.encryptedVote == "boolean", "config.yaml > encryptedVote should be a boolean") 25 | assert(typeof config.votesPattern == "string", "config.yaml > votesPattern should be a string") 26 | return config 27 | } 28 | 29 | type Config = { 30 | readExistingProcess: boolean 31 | stopOnError: boolean 32 | 33 | processInfoFilePath: string 34 | 35 | ethNetworkId: string 36 | vocdoniEnvironment: VocdoniEnvironment 37 | tokenAddress: string 38 | tokenBalanceMappingPosition?: number 39 | privKeys: string[] 40 | 41 | bootnodesUrlRw: string 42 | dvoteGatewayUri: string 43 | dvoteGatewayPublicKey: string 44 | web3Uri: string 45 | 46 | encryptedVote: boolean 47 | votesPattern: "all-0" | "all-1" | "all-2" | "all-even" | "incremental" 48 | } 49 | -------------------------------------------------------------------------------- /packages/data-models/test/unit/entities.ts: -------------------------------------------------------------------------------- 1 | import "mocha" // using @types/mocha 2 | import { expect } from "chai" 3 | import { addCompletionHooks } from "../mocha-hooks" 4 | 5 | import { checkValidEntityMetadata, EntityMetadataTemplate } from "../../src" 6 | 7 | addCompletionHooks() 8 | 9 | describe("Entity metadata", () => { 10 | 11 | it("Should accept a valid Entity Metadata JSON", () => { 12 | const entityMetadata = EntityMetadataTemplate 13 | 14 | expect(() => { 15 | checkValidEntityMetadata(entityMetadata) 16 | }).to.not.throw() 17 | }) 18 | 19 | it("Should reject invalid Entity Metadata JSON payloads", () => { 20 | const invalidMeta = 123 21 | // Totally invalid 22 | expect(() => { 23 | const payload = JSON.parse('{"test": 123}') 24 | checkValidEntityMetadata(payload) 25 | }).to.throw() 26 | 27 | expect(() => { 28 | const payload = JSON.parse('{"name": {"default": "hello", "fr": "Alô"}}') 29 | checkValidEntityMetadata(payload) 30 | }).to.throw() 31 | 32 | // Incomplete fields 33 | const entityMetadata = JSON.parse(JSON.stringify(EntityMetadataTemplate)) 34 | 35 | expect(() => { checkValidEntityMetadata(Object.assign({}, entityMetadata, { version: null })) }).to.throw() 36 | expect(() => { checkValidEntityMetadata(Object.assign({}, entityMetadata, { languages: null })) }).to.throw() 37 | expect(() => { checkValidEntityMetadata(Object.assign({}, entityMetadata, { name: null })) }).to.throw() 38 | expect(() => { checkValidEntityMetadata(Object.assign({}, entityMetadata, { description: null })) }).to.throw() 39 | expect(() => { checkValidEntityMetadata(Object.assign({}, entityMetadata, { votingProcesses: [] })) }).to.not.throw() 40 | expect(() => { checkValidEntityMetadata(Object.assign({}, entityMetadata, { newsFeed: null })) }).to.throw() 41 | expect(() => { checkValidEntityMetadata(Object.assign({}, entityMetadata, { media: { avatar: null } })) }).to.throw() 42 | expect(() => { checkValidEntityMetadata(Object.assign({}, entityMetadata, { actions: null })) }).to.throw() 43 | expect(() => { checkValidEntityMetadata(Object.assign({}, entityMetadata, { bootEntities: [] })) }).to.not.throw() 44 | expect(() => { checkValidEntityMetadata(Object.assign({}, entityMetadata, { fallbackBootNodeEntities: [] })) }).to.not.throw() 45 | expect(() => { checkValidEntityMetadata(Object.assign({}, entityMetadata, { trustedEntities: [] })) }).to.not.throw() 46 | expect(() => { checkValidEntityMetadata(Object.assign({}, entityMetadata, { censusServiceManagedEntities: [] })) }).to.not.throw() 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /example/signed-erc20-signal/config.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert" 2 | import { readFileSync } from "fs" 3 | import * as YAML from 'yaml' 4 | import { VocdoniEnvironment } from "@vocdoni/common" 5 | 6 | const CONFIG_PATH = "./config.yaml" 7 | 8 | export function getConfig(): Config { 9 | const config: Config = YAML.parse(readFileSync(CONFIG_PATH).toString()) 10 | 11 | assert(typeof config == "object", "The config file appears to be invalid") 12 | assert(typeof config.readExistingProcess == "boolean", "config.yaml > readExistingProcess should be a boolean") 13 | assert(typeof config.stopOnError == "boolean", "config.yaml > stopOnError should be a boolean") 14 | assert(typeof config.processInfoFilePath == "string", "config.yaml > processInfoFilePath should be a string") 15 | assert(typeof config.ethNetworkId == "string", "config.yaml > ethNetworkId should be a string") 16 | assert(typeof config.vocdoniEnvironment == "string", "config.yaml > vocdoniEnvironment should be a string") 17 | assert(typeof config.tokenAddress == "string", "config.yaml > tokenAddress should be a string") 18 | // assert(typeof config.tokenBalanceMappingPosition == "number", "config.yaml > tokenBalanceMappingPosition should be a number") 19 | assert(Array.isArray(config.privKeys) && config.privKeys.length, "config.yaml > privKeys should be an array of strings") 20 | assert(typeof config.bootnodesUrlRw == "string", "config.yaml > bootnodesUrlRw should be a string") 21 | assert(!config.dvoteGatewayUri || typeof config.dvoteGatewayUri == "string", "config.yaml > dvoteGatewayUri should be a string") 22 | assert(!config.dvoteGatewayPublicKey || typeof config.dvoteGatewayPublicKey == "string", "config.yaml > dvoteGatewayPublicKey should be a string") 23 | assert(!config.web3Uri || typeof config.web3Uri == "string", "config.yaml > web3Uri should be a string") 24 | assert(typeof config.oracleUri == "string", "config.yaml > oracleUri should be a string") 25 | assert(typeof config.encryptedVote == "boolean", "config.yaml > encryptedVote should be a boolean") 26 | assert(typeof config.votesPattern == "string", "config.yaml > votesPattern should be a string") 27 | return config 28 | } 29 | 30 | type Config = { 31 | readExistingProcess: boolean 32 | stopOnError: boolean 33 | 34 | processInfoFilePath: string 35 | 36 | ethNetworkId: string 37 | vocdoniEnvironment: VocdoniEnvironment 38 | tokenAddress: string 39 | tokenBalanceMappingPosition?: number 40 | privKeys: string[] 41 | 42 | bootnodesUrlRw: string 43 | dvoteGatewayUri: string 44 | dvoteGatewayPublicKey: string 45 | web3Uri: string 46 | oracleUri: string 47 | 48 | encryptedVote: boolean 49 | votesPattern: "all-0" | "all-1" | "all-2" | "all-even" | "incremental" 50 | } 51 | -------------------------------------------------------------------------------- /packages/common/src/random.ts: -------------------------------------------------------------------------------- 1 | import { keccak256 } from "@ethersproject/keccak256" 2 | 3 | export namespace Random { 4 | /** 5 | * Generates a random buffer of the given length 6 | */ 7 | export function getBytes(count: number): Buffer { 8 | if (typeof window != "undefined" && typeof window?.crypto?.getRandomValues != "function") { 9 | // browser 10 | const buff = new Uint8Array(count) 11 | window.crypto.getRandomValues(buff) 12 | return Buffer.from(buff) 13 | } 14 | else if (typeof process != "undefined" && typeof require != "undefined" 15 | && typeof require("crypto")?.randomBytes != "undefined") { 16 | return require("crypto").randomBytes(count) 17 | } 18 | 19 | // other environments (fallback) 20 | const result: number[] = [] 21 | for (let i = 0; i < count; i++) { 22 | const val = Math.random() * 256 | 0 23 | result.push(val) 24 | } 25 | return Buffer.from(result) 26 | } 27 | 28 | /** 29 | * Generates a random seed and returns a 32 byte keccak256 hash of it (starting with "0x") 30 | */ 31 | export function getHex(): string { 32 | const bytes = getBytes(32) 33 | return keccak256(bytes) 34 | } 35 | 36 | /** 37 | * Generates a random big integer, ranging from `0n` to `maxValue - 1` 38 | */ 39 | export function getBigInt(maxValue: bigint): bigint { 40 | const step = BigInt("256") 41 | let result = BigInt("0") 42 | let nextByte: number 43 | let nextValue: bigint 44 | 45 | while (true) { 46 | nextByte = getBytes(1)[0] 47 | nextValue = result * step + BigInt(nextByte) 48 | 49 | if (nextValue > maxValue) { 50 | // already reached maxValue 51 | return nextValue % maxValue 52 | } 53 | // accumulate bytes 54 | result = nextValue 55 | } 56 | } 57 | 58 | /** 59 | * Helper function that shuffles the elements of an array 60 | */ 61 | export function shuffle(array: T[]): T[] { 62 | let temporaryValue: T, idx: number 63 | let currentIndex = array.length 64 | 65 | // While there remain elements to shuffle... 66 | while (0 !== currentIndex) { 67 | // Pick a remaining element... 68 | idx = Math.floor(Math.random() * currentIndex) 69 | currentIndex -= 1 70 | 71 | // And swap it with the current element. 72 | temporaryValue = JSON.parse(JSON.stringify(array[currentIndex])) 73 | array[currentIndex] = JSON.parse(JSON.stringify(array[idx])) 74 | array[idx] = temporaryValue 75 | } 76 | 77 | return array 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /example/signed-off-chain/config.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert" 2 | import { readFileSync } from "fs" 3 | import * as YAML from 'yaml' 4 | import { VocdoniEnvironment } from "@vocdoni/common" 5 | 6 | const CONFIG_PATH = "./config.yaml" 7 | 8 | export function getConfig(): Config { 9 | const config: Config = YAML.parse(readFileSync(CONFIG_PATH).toString()) 10 | assert(typeof config == "object", "The config file appears to be invalid") 11 | assert(typeof config.readExistingAccounts == "boolean", "config.yaml > readExistingAccounts should be a boolean") 12 | assert(typeof config.readExistingProcess == "boolean", "config.yaml > readExistingProcess should be a boolean") 13 | assert(typeof config.stopOnError == "boolean", "config.yaml > stopOnError should be a boolean") 14 | assert(typeof config.accountListFilePath == "string", "config.yaml > accountListFilePath should be a string") 15 | assert(typeof config.processInfoFilePath == "string", "config.yaml > processInfoFilePath should be a string") 16 | assert(typeof config.mnemonic == "string", "config.yaml > mnemonic should be a string") 17 | assert(config.mnemonic, "config.yaml > Please, set the mnemonic to use") 18 | assert(typeof config.ethPath == "string", "config.yaml > ethPath should be a string") 19 | assert(typeof config.ethNetworkId == "string", "config.yaml > ethNetworkId should be a string") 20 | assert(typeof config.vocdoniEnvironment == "string", "config.yaml > vocdoniEnvironment should be a string") 21 | assert(typeof config.bootnodesUrlRw == "string", "config.yaml > bootnodesUrlRw should be a string") 22 | assert(!config.dvoteGatewayUri || typeof config.dvoteGatewayUri == "string", "config.yaml > dvoteGatewayUri should be a string") 23 | assert(!config.dvoteGatewayPublicKey || typeof config.dvoteGatewayPublicKey == "string", "config.yaml > dvoteGatewayPublicKey should be a string") 24 | assert(typeof config.numAccounts == "number", "config.yaml > numAccounts should be a number") 25 | assert(typeof config.maxConcurrency == "number", "config.yaml > maxConcurrency should be a number") 26 | assert(typeof config.encryptedVote == "boolean", "config.yaml > encryptedVote should be a boolean") 27 | assert(typeof config.votesPattern == "string", "config.yaml > votesPattern should be a string") 28 | return config 29 | } 30 | 31 | type Config = { 32 | readExistingAccounts: boolean 33 | readExistingProcess: boolean 34 | stopOnError: boolean 35 | 36 | accountListFilePath: string 37 | processInfoFilePath: string 38 | 39 | mnemonic: string 40 | ethPath: string 41 | ethNetworkId: string 42 | 43 | vocdoniEnvironment: VocdoniEnvironment 44 | bootnodesUrlRw: string 45 | dvoteGatewayUri: string 46 | dvoteGatewayPublicKey: string 47 | 48 | numAccounts: number 49 | maxConcurrency: number 50 | 51 | encryptedVote: boolean 52 | votesPattern: "all-0" | "all-1" | "all-2" | "all-even" | "incremental" 53 | } 54 | -------------------------------------------------------------------------------- /example/anonymous-off-chain/config.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert" 2 | import { readFileSync } from "fs" 3 | import * as YAML from 'yaml' 4 | import { VocdoniEnvironment } from "@vocdoni/common" 5 | 6 | const CONFIG_PATH = "./config.yaml" 7 | 8 | export function getConfig(): Config { 9 | const config: Config = YAML.parse(readFileSync(CONFIG_PATH).toString()) 10 | assert(typeof config == "object", "The config file appears to be invalid") 11 | assert(typeof config.readExistingAccounts == "boolean", "config.yaml > readExistingAccounts should be a boolean") 12 | assert(typeof config.readExistingProcess == "boolean", "config.yaml > readExistingProcess should be a boolean") 13 | assert(typeof config.stopOnError == "boolean", "config.yaml > stopOnError should be a boolean") 14 | assert(typeof config.accountListFilePath == "string", "config.yaml > accountListFilePath should be a string") 15 | assert(typeof config.processInfoFilePath == "string", "config.yaml > processInfoFilePath should be a string") 16 | assert(typeof config.mnemonic == "string", "config.yaml > mnemonic should be a string") 17 | assert(config.mnemonic, "config.yaml > Please, set the mnemonic to use") 18 | assert(typeof config.ethPath == "string", "config.yaml > ethPath should be a string") 19 | assert(typeof config.ethNetworkId == "string", "config.yaml > ethNetworkId should be a string") 20 | assert(typeof config.vocdoniEnvironment == "string", "config.yaml > vocdoniEnvironment should be a string") 21 | assert(typeof config.bootnodesUrlRw == "string", "config.yaml > bootnodesUrlRw should be a string") 22 | assert(!config.dvoteGatewayUri || typeof config.dvoteGatewayUri == "string", "config.yaml > dvoteGatewayUri should be a string") 23 | assert(!config.dvoteGatewayPublicKey || typeof config.dvoteGatewayPublicKey == "string", "config.yaml > dvoteGatewayPublicKey should be a string") 24 | assert(typeof config.numAccounts == "number", "config.yaml > numAccounts should be a number") 25 | assert(typeof config.maxConcurrency == "number", "config.yaml > maxConcurrency should be a number") 26 | assert(typeof config.encryptedVote == "boolean", "config.yaml > encryptedVote should be a boolean") 27 | assert(typeof config.votesPattern == "string", "config.yaml > votesPattern should be a string") 28 | return config 29 | } 30 | 31 | type Config = { 32 | readExistingAccounts: boolean 33 | readExistingProcess: boolean 34 | stopOnError: boolean 35 | 36 | accountListFilePath: string 37 | processInfoFilePath: string 38 | 39 | mnemonic: string 40 | ethPath: string 41 | ethNetworkId: string 42 | 43 | vocdoniEnvironment: VocdoniEnvironment 44 | bootnodesUrlRw: string 45 | dvoteGatewayUri: string 46 | dvoteGatewayPublicKey: string 47 | 48 | numAccounts: number 49 | maxConcurrency: number 50 | 51 | encryptedVote: boolean 52 | votesPattern: "all-0" | "all-1" | "all-2" | "all-even" | "incremental" 53 | } 54 | -------------------------------------------------------------------------------- /packages/hashing/test/unit/poseidon.ts: -------------------------------------------------------------------------------- 1 | import "mocha" // using @types/mocha 2 | import { expect } from "chai" 3 | import { addCompletionHooks } from "../mocha-hooks" 4 | 5 | import { Poseidon } from "../../src" 6 | 7 | addCompletionHooks() 8 | 9 | describe("Poseidon hashing", () => { 10 | it("Should hash big int's", () => { 11 | const items = [ 12 | { 13 | input: BigInt("1"), 14 | output: BigInt('18586133768512220936620570745912940619677854269274689475585506675881198879027') 15 | }, 16 | { 17 | input: BigInt("18586133768512220936620570745912940619677854269274689475585506675881198879027"), 18 | output: BigInt('17744324452969507964952966931655538206777558023197549666337974697819074895989') 19 | }, 20 | ] 21 | 22 | items.forEach(item => { 23 | expect(Poseidon.hash([item.input])).to.eq(item.output) 24 | }) 25 | }) 26 | 27 | it("Poseidon hashes should digest bigint arrays", () => { 28 | const BI_0 = BigInt("0") 29 | const BI_1 = BigInt("1") 30 | const BI_2 = BigInt("2") 31 | const BI_3 = BigInt("3") 32 | const BI_4 = BigInt("4") 33 | const BI_5 = BigInt("5") 34 | const BI_6 = BigInt("6") 35 | 36 | let hash = Poseidon.hash([BI_1]) 37 | expect(hash.toString()).to.eq("18586133768512220936620570745912940619677854269274689475585506675881198879027") 38 | 39 | hash = Poseidon.hash([BI_1, BI_2]) 40 | expect(hash.toString()).to.eq("7853200120776062878684798364095072458815029376092732009249414926327459813530") 41 | 42 | hash = Poseidon.hash([BI_1, BI_2, BI_0, BI_0, BI_0]) 43 | expect(hash.toString()).to.eq("1018317224307729531995786483840663576608797660851238720571059489595066344487") 44 | 45 | hash = Poseidon.hash([BI_1, BI_2, BI_0, BI_0, BI_0, BI_0]) 46 | expect(hash.toString()).to.eq("15336558801450556532856248569924170992202208561737609669134139141992924267169") 47 | 48 | hash = Poseidon.hash([BI_3, BI_4, BI_0, BI_0, BI_0]) 49 | expect(hash.toString()).to.eq("5811595552068139067952687508729883632420015185677766880877743348592482390548") 50 | 51 | hash = Poseidon.hash([BI_3, BI_4, BI_0, BI_0, BI_0, BI_0]) 52 | expect(hash.toString()).to.eq("12263118664590987767234828103155242843640892839966517009184493198782366909018") 53 | 54 | hash = Poseidon.hash([BI_1, BI_2, BI_3, BI_4, BI_5, BI_6]) 55 | expect(hash.toString()).to.eq("20400040500897583745843009878988256314335038853985262692600694741116813247201") 56 | }) 57 | 58 | it("Should fail when out of the field", () => { 59 | const items = [ 60 | BigInt("-1"), 61 | Poseidon.Q, 62 | Poseidon.Q + BigInt("1"), 63 | ] 64 | items.forEach(item => { 65 | try { 66 | Poseidon.hash([item]) 67 | } 68 | catch (err) { 69 | expect(err.message).to.eq("One or more inputs are out of the Poseidon field") 70 | } 71 | }) 72 | }) 73 | }) -------------------------------------------------------------------------------- /packages/signing/test/unit/sorting.ts: -------------------------------------------------------------------------------- 1 | import "mocha" // using @types/mocha 2 | import { expect } from "chai" 3 | import { addCompletionHooks } from "../mocha-hooks" 4 | 5 | import { JsonSignature } from "../../src/index" 6 | 7 | addCompletionHooks() 8 | 9 | describe("JSON normalized strings", () => { 10 | it("Should reorder JSON objects alphabetically", () => { 11 | let strA = JsonSignature.normalizedJsonString("abc") 12 | let strB = JsonSignature.normalizedJsonString("abc") 13 | expect(strA).to.equal(strB) 14 | 15 | strA = JsonSignature.normalizedJsonString(123) 16 | strB = JsonSignature.normalizedJsonString(123) 17 | expect(strA).to.equal(strB) 18 | 19 | strA = JsonSignature.normalizedJsonString({}) 20 | strB = JsonSignature.normalizedJsonString({}) 21 | expect(strA).to.equal(strB) 22 | 23 | strA = JsonSignature.normalizedJsonString({ a: 1, b: 2 }) 24 | strB = JsonSignature.normalizedJsonString({ b: 2, a: 1 }) 25 | expect(strA).to.equal(strB) 26 | 27 | strA = JsonSignature.normalizedJsonString({ a: 1, b: { c: 3, d: 4 } }) 28 | strB = JsonSignature.normalizedJsonString({ b: { d: 4, c: 3 }, a: 1 }) 29 | expect(strA).to.equal(strB) 30 | 31 | strA = JsonSignature.normalizedJsonString({ a: 1, b: [{ a: 10, m: 10, z: 10 }, { b: 11, n: 11, y: 11 }, 4, 5] }) 32 | strB = JsonSignature.normalizedJsonString({ b: [{ z: 10, m: 10, a: 10 }, { y: 11, n: 11, b: 11 }, 4, 5], a: 1 }) 33 | expect(strA).to.equal(strB) 34 | 35 | strA = JsonSignature.normalizedJsonString({ a: 1, b: [5, 4, 3, 2, 1, 0] }) 36 | strB = JsonSignature.normalizedJsonString({ b: [5, 4, 3, 2, 1, 0], a: 1 }) 37 | expect(strA).to.equal(strB) 38 | }) 39 | }) 40 | 41 | describe("JSON sorting", () => { 42 | it("Should reorder JSON objects alphabetically", () => { 43 | let strA = JSON.stringify(JsonSignature.sort("abc")) 44 | let strB = JSON.stringify(JsonSignature.sort("abc")) 45 | expect(strA).to.equal(strB) 46 | 47 | strA = JSON.stringify(JsonSignature.sort(123)) 48 | strB = JSON.stringify(JsonSignature.sort(123)) 49 | expect(strA).to.equal(strB) 50 | 51 | strA = JSON.stringify(JsonSignature.sort({})) 52 | strB = JSON.stringify(JsonSignature.sort({})) 53 | expect(strA).to.equal(strB) 54 | 55 | strA = JSON.stringify(JsonSignature.sort({ a: 1, b: 2 })) 56 | strB = JSON.stringify(JsonSignature.sort({ b: 2, a: 1 })) 57 | expect(strA).to.equal(strB) 58 | 59 | strA = JSON.stringify(JsonSignature.sort({ a: 1, b: { c: 3, d: 4 } })) 60 | strB = JSON.stringify(JsonSignature.sort({ b: { d: 4, c: 3 }, a: 1 })) 61 | expect(strA).to.equal(strB) 62 | 63 | strA = JSON.stringify(JsonSignature.sort({ a: 1, b: [{ a: 10, m: 10, z: 10 }, { b: 11, n: 11, y: 11 }, 4, 5] })) 64 | strB = JSON.stringify(JsonSignature.sort({ b: [{ z: 10, m: 10, a: 10 }, { y: 11, n: 11, b: 11 }, 4, 5], a: 1 })) 65 | expect(strA).to.equal(strB) 66 | 67 | strA = JSON.stringify(JsonSignature.sort({ a: 1, b: [5, 4, 3, 2, 1, 0] })) 68 | strB = JSON.stringify(JsonSignature.sort({ b: [5, 4, 3, 2, 1, 0], a: 1 })) 69 | expect(strA).to.equal(strB) 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /example/signed-erc20/index.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert" 2 | import { readFileSync, writeFileSync } from "fs" 3 | import { ProcessMetadata } from "@vocdoni/data-models" 4 | import { ProcessState, VotingApi } from "@vocdoni/voting" 5 | import { Wallet } from "@ethersproject/wallet" 6 | import { getConfig } from "./config" 7 | import { connectGateways } from "./net" 8 | import { getAccounts, TestAccount } from "./census" 9 | import { waitUntilPresent, waitUntilStarted } from "./util" 10 | import { checkVoteResults, launchNewVote, submitVotes } from "./voting" 11 | 12 | const config = getConfig() 13 | 14 | async function main() { 15 | let processId: string 16 | let processState: ProcessState 17 | let processMetadata: ProcessMetadata 18 | let accounts: TestAccount[] 19 | 20 | // Connect to a GW 21 | const gwPool = await connectGateways() 22 | accounts = getAccounts() 23 | const entityWallet = new Wallet(accounts[0].privateKey).connect(gwPool.provider) 24 | console.log("Entity ID", entityWallet.address) 25 | 26 | if (config.readExistingProcess) { 27 | console.log("Reading process metadata") 28 | const procInfo: { processId: string, processMetadata: ProcessMetadata } = JSON.parse(readFileSync(config.processInfoFilePath).toString()) 29 | processId = procInfo.processId 30 | processMetadata = procInfo.processMetadata 31 | 32 | await waitUntilPresent(processId, gwPool) 33 | 34 | processState = await VotingApi.getProcessState(processId, gwPool) 35 | 36 | assert(processId) 37 | assert(processMetadata) 38 | } 39 | else { 40 | // Create a new voting process 41 | const result = await launchNewVote(entityWallet, gwPool) 42 | processId = result.processId 43 | processMetadata = result.processMetadata 44 | processState = result.processState 45 | 46 | assert(processId) 47 | assert(processState) 48 | assert(processMetadata) 49 | writeFileSync(config.processInfoFilePath, JSON.stringify({ processId, processMetadata }, null, 2)) 50 | } 51 | 52 | console.log("- Entity Addr", processState.entityId) 53 | console.log("- Process ID", processId) 54 | console.log("- Process start block", processState.startBlock) 55 | console.log("- Process end block", processState.endBlock) 56 | console.log("- Process merkle root", processState.censusRoot) 57 | console.log("- Process merkle tree", processState.censusURI) 58 | console.log("-", accounts.length, "accounts on the census") 59 | 60 | await waitUntilStarted(processId, processState.startBlock, gwPool) 61 | 62 | await submitVotes(processId, processState, processMetadata, accounts, gwPool) 63 | 64 | await checkVoteResults(processId, gwPool) 65 | } 66 | 67 | ///////////////////////////////////////////////////////////////////////////// 68 | // MAIN 69 | ///////////////////////////////////////////////////////////////////////////// 70 | 71 | main() 72 | .then(() => { 73 | console.log("Done") 74 | process.exit(0) 75 | }) 76 | .catch(err => { 77 | console.error(err) 78 | process.exit(1) 79 | }) 80 | -------------------------------------------------------------------------------- /example/signed-erc20-signal/index.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert" 2 | import { readFileSync, writeFileSync } from "fs" 3 | import { ProcessMetadata } from "@vocdoni/data-models" 4 | import { ProcessState, VotingApi } from "@vocdoni/voting" 5 | import { Wallet } from "@ethersproject/wallet" 6 | import { getConfig } from "./config" 7 | import { connectGateways } from "./net" 8 | import { getAccounts, TestAccount } from "./census" 9 | import { waitUntilPresent, waitUntilStarted } from "./util" 10 | import { checkVoteResults, launchNewVote, submitVotes } from "./voting" 11 | 12 | const config = getConfig() 13 | 14 | async function main() { 15 | let processId: string 16 | let processState: ProcessState 17 | let processMetadata: ProcessMetadata 18 | let accounts: TestAccount[] 19 | 20 | // Connect to a GW 21 | const gwPool = await connectGateways() 22 | accounts = getAccounts() 23 | const entityWallet = new Wallet(accounts[0].privateKey).connect(gwPool.provider) 24 | console.log("Entity ID", entityWallet.address) 25 | 26 | if (config.readExistingProcess) { 27 | console.log("Reading process metadata") 28 | const procInfo: { processId: string, processMetadata: ProcessMetadata } = JSON.parse(readFileSync(config.processInfoFilePath).toString()) 29 | processId = procInfo.processId 30 | processMetadata = procInfo.processMetadata 31 | 32 | await waitUntilPresent(processId, gwPool) 33 | 34 | processState = await VotingApi.getProcessState(processId, gwPool) 35 | 36 | assert(processId) 37 | assert(processMetadata) 38 | } 39 | else { 40 | // Create a new voting process 41 | const result = await launchNewVote(entityWallet, gwPool) 42 | processId = result.processId 43 | processMetadata = result.processMetadata 44 | processState = result.processState 45 | 46 | assert(processId) 47 | assert(processState) 48 | assert(processMetadata) 49 | writeFileSync(config.processInfoFilePath, JSON.stringify({ processId, processMetadata }, null, 2)) 50 | } 51 | 52 | console.log("- Entity Addr", processState.entityId) 53 | console.log("- Process ID", processId) 54 | console.log("- Process start block", processState.startBlock) 55 | console.log("- Process end block", processState.endBlock) 56 | console.log("- Process merkle root", processState.censusRoot) 57 | console.log("- Process merkle tree", processState.censusURI) 58 | console.log("-", accounts.length, "accounts on the census") 59 | 60 | await waitUntilStarted(processId, processState.startBlock, gwPool) 61 | 62 | await submitVotes(processId, processState, processMetadata, accounts, gwPool) 63 | 64 | await checkVoteResults(processId, gwPool) 65 | } 66 | 67 | ///////////////////////////////////////////////////////////////////////////// 68 | // MAIN 69 | ///////////////////////////////////////////////////////////////////////////// 70 | 71 | main() 72 | .then(() => { 73 | console.log("Done") 74 | process.exit(0) 75 | }) 76 | .catch(err => { 77 | console.error(err) 78 | process.exit(1) 79 | }) 80 | -------------------------------------------------------------------------------- /packages/data-models/src/json-feed.ts: -------------------------------------------------------------------------------- 1 | // This file provides: 2 | // - Typescript type definitions for JsonFeed objects 3 | // - Metadata JSON validation checker 4 | // - A metadata JSON template 5 | 6 | import { object, array, string, bool } from "yup" 7 | export { JsonFeedTemplate } from "./templates/json-feed" 8 | 9 | /////////////////////////////////////////////////////////////////////////////// 10 | // VALIDATION 11 | /////////////////////////////////////////////////////////////////////////////// 12 | 13 | /** 14 | * Asserts that the given JSON Feed is valid. 15 | * Throws an exception if it is not. 16 | */ 17 | export function checkValidJsonFeed(jsonFeed: JsonFeed) { 18 | if (typeof jsonFeed != "object") throw new Error("The metadata must be a JSON object") 19 | 20 | try { 21 | jsonFeedSchema.validateSync(jsonFeed) 22 | return jsonFeedSchema.cast(jsonFeed) as JsonFeed 23 | } 24 | catch (err) { 25 | if (Array.isArray(err.errors)) throw new Error("ValidationError: " + err.errors.join(", ")) 26 | throw err 27 | } 28 | } 29 | 30 | // MAIN ENTITY SCHEMA 31 | 32 | const jsonFeedSchema = object({ 33 | version: string(), 34 | title: string(), 35 | home_page_url: string().optional(), 36 | description: string().optional(), 37 | feed_url: string().optional(), 38 | icon: string().optional(), 39 | favicon: string().optional(), 40 | expired: bool(), 41 | 42 | items: array().of( 43 | object({ 44 | id: string().optional(), 45 | title: string(), 46 | summary: string().optional(), 47 | content_text: string(), 48 | content_html: string(), 49 | url: string().optional(), 50 | image: string().optional(), 51 | tags: array().of(string()).optional(), 52 | date_published: string(), 53 | date_modified: string(), 54 | author: object({ 55 | name: string().optional(), 56 | url: string().optional(), 57 | }).optional(), 58 | }) 59 | ) 60 | }).unknown(true) // allow deprecated or unknown fields beyond the required ones 61 | 62 | /////////////////////////////////////////////////////////////////////////////// 63 | // TYPE DEFINITIONS 64 | /////////////////////////////////////////////////////////////////////////////// 65 | 66 | /** 67 | * JSON Feed. Intended to be stored on IPFS or similar. 68 | * More info: https://jsonfeed.org/version/1 69 | */ 70 | export interface JsonFeed { 71 | version: string, 72 | title: string, 73 | home_page_url?: string, 74 | description?: string, 75 | feed_url?: string, 76 | icon?: string, 77 | favicon?: string, 78 | expired: boolean, 79 | 80 | items: JsonFeedPost[] 81 | } 82 | 83 | export interface JsonFeedPost { 84 | id?: string, 85 | title: string, 86 | summary?: string, 87 | content_text?: string, 88 | content_html?: string, 89 | url?: string, 90 | image?: string, 91 | tags?: string[], 92 | date_published: string, 93 | date_modified: string, 94 | author?: { 95 | name?: string, 96 | url: string 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /example/signed-off-chain/census.ts: -------------------------------------------------------------------------------- 1 | import { utils, Wallet } from "ethers" 2 | import * as assert from "assert" 3 | import { CensusOffChain, CensusOffChainApi } from "@vocdoni/census" 4 | import { getConfig } from "./config" 5 | import { IGatewayClient } from "@vocdoni/client" 6 | 7 | const config = getConfig() 8 | 9 | export type TestAccount = { 10 | idx: number, 11 | mnemonic: string 12 | privateKey: string 13 | publicKey: string 14 | publicKeyEncoded: string 15 | } 16 | 17 | export function createWallets(amount: number) { 18 | console.log("Creating", amount, "wallets") 19 | const accounts = [] 20 | for (let i = 0; i < amount; i++) { 21 | if (i % 50 == 0) process.stdout.write("Wallet " + i + " ; ") 22 | const wallet = Wallet.createRandom() 23 | accounts.push({ 24 | idx: i, 25 | mnemonic: wallet.mnemonic.phrase, 26 | privateKey: wallet.privateKey, 27 | publicKey: utils.computePublicKey(wallet.publicKey, true), 28 | publicKeyEncoded: CensusOffChain.Public.encodePublicKey(wallet.publicKey) 29 | // address: wallet.address 30 | }) 31 | } 32 | 33 | console.log() 34 | return accounts 35 | } 36 | 37 | export async function generatePublicCensusFromAccounts(accounts: TestAccount[], entityWallet: Wallet, gwPool: IGatewayClient) { 38 | // Create new census 39 | console.log("Creating a new census") 40 | 41 | const censusIdSuffix = require("crypto").createHash('sha256').update("" + Date.now()).digest().toString("hex") 42 | const claimList: { key: string, value?: string }[] = accounts.map(account => ({ key: account.publicKeyEncoded, value: "" })) 43 | const managerPublicKeys = [utils.computePublicKey(entityWallet.publicKey, true)] 44 | 45 | if (config.stopOnError) { 46 | assert(censusIdSuffix.length == 64) 47 | assert(Array.isArray(claimList)) 48 | assert(claimList.length == config.numAccounts) 49 | assert(Array.isArray(managerPublicKeys)) 50 | assert(managerPublicKeys.length == 1) 51 | } 52 | 53 | // Adding claims 54 | console.log("Registering the new census to the Census Service") 55 | 56 | const { censusId } = await CensusOffChainApi.addCensus(censusIdSuffix, managerPublicKeys, entityWallet, gwPool) 57 | 58 | console.log("Adding", claimList.length, "claims") 59 | const { invalidClaims, censusRoot } = await CensusOffChainApi.addClaimBulk(censusId, claimList, entityWallet, gwPool) 60 | 61 | if (invalidClaims.length > 0) throw new Error("Census Service invalid claims count is " + invalidClaims.length) 62 | 63 | // Publish the census 64 | console.log("Publishing the new census") 65 | const censusUri = await CensusOffChainApi.publishCensus(censusId, entityWallet, gwPool) 66 | 67 | // Check that the census is published 68 | const censusSize = await CensusOffChainApi.getSize(censusId, gwPool) 69 | if (config.stopOnError) { 70 | assert(typeof censusSize == "number") 71 | assert(censusSize == claimList.length) 72 | } 73 | 74 | // Return the census ID / Merkle Root 75 | return { 76 | censusUri, 77 | censusRoot 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /example/anonymous-off-chain/census.ts: -------------------------------------------------------------------------------- 1 | import { utils, Wallet } from "ethers" 2 | import * as assert from "assert" 3 | import { CensusOffChain, CensusOffChainApi } from "@vocdoni/census" 4 | import { getConfig } from "./config" 5 | import { IGatewayClient } from "@vocdoni/client" 6 | 7 | const config = getConfig() 8 | 9 | export type TestAccount = { 10 | idx: number, 11 | mnemonic: string 12 | privateKey: string 13 | // publicKey: string 14 | publicKeyEncoded: string 15 | /** Snark friendly secret key */ 16 | secretKey: bigint 17 | } 18 | 19 | export function createWallets(amount: number) { 20 | console.log("Creating", amount, "wallets") 21 | const accounts = [] 22 | for (let i = 0; i < amount; i++) { 23 | if (i % 50 == 0) process.stdout.write("Wallet " + i + " ; ") 24 | const wallet = Wallet.createRandom() 25 | accounts.push({ 26 | idx: i, 27 | mnemonic: wallet.mnemonic.phrase, 28 | privateKey: wallet.privateKey, 29 | // publicKey: utils.computePublicKey(wallet.publicKey, true), 30 | publicKeyEncoded: CensusOffChain.Public.encodePublicKey(wallet.publicKey) 31 | // address: wallet.address 32 | }) 33 | } 34 | 35 | console.log() 36 | return accounts 37 | } 38 | 39 | export async function generatePublicCensusFromAccounts(accounts: TestAccount[], entityWallet: Wallet, gwPool: IGatewayClient) { 40 | // Create new census 41 | console.log("Creating a new census") 42 | 43 | const censusIdSuffix = require("crypto").createHash('sha256').update("" + Date.now()).digest().toString("hex") 44 | const claimList: { key: string, value?: string }[] = accounts.map(account => ({ key: account.publicKeyEncoded, value: "" })) 45 | const managerPublicKeys = [utils.computePublicKey(entityWallet.publicKey, true)] 46 | 47 | if (config.stopOnError) { 48 | assert(censusIdSuffix.length == 64) 49 | assert(Array.isArray(claimList)) 50 | assert(claimList.length == config.numAccounts) 51 | assert(Array.isArray(managerPublicKeys)) 52 | assert(managerPublicKeys.length == 1) 53 | } 54 | 55 | // Adding claims 56 | console.log("Registering the new census to the Census Service") 57 | 58 | const { censusId } = await CensusOffChainApi.addCensus(censusIdSuffix, managerPublicKeys, entityWallet, gwPool) 59 | 60 | console.log("Adding", claimList.length, "claims") 61 | const { invalidClaims, censusRoot } = await CensusOffChainApi.addClaimBulk(censusId, claimList, entityWallet, gwPool) 62 | 63 | if (invalidClaims.length > 0) throw new Error("Census Service invalid claims count is " + invalidClaims.length) 64 | 65 | // Publish the census 66 | console.log("Publishing the new census") 67 | const censusUri = await CensusOffChainApi.publishCensus(censusId, entityWallet, gwPool) 68 | 69 | // Check that the census is published 70 | const censusSize = await CensusOffChainApi.getSize(censusId, gwPool) 71 | if (config.stopOnError) { 72 | assert(typeof censusSize == "number") 73 | assert(censusSize == claimList.length) 74 | } 75 | 76 | // Return the census ID / Merkle Root 77 | return { 78 | censusUri, 79 | censusRoot 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /example/signed-blind/index.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert" 2 | import { readFileSync, writeFileSync } from "fs" 3 | import { ProcessMetadata } from "@vocdoni/data-models" 4 | import { VotingApi } from "@vocdoni/voting" 5 | import { Wallet } from "@ethersproject/wallet" 6 | import { ProcessContractParameters } from "@vocdoni/contract-wrappers" 7 | import { getConfig } from "./config" 8 | import { connectGateways } from "./net" 9 | import { ensureEntityMetadata } from "./entity" 10 | import { waitUntilPresent, waitUntilStarted } from "./util" 11 | import { checkVoteResults, launchNewVote, submitVotes } from "./voting" 12 | 13 | const config = getConfig() 14 | 15 | async function main() { 16 | let processId: string 17 | let processParams: ProcessContractParameters 18 | let processMetadata: ProcessMetadata 19 | 20 | // Connect to a GW 21 | const gwPool = await connectGateways() 22 | const entityWallet = Wallet.fromMnemonic(config.mnemonic, config.ethPath).connect(gwPool.provider) 23 | 24 | console.log("Entity ID", entityWallet.address) 25 | await ensureEntityMetadata(entityWallet, gwPool) 26 | 27 | if (config.readExistingProcess) { 28 | console.log("Reading process metadata") 29 | const procInfo: { processId: string, processMetadata: ProcessMetadata } = JSON.parse(readFileSync(config.processInfoFilePath).toString()) 30 | processId = procInfo.processId 31 | processMetadata = procInfo.processMetadata 32 | 33 | processParams = await VotingApi.getProcessContractParameters(processId, gwPool) 34 | 35 | assert(processId) 36 | assert(processMetadata) 37 | } 38 | else { 39 | // Create a new voting process 40 | const result = await launchNewVote(config.cspPublicKey, config.cspUri, entityWallet, gwPool) 41 | processId = result.processId 42 | processParams = result.processParams 43 | processMetadata = result.processMetadata 44 | assert(processId) 45 | assert(processParams) 46 | assert(processMetadata) 47 | writeFileSync(config.processInfoFilePath, JSON.stringify({ processId, processMetadata }, null, 2)) 48 | } 49 | 50 | await waitUntilPresent(processId, gwPool) 51 | 52 | console.log("- Entity Addr", processParams.entityAddress) 53 | console.log("- Process ID", processId) 54 | console.log("- Process start block", processParams.startBlock) 55 | console.log("- Process end block", processParams.startBlock + processParams.blockCount) 56 | console.log("- Process merkle root", processParams.censusRoot) 57 | console.log("- Process merkle tree", processParams.censusUri) 58 | 59 | await waitUntilStarted(processId, processParams.startBlock, gwPool) 60 | 61 | await submitVotes(processId, processParams, processMetadata, gwPool) 62 | 63 | await checkVoteResults(processId, processParams, entityWallet, gwPool) 64 | } 65 | 66 | ///////////////////////////////////////////////////////////////////////////// 67 | // MAIN 68 | ///////////////////////////////////////////////////////////////////////////// 69 | 70 | main() 71 | .then(() => { 72 | console.log("Done") 73 | process.exit(0) 74 | }) 75 | .catch(err => { 76 | console.error(err) 77 | process.exit(1) 78 | }) 79 | -------------------------------------------------------------------------------- /example/other/ethers-js.ts: -------------------------------------------------------------------------------- 1 | import * as ethers from "ethers" 2 | import ganache from "ganache-cli" 3 | import { compressPublicKey, expandPublicKey } from "../../dist" 4 | 5 | const config = { 6 | MNEMONIC: "..." 7 | } 8 | 9 | const { abi: entityResolverAbi, bytecode: entityResolverByteCode } = require("../build/entity-resolver.json") 10 | const { abi: votingProcessAbi, bytecode: votingProcessByteCode } = require("../build/process.json") 11 | 12 | async function main() { 13 | // local blockchain with prefinded accounts 14 | const provider = new ethers.providers.Web3Provider(ganache.provider({ 15 | mnemonic: config.MNEMONIC 16 | })) 17 | // const provider = new ethers.providers.JsonRpcProvider(config.GATEWAY_URL) 18 | const wallet1 = ethers.Wallet.fromMnemonic(config.MNEMONIC).connect(provider) 19 | 20 | // const privateKey = ethers.Wallet.fromMnemonic(config.MNEMONIC).privateKey 21 | // const wallet2 = new ethers.Wallet(privateKey, provider) 22 | // const wallet2 = ethers.Wallet.fromMnemonic(config.MNEMONIC).connect(provider) 23 | 24 | // deploy 25 | const resolverFactory = new ethers.ContractFactory(entityResolverAbi, entityResolverByteCode, wallet1) 26 | const processFactory = new ethers.ContractFactory(votingProcessAbi, votingProcessByteCode, wallet1) 27 | 28 | const resolverInstance1 = await resolverFactory.deploy() 29 | console.log("Resolver deployed at", resolverInstance1.address) 30 | 31 | const processInstance1 = await processFactory.deploy() 32 | console.log("Process deployed at", processInstance1.address) 33 | 34 | // attach 35 | const resolverAddress = resolverInstance1.address 36 | const processAddress = processInstance1.address 37 | const resolverInstance2 = new ethers.Contract(resolverAddress, entityResolverAbi, wallet1) 38 | const processInstance2 = new ethers.Contract(processAddress, votingProcessAbi, wallet1) 39 | 40 | // use 41 | const address = await wallet1.getAddress() 42 | 43 | const processId = await processInstance2.getProcessId(address, 0) 44 | console.log("PROCESS ID", processId) 45 | 46 | testPublicKey() 47 | 48 | console.log("DONE") 49 | } 50 | 51 | function testPublicKey() { 52 | // https://docs.ethers.io/ethers.js/html/api-advanced.html#cryptographic-operations 53 | const privateKey = ethers.Wallet.fromMnemonic(config.MNEMONIC).privateKey 54 | const signingKey = new ethers.utils.SigningKey(privateKey) 55 | 56 | let compressedPublicKey = compressPublicKey(signingKey.publicKey) 57 | let uncompressedPublicKey = /*expandPublicKey*/(signingKey.publicKey) 58 | 59 | console.log("Compressed public key:", compressedPublicKey) 60 | // "0x026655feed4d214c261e0a6b554395596f1f1476a77d999560e5a8df9b8a1a3515" 61 | 62 | console.log("Uncompressed public key:", uncompressedPublicKey) 63 | // "0x046655feed4d214c261e0a6b554395596f1f1476a77d999560e5a8df9b8a1a35" + 64 | // "15217e88dd05e938efdd71b2cce322bf01da96cd42087b236e8f5043157a9c068e" 65 | 66 | let address = ethers.utils.computeAddress(signingKey.publicKey) 67 | 68 | console.log('Address: ' + address) 69 | // "Address: 0x14791697260E4c9A71f18484C9f997B308e59325" 70 | } 71 | 72 | main() 73 | .then(() => process.exit()) 74 | .catch(err => console.error(err)) 75 | --------------------------------------------------------------------------------