Git Product home page Git Product logo

d-voting's Introduction

Global
GitHub contributors GitHub release (latest SemVer)
Blockchain
Go Reference
WEB

D-Voting

D-Voting is an e-voting platform based on the Dela blockchain. It uses state-of-the-art protocols that guarantee privacy of votes and a fully decentralized process. This project was born in early 2021 and has been iteratively implemented by EPFL students under the supervision of DEDIS members.

⚠️ This project is still under development and should not be used for real forms.

Main properties of the system are the following:

No single point of failure - The system is supported by a decentralized network of blockchain nodes, making no single party able to break the system without compromising a Byzantine threshold of nodes. Additionally, side-protocols always distribute trust among nodes: The distributed key generation protocol (DKG) ensures that a threshold of honest node is needed to decrypt ballots, and the shuffling protocol needs at least one honest node to ensure privacy of voters. Only the identification and authorization mechanism make use of a central authority, but can accommodate to other solutions.

Privacy - Ballots are cast on the client side using a safely-held distributed key-pair. The private key cannot not be revealed without coercing a threshold of nodes, and voters can retrieve the public key on any node. Ballots are decrypted only once a cryptographic process ensured that cast ballots cannot be linked to the original voter.

Transparency/Verifiability/Auditability - The whole voting process is recorded on the blockchain and signed by a threshold of blockchain nodes. Anyone can read and verify the log of events stored on the blockchain. Malicious behavior can be detected, voters can check that ballots are cast as intended, and auditors can witness the voting process.

🧩 Global architecture

The project has 4 main high-level components:

Proxy - A proxy offers the mean for an external actor such as a website to interact with a blockchain node. It is a component of the blockchain node that exposes HTTP endpoints for external entities to send commands to the node. The proxy is notably used by the web clients to use the voting system.

Web frontend - The web frontend is a web app built with React. It offers a view for end-users to use the D-Voting system. The app is meant to be used by voters and admins. Admins can perform administrative tasks such as creating an form, closing it, or revealing the results. Depending on the task, the web frontend will directly send HTTP requests to the proxy of a blockchain node, or to the web-backend.

Web backend - The web backend handles authentication and authorization. Some requests that need specific authorization are relayed from the web-frontend to the web-backend. The web backend checks the requests and signs messages before relaying them to the blockchain node, which trusts the web-backend. The web-backend has a local database to store configuration data such as authorizations. Admins use the web-frontend to perform updates.

Blockchain node - A blockchain node is the wide definition of the program that runs on a host and participate in the voting logic. The blockchain node is built on top of Dela with an additional d-voting smart contract, proxy, and two services: DKG and verifiable Shuffling. The blockchain node is more accurately a subsystem, as it wraps many other components. Blockchain nodes communicate through gRPC with the minogrpc network overlay. We sometimes refer to the blockchain node simply as a "node".

The following component diagrams summarizes the interaction between those high-level components:

Global component diagram

You can find more information about the architecture on the documentation website.

Workflow

A form follows a specific workflow to ensure privacy of votes. Once an form is created and open, there are 4 main steps from the cast of a ballot to getting the result of the form:

1) Create ballot The voter gets the shared public key and encrypts locally its ballot. The shared public key can be retrieved on any node and is associated to a private key that is distributed among the nodes. This process is done on the client's browser using the web-frontend.

2) Cast ballot The voter submits its encrypted ballot as a transaction to one of the blockchain node. This operation is relayed by the web-backend which verifies that the voters has the right to vote. If successful, the encrypted ballot is stored on the blockchain. At this stage each encrypted ballot is associated to its voter on the blockchain.

3) Shuffle ballots Once the form is closed by an admin, ballots are shuffled to ensure privacy of voters. This operation is done by a threshold of node that each perform their own shuffling. Each shuffling guarantees the integrity of ballots while re-encrypting and changing the order of ballots. At this stage encrypted ballots cannot be linked back to their voters.

4) Reveal ballots Once ballots have been shuffled, they are decrypted and revealed. This operation is done only if the previous step is correctly executed. The decryption is done by a threshold of nodes that must each provide a contribution to achieve the decryption. Once done, the result of the form is stored on the blockchain.

For a more formal and in-depth overview of the workflow, see the documentation

Smart contract

A smart contract is a piece of code that runs on a blockchain. It defines a set of operations that act on a global state (think of it as a database) and can be triggered with transactions. What makes a smart contract special is that its executions depends on a consensus among blockchain nodes where operations are successful only if a consensus is reached. Additionally, transactions and their results are permanently recorded and signed on an append-only ledger, making any operations on the blockchain transparent and permanent.

In the D-Voting system a single D-Voting smart contract handles the forms. The smart contract ensures that forms follow a correct workflow to guarantees its desirable properties such as privacy. For example, the smart contract won't allow ballots to be decrypted if they haven't been previously shuffled by a threshold of nodes.

Services

Apart from executing smart contracts, blockchain nodes need additional side services to support a form. Side services can read from the global state and send transactions to write to it via the D-Voting smart contract. They are used to perform specific protocol executions not directly related to blockchain protocols such as the distributed key generation (DKG) and verifiable shuffling protocols.

Distributed Key Generation (DKG)

The DKG service allows the creation of a distributed key-pair among multiple participants. Data encrypted with the key-pair can only be decrypted with the contribution of a threshold of participants. This makes it convenient to distribute trust on encrypted data. In the D-Voting project we use the Pedersen [1] version of DKG.

The DKG service needs to be setup at the beginning of each new form, because we want each form to have its own key-pair. Doing the setup requires two steps: 1) Initialization and 2) Setup. The initialization creates new RPC endpoints on each node, which they can use to communicate with each other. The second step, the setup, must be executed on one of the node. The setup step starts the DKG protocol and generates the key-pair. Once done, the D-Voting smart contract can be called to open the form, which will retrieve the DKG public key and save it on the smart contract.

Verifiable shuffling

The shuffling service ensures that encrypted votes can not be linked to the user who cast them. Once the service is setup, each node can perform what we call a "shuffling step". A shuffling step re-orders an array of elements such that the integrity of the elements is guarantee (i.e no elements have been modified, added, or removed), but one can't trace how elements have been re-ordered.

In D-Voting we use the Neff [2] implementation of verifiable shuffling. Once a form is closed, an admin can trigger the shuffling steps from the nodes. During this phase, every node performs a shuffling on the current list of encrypted ballots and tries to submit it to the D-Voting smart contract. The smart contract will accept only one shuffling step per block in the blockchain. Nodes re-try to shuffle the ballots, using the latest shuffled list in the blockchain, until the result of their shuffling has been committed to the blockchain or a threshold of nodes successfully submitted their own shuffling results.

πŸ“ Folders structure


.
β”œβ”€β”€ cli    
β”‚   β”œβ”€β”€ cosipbftcontroller  Custom initialization of the blockchain node
β”‚   β”œβ”€β”€ dvoting             Build the node CLI
β”‚   └── postinstall         Custom node CLI setup
β”œβ”€β”€ contracts           
β”‚   └── evoting             D-Voting smart contract
β”‚       └── controller      CLI commands for the smart contract
β”œβ”€β”€ deb-package             Debian package for deployment
β”œβ”€β”€ docs                    Documentation 
β”œβ”€β”€ integration             Integration tests
β”œβ”€β”€ internal                Internal packages: testing, tooling, tracing
β”œβ”€β”€ metrics             
β”‚   └── controller          CLI commands for Prometheus
β”œβ”€β”€ proxy                   Defines and implements HTTP handlers for the REST API
β”œβ”€β”€ services
β”‚   β”œβ”€β”€ dkg  
β”‚   β”‚   └── pedersen        Implementation of the DKG service
β”‚   └── shuffle   
β”‚       └── neff            Implementation of the shuffle service
└── web
    β”œβ”€β”€ backend
    β”‚   └── src             Sources of the web backend (express.js server)
    └── frontend
        └── src             Sources of the web frontend (react app)

πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» Contributors

Period Contributors(s) Activities Links
Spring 2021 Students: Anas Ibrahim, Vincent Parodi
Supervisor: NoΓ©mien Kocher
Initial implementation of the smart contract and services Report
Spring 2021 Student: Sarah Antille
Supervisor: NoΓ©mien Kocher
Initial implementation of the web frontend in react Report
Fall 2021 Students: Auguste Baum, Emilien Duc
Supervisor: NoΓ©mien Kocher
Adds a flexible form structure. Improves robustness and security. Report, Presentation
Fall 2021 Students: Ambroise Borbely
Supervisor: NoΓ©mien Kocher
Adds authentication and authorization mechanism on the frontend. Report
Spring 2022 Students: Guanyu Zhang, Igowa Giovanni
Supervisor: NoΓ©mien Kocher
Assistant: Emilien Duc
Improves production-readiness: deploy a test pipeline and analyze the system's robustness. Report, Presentation
Spring 2022 Students: Badr Larhdir, Capucine Berger
Supervisor: NoΓ©mien Kocher
Major iteration over the frontend - design and functionalities: implements a flexible form form, nodes setup, and result page. Report, Presentation
Fall 2022 Students: Amine Benaziz, Albert Troussard
Supervisors: NoΓ©mien Kocher, Pierluca Borso
Assistant: Emilien Duc
Improves production-readiness: implement a new transaction system, improves the setup script, analyze performance and robustness with improved testing. Report, Presentation (common)
Fall 2022 Students: Ahmed Elalamy, Ghita Tagemouati, Khadija Tagemouati
Supervisor: NoΓ©mien Kocher
Implements a policy-based authentication system, i18n of forms, individual results, and other minor improvement on the usability. Report, Presentation (common)
Fall 2022 Students: Chen Chang Lew
Supervisors: NoΓ©mien Kocher, Pierluca Borso, Simone Colombo
Threat modelling of the system, security audit: identification of threats and technical debts, design of a vote verifiability feature. Report, Security audit, Presentation (common)

βš™οΈ Setup

1: Install Go (at least 1.19).

2: Install the crypto utility from Dela:

git clone https://github.com/dedis/dela.git
cd dela/cli/crypto
go install

Go will install the binaries in $GOPATH/bin, so be sure this it is correctly added to you path (like with export PATH=$PATH:/Users/david/go/bin).

3: Install tmux

4: The authorization are stored in a postgres database. We use Docker-compose to define it easily and allow us to start and stop all the services with a single command. You will then need to install docker compose using the command :

sudo snap install docker

And finish the installation by following the steps depending on your OS.

Setup a simple system with 5 nodes (Linux and MacOS)

If you are using Windows and cannot use tmux, you need to follow the instructions in this section.

1: Only for the first time

cd web/backend
npm install
cp config.env.template config.env
cd ../frontend
npm install
cd ../..

2: In a new window, you will need to start the database:

cd d-voting/web/backend/src
sudo docker-compose up

If you want to stop the services you can use the following command:

sudo docker-compose down

If you want to have a way to check the database you can install pgAdmin.

3: Then run the following script to start and setup the nodes and the web server:

cd d-voting
./runSystems.sh -n 5

This will run 8 terminal sessions. You can navigate by hitting CTRL+B and then S. Use the arrows to select a window.

4: Stop nodes If you want to stop the system, you can use the following command:

(If you forgot, this will be done automatically when you start a new system)

./kill_test.sh

5: Troubleshoot

If while running

./runSystems.sh -n 5

You get this error:

Error: listen EADDRINUSE: address already in use :::5000

then in the file runSystems.sh, replace the line:

tmux send-keys -t $s:{end} "cd web/backend && npm start" C-m

with

tmux send-keys -t $s:{end} "cd web/backend && PORT=4000 npm start" C-m
#or any other available port

And in the web/frontend/src/setupProxy.js file, change :

target: 'http://localhost:5000',

with

target: 'http://localhost:4000',

Setup a simple system with 3 nodes (Windows)

In three different terminal sessions, from the root folder:

pk=adbacd10fdb9822c71025d6d00092b8a4abb5ebcb673d28d863f7c7c5adaddf3

LLVL=info dvoting --config /tmp/node1 start --postinstall \
  --promaddr :9100 --proxyaddr :9080 --proxykey $pk --listen tcp://0.0.0.0:2001 --public //localhost:2001

LLVL=info dvoting --config /tmp/node2 start --postinstall \
  --promaddr :9101 --proxyaddr :9081 --proxykey $pk --listen tcp://0.0.0.0:2002 --public //localhost:2002

LLVL=info dvoting --config /tmp/node3 start --postinstall \
  --promaddr :9102 --proxyaddr :9082 --proxykey $pk --listen tcp://0.0.0.0:2003 --public //localhost:2003

Then you should be able to run the setup script:

./setup.sh

With this other script using tmux you can choose the number of nodes that you want to set up:

./setupnNode.sh -n 3

This script will setup the nodes and services. If you restart do not forget to remove the old state:

rm -rf /tmp/node{1,2,3}

3: Launch the web backend

From a new terminal session, run:

cd web/backend
# if this is the first time, run `npm install` and `cp config.env.template config.env` first
npm start

4: Launch the web frontend

From a new terminal session, run:

cd web/frontend
# if this is the first time, run `npm install` first
REACT_APP_PROXY=http://localhost:9081 REACT_APP_NOMOCK=on npm start

Note that you need to be on EPFL's network to login with Tequila. Additionally, once logged with Tequila, update the redirect URL and replace dvoting-dev.dedis.ch with localhost. Once logged, you can create an form.

Testing

Run the scenario test

If nodes are running and setup.sh or ./runSystem.sh -n 3 --backend false --frontend false (for this test you don't want the user interface so the web components are not needed) has been called, you can run a test scenario:

sk=28912721dfd507e198b31602fb67824856eb5a674c021d49fdccbe52f0234409
LLVL=info dvoting --config /tmp/node1 e-voting scenarioTest --secretkey $sk

You can also run scenario_test.go, by running in the integration folder this command:

NNODES=3 go test -v scenario_test.go

For reference, here is a hex-encoded kyber Ed25519 keypair:

Public key: adbacd10fdb9822c71025d6d00092b8a4abb5ebcb673d28d863f7c7c5adaddf3

Secret key: 28912721dfd507e198b31602fb67824856eb5a674c021d49fdccbe52f0234409

Use the frontend

See README in web/.

Debian deployment

A package registry with debian packages is available at http://apt.dedis.ch. To install a package run the following:

echo "deb http://apt.dedis.ch/ squeeze main" >> /etc/apt/sources.list
wget -q -O- http://apt.dedis.ch/dvoting-release.pgp | sudo apt-key add -
sudo apt update
sudo apt install dedis-dvoting

Metrics

A d-Voting node exposes Prometheus metrics. You can start an HTTP server that serves those metrics with:

./dvoting --config /tmp/node1 metrics start --addr 127.0.0.1:9100 --path /metrics

Build info can be added to the binary with the ldflags, at build time. Infos are stored on variables in the root mod.go. For example:

versionFlag="github.com/dedis/d-voting.Version=`git describe --tags`"
timeFlag="github.com/dedis/d-voting.BuildTime=`date +'%d/%m/%y_%H:%M'`"

go build -ldflags="-X $versionFlag -X $timeFlag" ./cli/dvoting

Note that make build will do that for you.


This project has received funding from the European Union's Horizon 2020 research and innovation programme under grant agreement No 825377.

d-voting's People

Contributors

aelalamy42 avatar alberttkt avatar ambor1011 avatar augustebaum avatar badrlarhdir avatar chenchanglew avatar cmsigrist avatar dependabot[bot] avatar emduc avatar ghita2002 avatar giogio21 avatar gnarula avatar ineiti avatar jbgyz avatar jbsv avatar khadija21102 avatar lanterno avatar mamine2207 avatar nkcr avatar pascalinde avatar pierluca avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

d-voting's Issues

Define DKG error code

The DKG return the status Failed when the user tries to setup a node that is already Setup. Currently, the front-end checks during the polling the content of the error message and ignores the error if it contains the string "setup() was already called, only one call is allowed" (marking the Promise as resolved with the status Setup).
An error code, corresponding to this case should be defined instead.

Web backend Initializing nodes

When a request to initialize a node is sent, the web backend must redirect it to the correct (i.e the provided) proxy address.

Web backend: database for the proxies' addresses

The web backend needs to have a database containing the mapping between the IP address of a node and the IP address of its proxy.
When the frontend makes a GET request on /api/elections/ElectionID/proxies, the web backend must return the mapping for each node participating in the given election.
It should also be possible to update this mapping by making a POST request (on the same endpoint ?) and giving the new node and proxy addresses.

THREAT - ShuffleThreshold shouldn’t be used as the nbrSubmissions threshold

Scenario

In general we define that ShuffleThreshold > f, while DKGThreshold > 2f. This is because we only need a shuffler to shuffle 1 time more than the malicious node (f). And for computePubshares, we will need to have more than 2f to protect against the byzantine node.
However, the current system only has β€œShuffleThreshold” which didn’t differentiate between β€œshuffle” and β€œcompute pubshares”. Which will cause problems in the future if we tighten the shuffle threshold.

Source

For checking if we have shuffled enough, we can use shuffleThreshold
contracts/evoting/evoting.go line 396~399

    // in case we have enough shuffled ballots, we update the status
    if len(form.ShuffleInstances) >= form.ShuffleThreshold {
        form.Status = types.ShuffledBallots
        PromFormStatus.WithLabelValues(form.FormID).Set(float64(form.Status))
    }

But for checking
contracts/evoting/evoting.go line 583~586

  if nbrSubmissions >= form.ShuffleThreshold {
       form.Status = types.PubSharesSubmitted
       PromFormStatus.WithLabelValues(form.FormID).Set(float64(form.Status))
   }

Breaking Property

Integrity

Risk

CVSS Score: 6.6/10

Mitigation

We can create a new threshold β€œDKGThreshold” for combine Pubshares and only use β€œShuffleThreshold” for shuffler related function.

Adds an authorization mechanism

Hi,

As a matter of scalability, I was thinking it made sense to offer the possibility to give a list of scipers who are allowed to vote in a given election. Since the nodes rely on the web backend's signature, it is possible to include it as a requirement for the web backend to sign the tx (is it desirable?).
Similarly, it could be a good idea that the list of admins who can act on the election is given when the election is created (rather than having all admins being able to act on all elections).

Bruteforce ballot re-encryption allows ballot de-anonimization

The cryptographic function used to encrypt ballots is deterministic, ie. two encryptions of the same ballot will be equal, and the key is public. Therefore using a decrypted ballot it's possible for an attacker to re-encrypt it and see who submitted it. This is a problem only for votations with either few participants or where ballots are mostly unique (eg. with a text question)

Possible mititgations:

  • Use a random seed when encrypting the ballot that would not be shared during decryption (could use the new padding of ballots)

`memcoin proxy start` is not idempotent

To reproduce:

  1. In the project root, run start_test.sh.
  2. In the pane not reserved for the running nodes, run all the commands in setup.sh until
memcoin --config /tmp/node1 proxy start --clientaddr 127.0.0.1:8081
  1. Run this command now. Everything should work.
  2. Run this command again. Node 1 panics with the following stack trace:
<datetime> PNC /path/to/go/pkg/mod/go.dedis.ch/[email protected]/mino/proxy/http/mod.go:108 >
failed to create conn '127.0.0.1:8081': listen tcp 127.0.0.1:8081: bind: address already in use role="http proxy"
panic: failed to create conn '127.0.0.1:8081': listen tcp 127.0.0.1:8081: bind: address already in use

goroutine 438 [running]:
github.com/rs/zerolog.(*Logger).Panic.func1({0xc0000b0d20, 0x0})
        /path/to/go/pkg/mod/github.com/rs/[email protected]/log.go:342 +0x2d
github.com/rs/zerolog.(*Event).msg(0xc0004a6c00, {0xc0000b0d20, 0x5f})
        /path/to/go/pkg/mod/github.com/rs/[email protected]/event.go:147 +0x2b8
github.com/rs/zerolog.(*Event).Msgf(0xc0004a6c00, {0x1738dc3, 0xc00038a3d0}, {0xc000063f98, 0xc00038a270, 0xc00038a3a8})
        /path/to/go/pkg/mod/github.com/rs/[email protected]/event.go:127 +0x4e
go.dedis.ch/dela/mino/proxy/http.(*HTTP).Listen(0xc0000aa3f0)
        /path/to/go/pkg/mod/go.dedis.ch/[email protected]/mino/proxy/http/mod.go:108 +0x3be
created by go.dedis.ch/dela/mino/proxy/http/controller.startAction.Execute
        /path/to/go/pkg/mod/go.dedis.ch/[email protected]/mino/proxy/http/controller/action.go:28 +0xf4
	
panic: failed to create conn '127.0.0.1:8081': listen tcp 127.0.0.1:8081: bind: address already in use

goroutine 282 [running]:
github.com/rs/zerolog.(*Logger).Panic.func1({0xc0000430e0, 0x0})
        /path/to/go/pkg/mod/github.com/rs/[email protected]/log.go:342 +0x2d
github.com/rs/zerolog.(*Event).msg(0xc000032fc0, {0xc0000430e0, 0x5f})
        /path/to/go/pkg/mod/github.com/rs/[email protected]/event.go:147 +0x2b8
github.com/rs/zerolog.(*Event).Msgf(0xc000032fc0, {0x171d6be, 0xc00003e4d0}, {0xc00009ef98, 0x0, 0xc00003e468})
        /path/to/go/pkg/mod/github.com/rs/[email protected]/event.go:127 +0x4e
go.dedis.ch/dela/mino/proxy/http.(*HTTP).Listen(0xc0001d6240)
        /path/to/go/pkg/mod/go.dedis.ch/[email protected]/mino/proxy/http/mod.go:108 +0x3be
created by go.dedis.ch/dela/mino/proxy/http/controller.startAction.Execute
        /path/to/go/pkg/mod/go.dedis.ch/[email protected]/mino/proxy/http/controller/action.go:28 +0xf4

We'd expect it not to crash.

Technical Debt - change legacy structure "CreateForm"

The CreateForm struct no requires using AdminID anymore because all the authorizations are now handled in the web-backend. Thus the AdminID in the struct should be removed.

Where:
contracts/evoting/types/transaction.go

type CreateForm struct {
	Configuration Configuration
	AdminID       string
}

Target:

  • all the unit tests in smart-contract should pass.
  • all the functions that have CreateForm struct should able to work as before.

Verifiability - show hash of encrypted vote in election detail pages

Implementation step of Verifiability Documentation

  1. frontend: show a user the hash of their encrypted ballot. (Issue: #239)
  2. smart-contract + proxy: add users' hash of the encrypted ballot (sha256) in the form. The hash will send along with the getForm to the user. (Issue: #240)
  3. frontend: In the election info page, show a list of the voter who vote and their hash of encrypted votes got from the proxy. (This issue)

How:
On the election info page, show a list of the voter who vote and their hash of encrypted votes got from the proxy.

Where:
web/frontend/src/

Target:
Users can view their previously cast votes in the list.
Create paging and search for the user id, since we might have a lot of ballots. (Optional)

THREAT - The public/private key of the election is changed by the Adversary

Scenario

When a user wants to cast a ballot, the frontend server will request the election public key from a blockchain node. And the ballot will be encrypted using the public key.
However, if the frontend requests the public key from a compromised blockchain node, the adversary can reply with a fake public key to the user. Then it can decrypt the ballot if the user uses the public key for the encryption.

Source

β€œweb/frontend/src/pages/ballot/Show.tsx” function sendBallot
β€œpubKey” is derived from the function β€œweb/frontend/src/components/utils/useElection.tsx” to use pctx.getProxy() for the election info.

Breaking Property

Confidentiality, Integrity

Risk

CVSS Score: 5.8/10

Mitigation

Frontend receives election public keys from at least β…” of the nodes.
Frontend reports/sends alert to the D-voting community when releasing there is a different public key.

Allow ciphertexts to be bigger than 29 bytes

For now ciphertexts are bounded by Kyber to be <= than 29 bytes. It would be better to represent a cipher text by chunks of 29 bytes. To make sure comparisons between encrypted and decrypted ballots is impossible, the size of all ballots should still be the same

Technical Debt - Duplicate function getForm()

Under dkg folder, struct Actor and struct dkgHandler both have the function getForm() and all the implementations are the same. Thus we can remove one of the functions or call one function from another to reduce duplication.

Where:
services/dkg/pedersen/mod.go getForm()
services/dkg/pedersen/handler.go getForm()
services/shuffle/neff/mod.go getForm()

Target:
all the unit tests in dkg should pass.
all the unit tests in shuffling should pass.
all the functions should be able to work as before

Technical Debt - check lenAddrs before sending getPeerKey

Before sending the request, we can check for the length of the recipient to decide whether we want to call sendmsg function.

Where:
services/dkg/pedersen/mod.go setup()

Target:
all the unit tests in dkg should pass.
all the functions should be able to work as before.

Setting up the nodes should work if the majority of the nodes were successfully initialized

For the moment, the DKG service requires that all the nodes of the roster be initialized to successfully perform the setup. But the system should still work even if some node are not responding (strictly less than 1/3). The following files / block of codes would need to be modified to achieve this:

The DKG service times out with an error if not all the nodes are initialized:

for i := 0; i < lenAddrs; i++ {

The status of an election is set to Initial if one of the node is not initialized:

// TODO: can be modified such that if the majority of the node are

The election does not got to the status Node Initialized if one of the node fails to initialize

// TODO: can be modified such that if the majority of the node are

If some nodes fail to initialize and consensus cannot be reached because of this, only these nodes should retry the initialization (rather than all of them)

// TODO: can be modified such that if the majority of the node are

🚦 Road to V1

This issue to gathers the list of elements that need to be done to release a V1 of D-Voting.

Features

WIP

  • hard BE MVP - Adds a per-election authorization mechanism.

todo

  • hard FE - Provide a ticket when a ballot is cast to provide cast-as-intended functionality
  • medium FE MVP - Show status of the nodes.
  • medium FE - Makes the proxy configuration easier
  • easy FE - Allows a question title to be empty, and don't display it if this is the case

done

  • medium FE - Implements i18n on the election fields
  • medium FE - Form result: add a result view "by ballot" instead of "by question"

Improvements

WIP


todo

  • hard FE - Use the "strict" mode on Typescript
  • hard FE - Use a state manager such as Redux, or the React Context API
  • medium FE MVP - Update error handling: display better errors
  • medium FE MVP - Login: redirect to previous page
  • medium FE MVP - Adds version numbers in web-backend headers
  • medium - Use verifiable DKG
  • easy FE - Adds a confirmation when a user leaves the casting vote page, or saves it on the local storage

done

  • easy FE - Display error in the DKG statuses, if any @nkcr
  • medium FE - Adds version numbers on the web frontend footer
  • easy FE - Update the favicon @aelalamy42
  • easy FE - Update the web-frontend title
  • easy FE - Rename "election" to "form" @Ghita2002
  • easy FE - Adds authors to the about page @aelalamy42
  • easy FE - Translate the interface in french and german
  • medium FE - Adds a "hint" field for the questions

Bugs

WIP


todo

  • hard MVP - DKG #26. Add a ZKP on the encrypted ballot ?
  • medium MVP - DKG DKG state is not correctly stored. If a node restarts it looses its state. (-> merge with Dela's DKG?)
  • medium MVP - DKG Make DKG setup resilient to nodes not responding (-> merge with Dela's DKG?)
  • medium MVP - #22
  • easy FE MVP - #139
  • easy FE MVP - Cast vote button is not reactive and can lead to multiple cast
  • easy FE MVP - #175

done

  • hard - Fetching election becomes very slow (should have been fixed here)
  • easy - #165 - @nkcr

Production-readiness

WIP


todo

  • hard - Improve coverage
  • hard - Load test the system
  • hard - Adds integrations tests
  • medium - Improve documentation
  • medium - Implement logs monitoring of the nodes

done

  • hard - Adds continuous delivery: build deb package - @nkcr

    • Deploy a .deb registry (aptly)
    • Add Github action to build deb
    • Add Github action to upload new deb to registry
  • hard - Implements ready-to-use docker(-compose) files to deploy a node

`memcoin dkg registerHandlers` is not idempotent

To reproduce:

  1. In the project root, run start_test.sh.
  2. In the pane not reserved for the running nodes, run all the commands in setup.sh until
memcoin --config /tmp/node1 dkg registerHandlers
  1. Run this command now. Everything should work.
  2. Run this command again. Node 1 panics with the following stack trace:
panic: http: multiple registrations for /evoting/dkg/init

goroutine 428 [running]:
net/http.(*ServeMux).Handle(0xc000398700, {0x172c58a, 0x11}, {0x17ff200, 0xc000388708})
        /usr/local/Cellar/go/1.17.5/libexec/src/net/http/server.go:2441 +0x226
net/http.(*ServeMux).HandleFunc(...)
        /usr/local/Cellar/go/1.17.5/libexec/src/net/http/server.go:2478
go.dedis.ch/dela/mino/proxy/http.HTTP.RegisterHandler(...)
        /path/to/go/pkg/mod/go.dedis.ch/[email protected]/mino/proxy/http/mod.go:144
github.com/dedis/d-voting/services/dkg/pedersen/controller.(*registerHandlersAction).Execute(0x1722ac8, {{0x18076c0, 0xc00012c418}, {0x18165d8, 0xc00041ae10
}, {0x17fcda0, 0xc00030c040}})
        /path/to/EPFL/D-Voting/src/d-voting/services/dkg/pedersen/controller/action.go:355 +0x169
go.dedis.ch/dela/cli/node.(*socketDaemon).handleConn(0xc00015e8f0, {0x181c740, 0xc00030c030})
        /path/to/go/pkg/mod/go.dedis.ch/[email protected]/cli/node/daemon.go:185 +0x5e9
created by go.dedis.ch/dela/cli/node.(*socketDaemon).Listen.func2
        /path/to/go/pkg/mod/go.dedis.ch/[email protected]/cli/node/daemon.go:129 +0x78

We'd expect the node not to crash.

`memcoin e-voting registerHandlers` is not idempotent

To reproduce:

  1. In the project root, run start_test.sh.
  2. In the pane not reserved for the running nodes, run all the commands in setup.sh until
memcoin --config /tmp/node1 e-voting registerHandlers --signer private.key
  1. Run this command now. Everything should work.
  2. Run this command again. Node 1 panics with the following stack trace:
panic: http: multiple registrations for /evoting/login

goroutine 365 [running]:
net/http.(*ServeMux).Handle(0xc00007e800, {0x172a660, 0xe}, {0x17ff200, 0xc00006a750})
        /usr/local/Cellar/go/1.17.5/libexec/src/net/http/server.go:2441 +0x226
net/http.(*ServeMux).HandleFunc(...)
        /usr/local/Cellar/go/1.17.5/libexec/src/net/http/server.go:2478
go.dedis.ch/dela/mino/proxy/http.HTTP.RegisterHandler(...)
        /path/to/go/pkg/mod/go.dedis.ch/[email protected]/mino/proxy/http/mod.go:144
github.com/dedis/d-voting/contracts/evoting/controller.registerVotingProxy({0x1811068, 0xc0003a25a0}, {0x1811420, 0xc0003524c0}, 0xc0003003c0, {0x18072b0, 0xc00020c540}, {0x18072d8, 0xc000204150}, {0x1810f18, ...}, ...)
        /path/to/EPFL/D-Voting/src/d-voting/contracts/evoting/controller/http.go:74 +0x69c
github.com/dedis/d-voting/contracts/evoting/controller.(*registerAction).Execute(0x1722ac8, {{0x18076c0, 0xc00012c418}, {0x18165d8, 0xc00048eb40}, {0x17fcda0, 0xc000010080}})
        /path/to/EPFL/D-Voting/src/d-voting/contracts/evoting/controller/action.go:112 +0x616
go.dedis.ch/dela/cli/node.(*socketDaemon).handleConn(0xc00015e8f0, {0x181c740, 0xc000010070})
        /path/to/go/pkg/mod/go.dedis.ch/[email protected]/cli/node/daemon.go:185 +0x5e9
created by go.dedis.ch/dela/cli/node.(*socketDaemon).Listen.func2
        /path/to/go/pkg/mod/go.dedis.ch/[email protected]/cli/node/daemon.go:129 +0x78

We'd expect the node not to crash.

`ordering.service.GetProof` returns proofs for elections which don't exist

To reproduce:

  1. In the project root, run start_test.sh.
  2. In the recently opened tmux session, run setup.sh in the command pane (the one that doesn't have all the logs).
  3. In the pane not reserved for the running nodes, run
memcoin --config /tmp/node1 e-voting scenarioTestPart1

which creates an election. The electionID should be
e8f97378c283218c33dc74ccb120c8677b0c2d6aefa7cc84ef28652862c2f53e -- if it isn't, the result is the same. It should correspond to the only election on the chain.

  1. In the same pane, run
memcoin --config /tmp/node1 dkg init --electionID 04

You should get

command error: failed to start the RPC: election 04 does not exist
  1. In the same pane, run
memcoin --config /tmp/node1 dkg init --electionID 01

You should get no output in that pane, and see in one of the panes:

<datetime> INF services/dkg/pedersen/controller/action.go:98 >
DKG was successfully linked to election [1]

We expected the following error:

command error: failed to start the RPC: election 01 does not exist

In fact, you can try any number of election IDs; some seem to always work, e.g. those ending in 1 and those ending in 9.

Verifiability - show vote hash after casting a vote

Implementation step of Verifiability Documentation

  1. frontend: show a user the hash of their encrypted ballot. (This issue)
  2. smart-contract + proxy: add users' hash of encrypted ballot (sha256) in the form. The hash will send along with the getForm to the user. (Issue: #240)
  3. frontend: In the election info page, show a list of the voter who vote and their hash of encrypted votes got from the proxy. (Issue: #241)

How:
After a user cast a vote and send it to the backend, the frontend will pop up a message (like how you show succeed message after creating an election) showing the hash value (sha256) of the encrypted ballot to the user.
The user can later use that hash value to check if his/her ballot is cast correctly in the blockchain.

Where:
web/frontend/src/

Target:
Users can able to see the hash of the vote.
The hash will only show once.
Users can click a copy icon button to copy the hash of the vote. (optional)

Improve D-voting benchmark

The goal is to remove arbitrary sleep times in the main benchmark code and to watch that a block has been added to the chain before moving on to the next test step.

Cast an empty ballot makes shuffle impossible

There is currently no check on the ballots submitted using the CastVote() command. If a node submits an empty ballot, the problem will be spotted only during the shuffle and the nodes will never be able to proceed.

A unit test has been added in /contracts/evoting/mod_test.go which should fail but currently passes.

To spot the problem, one can replace a casted ballot in the ScenarioTest by a simple CiphterText{}

Technical Debt - verify signature before execute request

In the proxy folder, some of the functions execute part of the request before verifying the signature. This might have the potential leak with some different msg output. Although most of the errors msg and behavior are publicly accessible in other endpoints (GET) etc without requiring a signature, In general, verifying the signature before executing the request always guarantees better security.

Where:
Proxy/dkg.go -> EditDKGActor()
Proxy/election.go -> EditForm()
Proxy/election.go -> DeleteForm()
Proxy/shuffle.go -> EditShuffle()

Target:
For every request that requires verifying signature in proxy, we should always verify it first before executing the rest of the command.

THREAT - A user can vote multiple times (count as multiple votes) in an election.

Scenario

Whenever the frontend sends an encrypted ballot to the backend to sign, the backend will include the userID of the user in the ballot and sign the msg. This design will allow every user to vote at most once. However, since we are using Tequila as our login server, thus it is hard to create multiple valid users in Tequila during backend testing. A workaround was created. Backend implements a function called β€œmakeID” to create a random ID and sign it along with the encrypted ballot. This will allow a user to vote multiple times which counts as multiple votes. And this function is not yet removed until now. Thus an adversary can vote as many times as he wants to manipulate the election results.

Source

In the file β€œweb/backend/Server.ts” function app.use(β€˜/api/evoting/*’)

app.use('/api/evoting/*', (req, res) => {
. . . 
// special case for voting
  const regex = /\/api\/evoting\/elections\/.*\/vote/;
  if (req.baseUrl.match(regex)) {
    // We must set the UserID to know who this ballot is associated to. This is
    // only needed to allow users to cast multiple ballots, where only the last
    // ballot is taken into account. To preserve anonymity the web-backend could
    // translate UserIDs to another random ID.
    // bodyData.UserID = req.session.userid.toString();
    bodyData.UserID = makeid(10);
  }
 
function makeid(length: number) {
  let result = '';
  const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  const charactersLength = characters.length;
  for (let i = 0; i < length; i += 1) {
    result += characters.charAt(Math.floor(Math.random() * charactersLength));
  }
  return result;
}

Breaking Property

Availability, Auditability

Risk

CVSS score: 6.5/10

Mitigation

Remove the makeId function. If the developer prefers to keep the function for testing, we can set an env variable in config.env to determine this environment, which production environment would use User Id and the Dev environment would use Fake Id.

THREAT - Election will not be able to reveal the result if anyone submits a fake vote.

Scenario

If a ballot fails to decrypt during the decryption process, it will return an error and make the whole decryption step fail. This will make the smart contracts will not accept the transaction which means the decryption process will never succeed.

Source

contracts/evoting/evoting.go combineShares()

for j := 0; j < ballotSize; j++ {
    chunk, err := decrypt(i, j, allPubShares, form.PubsharesUnits.Indexes)
    if err != nil {
        return xerrors.Errorf("failed to decrypt (K, C): %v", err)
    }
...
}

Breaking Property

Availability

Risk

CVSS Score: 5.7/10

Mitigation

Ignore the ballot which has a decryption error, just assume it is an empty ballot during the reveal result.

Target

Add some unit-test related to decrypt an error ballot.

Technical Debt - shouldn't use fingerprint function for pseudorandomness because it is not efficient.

the shuffler and smart contract will use the ballots' fingerprints as a seed for generating pseudorandomness for the shuffled proof.
However, the fingerprint method will unmarshal all the encrypted ballots and count their hash of them which is not efficient.
We should come out with a more efficient way to calculate the seed for pseudorandomness.

Where:
services/shuffle/neff/handler.go makeTx()
contracts/evoting/evoting.go shuffleBallots()

Why:
To increase the performance of shuffling and verifying proof.

Target:
add a new function to generate seeds for pseudorandomness.
add unit-test that new function.
all unit tests and integration tests should be passed.

Technical Debt - variable name "buff, formIDBuf, formIDBuff, formID" not consistent

In the proxy folder, some of the variable names are not consistent.

Where:
proxy/shuffle.go -> EditShuffle

  • buff, err := hex.DecodeString(formID)
    proxy/dkg.go -> NewDKGActor()
  • formIDBuf, err := hex.DecodeString(req.FormID)
    proxy/election.go -> getForm()
  • formID, err := hex.DecodeString(formIDHex)

contracts/evoting/evoting.go -> getForm()

  • formIDBuff, err := hex.DecodeString(formIDHex)

Target:

  • Use the same variable name to increase the readability of the code. (formIDbuf)
  • Refactor some of the logic to reduce duplicate variable names with the same purpose.

Technical Debt - Need refactor in dkg & shuffler

Some parts of the code need to rewrite to increase its readability

Where:
services/dkg/pedersen/handler.go handleDecryptRequest() Line 505-519
services/shuffle/neff/handler.go handleStartShufflt() Line 134-148

Target:
all the unit tests in dkg should pass.
all the unit tests in shuffling should pass.
all the functions should be able to work as before.

Technical Debt - change loop and sleep to channel + ctx timeout to increase readability of code.

when waiting for the shuffler to reach its shuffle num threshold, the code loop for 10*rosterLen and sleep for roterlen /2 second each time until we received enough amount of round or loop finish. We should change this into using channel along with ctx timeout.
On the other hand, when creating watchCtx waiting times we can use a constant var at the top of the file to increase readability.

Where:
services/shuffle/neff/mod.go waitAndCheckShuffling()
services/shuffle/neff/handler.go handleStartShuffle()

Why:
Increase code readability and reliability.

Target:
all the unit tests in Shuffle should be passed.
all the functions should be able to work as before.

Technical Debt - unclear/wrong comment

In the proxy folder, some of the comments in the code are not actually describing the code correctly.

Where:
Proxy/election.go -> NewForm() line 117, comment msg said sign response but it didn’t (should change "sign the response" to "encode the response")
Proxy/election.go -> NewFormVote() line 169, comment msg said encrypt the vote but it didn't (should change this to decode ballot or unmarshal ballot)
Contracts/evoting/types/ciphervote.go line 62, should be "failed to marshal C:"

Target:
The comment should be able to fit what the code trying to achieve.

Verifiability - add users' hash of the encrypted ballot for each election.

Implementation step of Verifiability Documentation

  1. frontend: show a user the hash of their encrypted ballot. (Issue: #239)
  2. smart-contract + proxy: add users' hash of the encrypted ballot (sha256) in the form. The hash will send along with the getForm to the user. (This issue)
  3. frontend: In the election info page, show a list of the voter who vote and their hash of encrypted votes got from the proxy. (Issue: #241)

How:
Update the form struct by adding an array variable to store the hash of encrypted votes.
Update the getForm function to return the hash of encrypted votes.

Where:
contracts/evoting/types/election.go
proxy/election.go getForm()

Target:
edit the unit tests in proxy, and update the getForm unit-test.
edit the unit tests in smart-contract, update the form related unit-test

Check request method type

We should probably restrict HTTP APIs defined in contracts/evoting/controller to either POST/GET/PUT depending on the endpoint being invoked.

This might require a change in the frontend as well.

Frontend correct redirection after Action

Fixes to have correct redirection

  • When creating an election the user should be redirected to the election show page of this new election
  • When casting a vote the user should be redirected to the correct page ( show page or elections table depending on where he came from )
  • The back button of show results should redirect the user to the correct page ( show page or elections table depending on where he came from )

THREAT - Denial of Service, Dkg public key will always return false if an adversary compromise one device.

Scenario

During the node initialization phase, the master node will run dkg init and collect all the dkgPubKeys to make sure all the keys are the same or the process will fail. However, a malicious node can just always return a false key then will always let the dkg initialization process fail. And right now there is not a monitor system or log to determine which node has the different output thus we are not able to track malicious users.

Source

β€œservices/dkg/pedersen/mod.go” function setup()
The code here will check that all the dkgPubKeys returned from the nodes are the same, and will output false if any of those fails.

Breaking Property

Availability

Risk

CVSS Score: 4.4/10

Mitigation

The DKG server will proceed to the next phase once it receives more than β…” of the correct dkg pubkey.
The DKG server will also report the nodes that return fake keys and raise an alarm to notify there exist some malicious node.

Target

All unit test should have pass.
Add one unit test to test if dkg receives the false dkg public key

THREAT - Frontend create form didn't check for the maximum length of the form

Scenario

When creating a form, an operator/admin got a choice to select the β€œtext” option, however the text option maxlength doesn't have a limit, which means a malicious user can create a huge form. And this will increase the load of the node & server when they encoded the cast vote. Since all encrypted ballots should be the same length to avoid leakage, thus the frontend will pad the ballot before the encryption. Thus with a huge form created it will increase the load of the node & server to process the result even if the vote size itself is small. This will potentially be a denial of service attack.

Source

Frontend create form
image

Frontend encoded ballot

  // add padding if necessary until encodedBallot.length == ballotSize
  if (encodedBallotSize < ballotSize) {
    const padding = new ShortUniqueId({ length: ballotSize - encodedBallotSize });
    encodedBallot += padding();
  }

Breaking Property

Availability

Risk

CVSS Score: 4.5/10

Mitigation

This can be mitigated by setting a maximum size of ballot for each form.
(Note this will also need to check in the smart contract because we will not trust the request from end-user)

Target

add check-in frontend and smart contract for the maximum length of the form.
add unit-test to test related issue.

Ballot encoded from a different public key makes decryption impossible

When a casted ballot has been encrypted with a different public key, the decryption process seems to never end. One can add this ballot to the ScenarioTest to simulate this situation (after the three normal ballots have been casted) :

	// ###################################### CAST WRONG BALLOT ################

	// encrypt a ballot with a different public key:

	// TODO with current implementation, the ballot prevents the decryption from ending

	RandomStream := suite.RandomStream()
	h := suite.Scalar().Pick(RandomStream)
	wrongPubKey := suite.Point().Mul(h, nil)
	
	ballot4 := make([]types.Ciphertext, election.ChunksPerBallot())
	
	for i := 0; i < election.ChunksPerBallot(); i++ {
		// Embed the message into a curve point
		message := "Chunk" + strconv.Itoa(i)
		M := suite.Point().Embed([]byte(message), random.New())
	
		// ElGamal-encrypt the point to produce ciphertext (X,Y).
		k := suite.Scalar().Pick(random.New()) // ephemeral private key
		X := suite.Point().Mul(k, nil)         // ephemeral DH public key
		S := suite.Point().Mul(k, wrongPubKey) // ephemeral DH shared secret
		Y := S.Add(S, M)                       // message blinded with secret
	
		KMarshalled, _ := X.MarshalBinary()
		CMarshalled, _ := Y.MarshalBinary()
	
		ballot4[i] = types.Ciphertext{
			K: KMarshalled,
			C: CMarshalled,
		}
	}
	
	castVoteRequest = types.CastVoteRequest{
		ElectionID: electionID,
		UserID:     "user4",
		Ballot:     ballot4,
		Token:      token,
	}
	
	js, err = json.Marshal(castVoteRequest)
	if err != nil {
		return xerrors.Errorf("failed to set marshall types.SimpleElection : %v", err)
	}
	
	resp, err = http.Post(proxyAddr+castVoteEndpoint, "application/json", bytes.NewBuffer(js))
	if err != nil {
		return xerrors.Errorf("failed retrieve the decryption from the server: %v", err)
	}
	
	if resp.StatusCode != http.StatusOK {
		buf, _ := ioutil.ReadAll(resp.Body)
		return xerrors.Errorf("unexpected status: %s - %s", resp.Status, buf)
	}
	
	body, err = io.ReadAll(resp.Body)
	if err != nil {
		return xerrors.Errorf("failed to read the body of the response: %v", err)
	}
	dela.Logger.Info().Msg("Response body : " + string(body))
	resp.Body.Close()

	// ###################################### CAST WRONG BALLOT ################

What needs to be done is either

  • Adapt the decryption process to drop the ballots when such a situation occures so that it can keep going
  • Or find a way to make sure the ballot has been encrypted with the right public key when the tx is submitted

No proof of decryption

For now, the only requirements of the smart contract to accept the decryptBallots transaction is that it's submitted with the adminId. We should have a way to make sure the decrypted ballots of the transaction are really the result of decrypting the shuffled ballots of the previous state.

Speed up the decryption process

Right now the system decrypts ballot one at a time. This process could be much improved if instead of sending one DKG decrypt request per ballot, we send one decrypt request with all ballots.

Link DKG instances to elections

When a chain is created, a DKG service is run, and the resulting keypair is to be used for all subsequent elections run on that chain. This means that if the keypair is compromised, many elections could be affected.

Verify the identity of a shuffling instance

Right now the only check we do during the shuffling phase is if we have a threshold of correct shuffling. However, we do not verify that each shuffling comes from a different node. All the shuffling could come from a single node.

THREAT - All the election stages can be ignored by malicious node

Scenario

Every request that is signed by the backend will send to the same node as described in config.env.template. However, if the node that is set in the backend env is a malicious node, it can selectively ignore some requests from the backend. For example, it can decide to execute a vote from certain sets of users while ignoring other users' vote that is not on their list.

Source

In the file β€œweb/backend/Server.ts” function sendToDela.

function sendToDela(dataStr: string, req: express.Request, res: express.Response) {
  let payload = getPayload(dataStr);
  let uri = process.env.DELA_NODE_URL + req.baseUrl.slice(4);

And the value of process.env.DELA_NODE_URL is set to default = β€œhttp://localhost:9081/”
In β€œweb/backend/config.env.template”

Breaking Property

Availability

Risk

CVSS Score: 4.1/10

Mitigation

  1. Instead of sending it to one node server, the backend will randomly pick one node server and send it to them. If the node happened to be malicious and drop the requests. The backend will pick a new random node server and send it again. However, this might cause a long wait time from frontend.
  2. In order to mitigate the long response time from the backend we can just let the frontend check using get Election Info and then report failed at frontend pages and let the end user submit the request again. But this might cause bad user experiences because users might need to submit a request multiple times.
  3. End users can choose which node to send to.
  4. There is another way that we can solve this Threat is to redesign the system architecture, while the backend no longer sends data to the node while just being used as an authentication/authorization tool. The backend will now only sign the request from the frontend and then send the backend to the frontend and let the frontend handle the request sent to the node. However, this will introduce a new threat like a β€œreplay attack” because the end user can record the signed msg from the backend and send it over and over again. In order to solve the replay attack we might need to have a nonce or counter for every signed request and the nodes should save the nonce or counter in the Dela global state which required lots of effort to mitigate the problem

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.