diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..128fefa7d --- /dev/null +++ b/.env.example @@ -0,0 +1,22 @@ +# This is an example .env file with default passwords and private keys. +# Do not use this in production or with any public-facing ports! +BACKEND_HOST=backend # name of the 'backend' container +BACKEND_PORT=5000 # port of the 'backend' container +COMPOSE_FILE=./docker-compose/docker-compose.yml # Docker Compose configuration file to use +DATABASE_HOST=db # name of the PostgreSQL container +DATABASE_PASSWORD=Ohw0phoa # choose any PostgreSQL password +DATABASE_PORT=5432 # port of the PostgreSQL container +DATABASE_USERNAME=dvoting +DB_PATH=dvoting # LMDB database path +DELA_PROXY_URL=http://172.19.44.254:8080 # IP and port of one of the DELA containers +FRONT_END_URL=http://127.0.0.1:3000 # the automated frontend tests expect this value do not change it +NODEPORT=2000 # DELA node port +# For public-facing services and production, this key needs to be changed! +PRIVATE_KEY=6aadf480d068ac896330b726802abd0da2a5f3824f791fe8dbd4cd555e80b809 +PROXYPORT=8080 # DELA proxy port +PUBLIC_KEY=3e5fcaed4c5d79a8eccceeb087ee0a13b8f91d917ed62017a9cd28e13b228389 +REACT_APP_DEV_LOGIN=true # debugging admin login /!\ disable in production /!\ +REACT_APP_RANDOMIZE_VOTE_ID=true # randomize voter ID for debugging /!\ disable in production /!\ +REACT_APP_SCIPER_ADMIN=123456 # debugging admin ID /!\ disable in production /!\ +REACT_APP_BLOCKLIST= # comma-separad form IDs to hide from non-admin users +SESSION_SECRET=kaibaaF9 # choose any secret diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index e6aab2ac3..46f272ec8 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,7 +1,10 @@ Thank you for opening a pull request with this project, please also: * [ ] add a brief description of your changes here -* [ ] assign a reviewer +* [ ] assign the PR to yourself, or to the person(s) working on it * [ ] start in `draft` mode and `in progress` pipeline in the project (if applicable) -* [ ] once it's ready put it in the `Review` or `Ready4Merge` pipeline in the project (if applicable) and remove `draft` -* [ ] if applicable, add this PR to its related issue by one of the special keywords (https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests#linking-a-pull-request-to-an-issue) +* [ ] if applicable, add this PR to its related issue by one of the special keywords [Closing keywords](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests#linking-a-pull-request-to-an-issue) +* once it's ready + * [ ] put it in the `Review` or `Ready4Merge` pipeline in the project (if applicable) + * [ ] remove `draft` + * [ ] assign a reviewer diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 3c12b6d26..08b3739cc 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -4,45 +4,64 @@ on: push: branches: - main + pull_request: + types: [opened, synchronize, reopened, ready_for_review] jobs: build-docker: name: Build D-Voting Docker images runs-on: ubuntu-22.04 + env: + DockerTag: latest + push: ${{ (github.ref == 'refs/heads/main') && 'true' || 'false' }} + steps: - name: Checkout uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set env + run: | + git describe --tags + echo "REACT_APP_VERSION=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV + echo "REACT_APP_BUILD=$(git describe --tags)" >> $GITHUB_ENV + echo "REACT_APP_BUILD_TIME=$(date)" >> $GITHUB_ENV - name: Set up Docker Buildx id: buildx uses: docker/setup-buildx-action@v1 - name: Login to GHCR + if: ${{ env.push == 'true' }} uses: docker/login-action@v1 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Build Frontend uses: docker/build-push-action@v2 with: context: . file: Dockerfiles/Dockerfile.frontend platforms: linux/amd64 - push: true - tags: ghcr.io/c4dt/d-voting-frontend:latest + build-args: | + REACT_APP_VERSION + REACT_APP_BUILD + REACT_APP_BUILD_TIME + push: ${{ env.push }} + tags: ghcr.io/dedis/d-voting-frontend:${{ env.DockerTag }} - name: Build Backend uses: docker/build-push-action@v2 with: context: . file: Dockerfiles/Dockerfile.backend platforms: linux/amd64 - push: true - tags: ghcr.io/c4dt/d-voting-backend:latest + push: ${{ env.push }} + tags: ghcr.io/dedis/d-voting-backend:${{ env.DockerTag }} - name: Build D-Voting uses: docker/build-push-action@v2 with: context: . - target: build file: Dockerfiles/Dockerfile.dela platforms: linux/amd64 - push: true - tags: ghcr.io/c4dt/d-voting-dela:latest + push: ${{ env.push }} + tags: ghcr.io/dedis/d-voting-dela:${{ env.DockerTag }} diff --git a/.github/workflows/go_dvoting_test.yml b/.github/workflows/go_dvoting_test.yml index f98569577..f82bdbd53 100644 --- a/.github/workflows/go_dvoting_test.yml +++ b/.github/workflows/go_dvoting_test.yml @@ -12,14 +12,14 @@ jobs: name: Scenario runs-on: ubuntu-latest steps: - - name: Set up Go ^1.17 - uses: actions/setup-go@v2 + - name: Use Go 1.20 + uses: actions/setup-go@v4 with: - go-version: ^1.17 + go-version: '1.20' - name: Install crypto util from Dela run: | - git clone https://github.com/c4dt/dela.git + git clone https://github.com/dedis/dela.git cd dela go install ./cli/crypto diff --git a/.github/workflows/go_integration_tests.yml b/.github/workflows/go_integration_tests.yml index 5b0cfc9dd..07813af07 100644 --- a/.github/workflows/go_integration_tests.yml +++ b/.github/workflows/go_integration_tests.yml @@ -11,10 +11,10 @@ jobs: name: Integration test runs-on: ubuntu-latest steps: - - name: Set up Go ^1.17 - uses: actions/setup-go@v2 + - name: Use Go 1.20 + uses: actions/setup-go@v4 with: - go-version: ^1.17 + go-version: '1.20' - name: Check out code into the Go module directory uses: actions/checkout@v2 @@ -25,10 +25,10 @@ jobs: name: Test bad vote runs-on: ubuntu-latest steps: - - name: Set up Go ^1.17 - uses: actions/setup-go@v2 + - name: Use Go 1.20 + uses: actions/setup-go@v4 with: - go-version: ^1.17 + go-version: '1.20' - name: Check out code into the Go module directory uses: actions/checkout@v2 @@ -39,24 +39,29 @@ jobs: name: Test crash runs-on: ubuntu-latest steps: - - name: Set up Go ^1.17 - uses: actions/setup-go@v2 + - name: Use Go 1.20 + uses: actions/setup-go@v4 with: - go-version: ^1.17 + go-version: '1.20' - name: Check out code into the Go module directory uses: actions/checkout@v2 - name: Run the crash test - run: go test -timeout 10m -run TestCrash ./integration/... + run: | + for a in $( seq 3 ); do + echo "Testing sequence $a" + go test -timeout 10m -run TestCrash ./integration/... && exit 0 + done + exit 1 revote: name: Test revote runs-on: ubuntu-latest steps: - - name: Set up Go ^1.17 - uses: actions/setup-go@v2 + - name: Use Go 1.20 + uses: actions/setup-go@v4 with: - go-version: ^1.17 + go-version: '1.20' - name: Check out code into the Go module directory uses: actions/checkout@v2 diff --git a/.github/workflows/go_scenario_test.yml b/.github/workflows/go_scenario_test.yml index c2e609697..54027938f 100644 --- a/.github/workflows/go_scenario_test.yml +++ b/.github/workflows/go_scenario_test.yml @@ -11,14 +11,14 @@ jobs: name: Tests runs-on: ubuntu-latest steps: - - name: Set up Go ^1.17 - uses: actions/setup-go@v2 + - name: Use Go 1.20 + uses: actions/setup-go@v4 with: - go-version: ^1.17 + go-version: '1.20' - name: Install crypto util from Dela run: | - git clone https://github.com/c4dt/dela.git + git clone https://github.com/dedis/dela.git cd dela go install ./cli/crypto diff --git a/.github/workflows/go_test.yml b/.github/workflows/go_test.yml index dba5a8188..821477592 100644 --- a/.github/workflows/go_test.yml +++ b/.github/workflows/go_test.yml @@ -10,18 +10,19 @@ jobs: name: Tests runs-on: ubuntu-latest steps: - - name: Use Go >= 1.19 - uses: actions/setup-go@v3 + - name: Use Go 1.20 + uses: actions/setup-go@v4 with: - go-version: '>=1.19' + go-version: '1.20' id: go - name: Check out code into the Go module directory uses: actions/checkout@v3 - - name: Run lint - run: make lint - +# TODO: https://github.com/dedis/d-voting/issues/392 +# - name: Run lint +# run: make lint +# - name: Run vet run: make vet diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index a4b775e5f..84d119f27 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -13,10 +13,10 @@ jobs: - name: checkout uses: actions/checkout@v3 - - name: Use go - uses: actions/setup-go@v3 + - name: Use Go 1.20 + uses: actions/setup-go@v4 with: - go-version: '>=1.18' + go-version: '1.20' - name: Install fpm run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f5f3ef86..16e1cbfa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,12 +9,25 @@ Latest changes in each category go to the top ## [Unreleased] ### Added +- dev_login can change userId when clicking on the user in the upper right +- admin can now add users as voters +- New debugging variables in [local_vars.sh](./scripts/local_vars.sh) - Changelog - please use it ### Changed +- for the Dockerfiles and docker-compose.yml, `DELA_NODE_URL` has been replaced with `DELA_PROXY_URL`, + which is the more accurate name. +- the actions in package.json for the frontend changed. Both are somewhat development mode, + as the webserver is not supposed to be used in production. + - `start`: starts in plain mode + - `start-https`: starts in HTTPS mode + ### Deprecated ### Removed ### Fixed +- Proxy editing fixed: adding, modifying, deleting now works +- When fetching form and user updates, only do it when showing the activity +- Redirection when form doesn't exist and nicer error message - File formatting and errors in comments - Popup when voting and some voting translation fixes - Fixed return error when voting diff --git a/Dockerfiles/Dockerfile.dela b/Dockerfiles/Dockerfile.dela index 5535c8dbd..5aaeb0d7b 100644 --- a/Dockerfiles/Dockerfile.dela +++ b/Dockerfiles/Dockerfile.dela @@ -1,17 +1,19 @@ FROM golang:1.20.6-bookworm AS base -RUN apt-get update && apt-get install git -# make sure we're using the same head as d-voting -RUN git clone https://github.com/c4dt/dela.git -WORKDIR /go/dela/cli/crypto -RUN go install +RUN apt-get update -y && apt-get install -y git WORKDIR /go/d-voting +COPY go.mod . +COPY go.sum . +RUN go mod download COPY . . - -FROM base AS build -COPY --from=base /go/dela . -COPY --from=base /go/d-voting . +ENV GOCACHE=/root/.cache/go-build WORKDIR /go/d-voting/cli/dvoting -RUN go build -ENV PATH=/go/dela/cli/crypto:/go/d-voting/cli/dvoting:${PATH} +RUN --mount=type=cache,target="/root/.cache/go-build" go install +# make sure we're using the same head as d-voting +RUN --mount=type=cache,target="/root/.cache/go-build" cd $( go list -f '{{.Dir}}' go.dedis.ch/dela )/cli/crypto && go install + +FROM golang:1.20.6-bookworm AS build +WORKDIR /usr/local/bin +COPY --from=base /go/bin/crypto . +COPY --from=base /go/bin/dvoting . ENTRYPOINT ["/bin/bash", "-c", "dvoting --config /data/node start --postinstall --proxyaddr :$PROXYPORT --proxykey $PROXYKEY --listen tcp://0.0.0.0:2000 --public $PUBLIC_URL --routing tree --noTLS"] CMD [] diff --git a/Dockerfiles/Dockerfile.frontend b/Dockerfiles/Dockerfile.frontend index 77376ff36..46457046d 100644 --- a/Dockerfiles/Dockerfile.frontend +++ b/Dockerfiles/Dockerfile.frontend @@ -5,5 +5,12 @@ ENV REACT_APP_NOMOCK=on WORKDIR /web/frontend COPY ../web/frontend . RUN npm install +ARG REACT_APP_VERSION=unknown +ARG REACT_APP_BUILD=unknown +ARG REACT_APP_BUILD_TIME=after_2024_03 +ENV REACT_APP_VERSION=$REACT_APP_VERSION +ENV REACT_APP_BUILD=$REACT_APP_BUILD +ENV REACT_APP_BUILD_TIME=$REACT_APP_BUILD_TIME + ENTRYPOINT ["npm"] CMD ["start"] diff --git a/Makefile b/Makefile index a7eb71f25..a774d69a0 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ version=$(shell git describe --abbrev=0 --tags || echo '0.0.0') -versionFlag="github.com/c4dt/d-voting.Version=$(version)" +versionFlag="github.com/dedis/d-voting.Version=$(version)" versionFile=$(shell echo $(version) | tr . _) -timeFlag="github.com/c4dt/d-voting.BuildTime=$(shell date +'%d/%m/%y_%H:%M')" +timeFlag="github.com/dedis/d-voting.BuildTime=$(shell date +'%d/%m/%y_%H:%M')" lint: # Coding style static check. diff --git a/README.docker.md b/README.docker.md index bc66a08f9..dce48973d 100644 --- a/README.docker.md +++ b/README.docker.md @@ -8,96 +8,66 @@ The files related to the Docker environment can be found in * `Dockerfiles/` (Dockerfiles) * `scripts/` (helper scripts) -You also need to either create a `.env` file in the project's root -or point to another environment file using the `--env-file` flag -when running `docker compose`. +### Setup -The environment file needs to contain +It is recommended to use the `run_docker.sh` helper script for setting up and +tearing down the environment as it handles all the necessary intermediary steps +to have a working D-Voting application. -``` -DELA_NODE_URL=http://172.19.44.254:8080 -DATABASE_USERNAME=dvoting -DATABASE_PASSWORD=XXX # choose any PostgreSQL password -DATABASE_HOST=db -DATABASE_PORT=5432 -DB_PATH=dvoting # LMDB database path -FRONT_END_URL=http://127.0.0.1:3000 -BACKEND_HOST=backend -BACKEND_PORT=5000 -SESSION_SECRET=XXX # choose any secret -PUBLIC_KEY=XXX # public key of pre-generated key pair -PRIVATE_KEY=XXX # private key of pre-generated key pair -PROXYPORT=8080 -NODEPORT=2000 # DELA node port -``` +This script needs to be executed at the project's root. -For the `PUBLIC_KEY` and `PRIVATE_KEY`, you need to run the following commands: +To set up the environment: -```bash -cd web/backend -npm ci -npm run keygen +``` +./scripts/run_docker.sh ``` -And then copy the two lines to the `.env` file. - -There are two Docker Compose file you may use: +This will run the subcommands: -* `docker-compose/docker-compose.yml` for the preprod version, or -* `docker-compose/docker-compose.debug.yml` for the development/debugging version +- `setup` which will build the images and start the containers +- `init_dela` which will initialize the DELA network +- `local_admin` which will add local admin accounts for testing and debugging +- `local_login` which will set a local cookie that allows for interacting w/ the API via command-line +- `add_proxies` which will set up the DELA node proxies -You run +Each of these subcommands can also be run by invoking the script w/ the subcommand: ``` -export COMPOSE_FILE= +./scripts/run_docker.sh ``` -The preprod version will create an environment without any debugging tools that's as close as possible to a real environment. -It is meant to be used to test the `main` branch before deploying it to production. Use the development/debugging version -for setting up your local development environment. +/!\ The `init_dela` subcommand must only be run exactly **once**. -Run +To tear down the environment: ``` -docker compose build -docker compose up +./scripts/run_docker.sh teardown ``` -to set up the environment. - -/!\ Any subsequent `docker compose` commands must be run with `COMPOSE_FILE` being -set to the Docker Compose file that defines the current environment. - -Use +This will: -``` -docker compose down -``` +- remove the local cookie +- stop and remove the containers and their attached volumes +- remove the images -to shut off, and +/!\ This command is meant to reset your environment. If you want to stop one or more +containers, use the appropriate `docker compose` commands (see below for using the correct `docker-compose.yml`). -``` -docker compose down -v -``` +### Docker environment -to delete the volumes and reset your instance. +There are two Docker Compose file you may use: -## Post-install commands +* `docker-compose/docker-compose.yml` (recommended, default in `.env.example` and `run_docker.sh`), or +* `docker-compose/docker-compose.debug.yml`, which contains some additional debugging tools -To set up the DELA network, go to `scripts/` and run +To run `docker compose` commands w/ the right `docker-compose.yml`, you need to either run ``` -./init_dela.sh +export COMPOSE_FILE= ``` -/!\ This script uses `docker compose` as well, so make sure that the `COMPOSE_FILE` variable is -set to the right value. - -To set up the permissions, run +or ``` -docker compose exec backend npx cli addAdmin --sciper XXX -docker compose down && docker compose up -d +source .env ``` - -to add yourself as admin and clear the cached permissions. diff --git a/README.md b/README.md index 635f11808..cf4c87cc7 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,13 @@ - - + +
- + GitHub contributors - + GitHub release (latest SemVer) @@ -26,37 +26,37 @@ Blockchain - - + + - - + + - - + + - - + +
- - + + - - Go Reference + + Go Reference WEB - - + + - - + + @@ -67,7 +67,7 @@ # D-Voting **D-Voting** is an e-voting platform based on the -[Dela](https://github.com/c4dt/dela) blockchain. It uses state-of-the-art +[Dela](https://github.com/dedis/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. @@ -147,7 +147,7 @@ sometimes refer to the blockchain node simply as a "node". The following component diagrams summarizes the interaction between those high-level components: -[minogrpc]: https://github.com/c4dt/dela/tree/master/mino/minogrpc +[minogrpc]: https://github.com/dedis/dela/tree/master/mino/minogrpc ![Global component diagram](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/dedis/d-voting/main/docs/assets/component-global.puml) @@ -390,7 +390,7 @@ results. 2: Install the `crypto` utility from Dela: ```sh -git clone https://github.com/c4dt/dela.git +git clone https://github.com/dedis/dela.git cd dela/cli/crypto go install ``` @@ -601,7 +601,7 @@ Afterwards use the following commands, replace 4 by the desired nb of nodes : ./runNode.sh -n 4 -a true -d true ./setupnNode.sh -n 4 -d true -NNODES=4 KILLNODE=true go test -v -run ^TestScenario$ github.com/c4dt/d-voting/integration -count=1 +NNODES=4 KILLNODE=true go test -v -run ^TestScenario$ github.com/dedis/d-voting/integration -count=1 ``` Here we set KILLNODE=true or false to decide whether kill and restart a node @@ -650,14 +650,48 @@ 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: ```sh -versionFlag="github.com/c4dt/d-voting.Version=`git describe --tags`" -timeFlag="github.com/c4dt/d-voting.BuildTime=`date +'%d/%m/%y_%H:%M'`" +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. +# Benchmarks + +For more details, see https://github.com/c4dt/d-voting/issues/47 + +``` ++------------+-------------+--------------+ +I I 1'000 I 10'000 I ++------------+-------------+--------------+ +I CASTING I 95s I 888s I +I I 95ms/vote I 95ms/vote I ++------------+-------------+--------------+ +I Blocks I 179 I 1466 I +I I 5.6/block I 6.8/block I ++------------+-------------+--------------+ +I SHUFFLING I 56s I 542s I ++------------+-------------+--------------+ +I Blocks I 9 I 10 I ++------------+-------------+--------------+ +I DECRYPTING I 5s I 49s I ++------------+-------------+--------------+ +I Blocks I 2 I 3 I ++------------+-------------+--------------+ +I COMBINING I 1s I 47s I ++------------+-------------+--------------+ +I Blocks I 1 I 1 I ++------------+-------------+--------------+ +I DISPLAYING I <1s I 5s I ++------------+-------------+--------------+ + +``` + +- 10'000 votes + - casting: 95s, 179 blocks - 95ms/vote, 5.6 votes / block + --- diff --git a/autotest.sh b/autotest.sh index 67ef14ec1..b9872287b 100755 --- a/autotest.sh +++ b/autotest.sh @@ -52,7 +52,7 @@ do ./setupnNode.sh -n $N_NODE -d true sleep 3 # Start scenario test and keep logs - NNODES=$N_NODE go test -v -run ^TestScenario$ github.com/c4dt/d-voting/integration -count=1 | tee ./log/log/gotest.log + NNODES=$N_NODE go test -v -run ^TestScenario$ github.com/dedis/d-voting/integration -count=1 | tee ./log/log/gotest.log sleep 3 # Stop the test ./kill_test.sh diff --git a/cli/cosipbftcontroller/action.go b/cli/cosipbftcontroller/action.go index f31027197..febe68f4b 100644 --- a/cli/cosipbftcontroller/action.go +++ b/cli/cosipbftcontroller/action.go @@ -12,16 +12,16 @@ import ( "fmt" "strings" - "github.com/c4dt/dela" - "github.com/c4dt/dela/cli/node" - "github.com/c4dt/dela/core/ordering" - "github.com/c4dt/dela/core/ordering/cosipbft/authority" - "github.com/c4dt/dela/core/ordering/cosipbft/contracts/viewchange" - "github.com/c4dt/dela/core/txn" - "github.com/c4dt/dela/core/txn/pool" - "github.com/c4dt/dela/cosi" - "github.com/c4dt/dela/crypto" - "github.com/c4dt/dela/mino" + "go.dedis.ch/dela" + "go.dedis.ch/dela/cli/node" + "go.dedis.ch/dela/core/ordering" + "go.dedis.ch/dela/core/ordering/cosipbft/authority" + "go.dedis.ch/dela/core/ordering/cosipbft/contracts/viewchange" + "go.dedis.ch/dela/core/txn" + "go.dedis.ch/dela/core/txn/pool" + "go.dedis.ch/dela/cosi" + "go.dedis.ch/dela/crypto" + "go.dedis.ch/dela/mino" "golang.org/x/xerrors" ) diff --git a/cli/cosipbftcontroller/action_test.go b/cli/cosipbftcontroller/action_test.go index 46a2cf396..2d0ba484c 100644 --- a/cli/cosipbftcontroller/action_test.go +++ b/cli/cosipbftcontroller/action_test.go @@ -7,19 +7,19 @@ import ( "testing" "time" - "github.com/c4dt/d-voting/internal/testing/fake" - "github.com/c4dt/dela/cli/node" - "github.com/c4dt/dela/core/access" - "github.com/c4dt/dela/core/ordering" - "github.com/c4dt/dela/core/ordering/cosipbft/authority" - "github.com/c4dt/dela/core/txn" - "github.com/c4dt/dela/core/txn/pool" - "github.com/c4dt/dela/core/txn/pool/mem" - "github.com/c4dt/dela/core/validation" - "github.com/c4dt/dela/cosi" - "github.com/c4dt/dela/crypto" - "github.com/c4dt/dela/mino" + "github.com/dedis/d-voting/internal/testing/fake" "github.com/stretchr/testify/require" + "go.dedis.ch/dela/cli/node" + "go.dedis.ch/dela/core/access" + "go.dedis.ch/dela/core/ordering" + "go.dedis.ch/dela/core/ordering/cosipbft/authority" + "go.dedis.ch/dela/core/txn" + "go.dedis.ch/dela/core/txn/pool" + "go.dedis.ch/dela/core/txn/pool/mem" + "go.dedis.ch/dela/core/validation" + "go.dedis.ch/dela/cosi" + "go.dedis.ch/dela/crypto" + "go.dedis.ch/dela/mino" ) func TestSetupAction_Execute(t *testing.T) { @@ -94,7 +94,6 @@ func TestRosterAddAction_Execute(t *testing.T) { var p pool.Pool require.NoError(t, ctx.Injector.Resolve(&p)) - require.Equal(t, 1, p.Len()) ctx.Injector = node.NewInjector() err = action.Execute(ctx) diff --git a/cli/cosipbftcontroller/mod.go b/cli/cosipbftcontroller/mod.go index 036a4f1c4..1b09ac720 100644 --- a/cli/cosipbftcontroller/mod.go +++ b/cli/cosipbftcontroller/mod.go @@ -8,30 +8,30 @@ import ( "path/filepath" "time" - "github.com/c4dt/dela/contracts/value" - "github.com/c4dt/dela/crypto" - - "github.com/c4dt/dela/cli" - "github.com/c4dt/dela/cli/node" - "github.com/c4dt/dela/core/access/darc" - "github.com/c4dt/dela/core/execution/native" - "github.com/c4dt/dela/core/ordering" - "github.com/c4dt/dela/core/ordering/cosipbft" - "github.com/c4dt/dela/core/ordering/cosipbft/authority" - "github.com/c4dt/dela/core/ordering/cosipbft/blockstore" - "github.com/c4dt/dela/core/ordering/cosipbft/types" - "github.com/c4dt/dela/core/store/hashtree/binprefix" - "github.com/c4dt/dela/core/store/kv" - "github.com/c4dt/dela/core/txn/pool" - poolimpl "github.com/c4dt/dela/core/txn/pool/gossip" - "github.com/c4dt/dela/core/txn/signed" - "github.com/c4dt/dela/core/validation/simple" - "github.com/c4dt/dela/cosi/threshold" - "github.com/c4dt/dela/crypto/bls" - "github.com/c4dt/dela/crypto/loader" - "github.com/c4dt/dela/mino" - "github.com/c4dt/dela/mino/gossip" - "github.com/c4dt/dela/serde/json" + "go.dedis.ch/dela/contracts/value" + "go.dedis.ch/dela/crypto" + + "go.dedis.ch/dela/cli" + "go.dedis.ch/dela/cli/node" + "go.dedis.ch/dela/core/access/darc" + "go.dedis.ch/dela/core/execution/native" + "go.dedis.ch/dela/core/ordering" + "go.dedis.ch/dela/core/ordering/cosipbft" + "go.dedis.ch/dela/core/ordering/cosipbft/authority" + "go.dedis.ch/dela/core/ordering/cosipbft/blockstore" + "go.dedis.ch/dela/core/ordering/cosipbft/types" + "go.dedis.ch/dela/core/store/hashtree/binprefix" + "go.dedis.ch/dela/core/store/kv" + "go.dedis.ch/dela/core/txn/pool" + poolimpl "go.dedis.ch/dela/core/txn/pool/gossip" + "go.dedis.ch/dela/core/txn/signed" + "go.dedis.ch/dela/core/validation/simple" + "go.dedis.ch/dela/cosi/threshold" + "go.dedis.ch/dela/crypto/bls" + "go.dedis.ch/dela/crypto/loader" + "go.dedis.ch/dela/mino" + "go.dedis.ch/dela/mino/gossip" + "go.dedis.ch/dela/serde/json" "golang.org/x/xerrors" ) @@ -40,9 +40,6 @@ const ( errInjector = "injector: %v" ) -// valueAccessKey is the access key used for the value contract. -var valueAccessKey = [32]byte{2} - func blsSigner() encoding.BinaryMarshaler { return bls.NewSigner() } @@ -130,7 +127,7 @@ func (m miniController) OnStart(flags cli.Flags, inj node.Injector) error { rosterFac := authority.NewFactory(onet.GetAddressFactory(), cosi.GetPublicKeyFactory()) cosipbft.RegisterRosterContract(exec, rosterFac, access) - value.RegisterContract(exec, value.NewContract(valueAccessKey[:], access)) + value.RegisterContract(exec, value.NewContract(access)) txFac := signed.NewTransactionFactory() vs := simple.NewService(exec, txFac) diff --git a/cli/cosipbftcontroller/mod_test.go b/cli/cosipbftcontroller/mod_test.go index d3cf0cb6e..ac9e3525e 100644 --- a/cli/cosipbftcontroller/mod_test.go +++ b/cli/cosipbftcontroller/mod_test.go @@ -6,15 +6,15 @@ import ( "path/filepath" "testing" - "github.com/c4dt/d-voting/services/dkg" - "github.com/c4dt/dela/core/ordering" - - "github.com/c4dt/d-voting/internal/testing/fake" - "github.com/c4dt/dela/cli" - "github.com/c4dt/dela/cli/node" - "github.com/c4dt/dela/core/store/kv" - "github.com/c4dt/dela/core/txn/pool" + "github.com/dedis/d-voting/services/dkg" + "go.dedis.ch/dela/core/ordering" + + "github.com/dedis/d-voting/internal/testing/fake" "github.com/stretchr/testify/require" + "go.dedis.ch/dela/cli" + "go.dedis.ch/dela/cli/node" + "go.dedis.ch/dela/core/store/kv" + "go.dedis.ch/dela/core/txn/pool" ) func TestMinimal_SetCommands(t *testing.T) { diff --git a/cli/dvoting/mod.go b/cli/dvoting/mod.go index 57a96706b..f562ed5bf 100644 --- a/cli/dvoting/mod.go +++ b/cli/dvoting/mod.go @@ -30,25 +30,25 @@ import ( "io" "os" - dkg "github.com/c4dt/d-voting/services/dkg/pedersen/controller" - "github.com/c4dt/d-voting/services/dkg/pedersen/json" - shuffle "github.com/c4dt/d-voting/services/shuffle/neff/controller" + dkg "github.com/dedis/d-voting/services/dkg/pedersen/controller" + "github.com/dedis/d-voting/services/dkg/pedersen/json" + shuffle "github.com/dedis/d-voting/services/shuffle/neff/controller" - cosipbft "github.com/c4dt/d-voting/cli/cosipbftcontroller" - "github.com/c4dt/d-voting/cli/postinstall" - evoting "github.com/c4dt/d-voting/contracts/evoting/controller" - metrics "github.com/c4dt/d-voting/metrics/controller" - "github.com/c4dt/dela/cli/node" - access "github.com/c4dt/dela/contracts/access/controller" - db "github.com/c4dt/dela/core/store/kv/controller" - pool "github.com/c4dt/dela/core/txn/pool/controller" - signed "github.com/c4dt/dela/core/txn/signed/controller" - mino "github.com/c4dt/dela/mino/minogrpc/controller" - proxy "github.com/c4dt/dela/mino/proxy/http/controller" + cosipbft "github.com/dedis/d-voting/cli/cosipbftcontroller" + "github.com/dedis/d-voting/cli/postinstall" + evoting "github.com/dedis/d-voting/contracts/evoting/controller" + metrics "github.com/dedis/d-voting/metrics/controller" + "go.dedis.ch/dela/cli/node" + access "go.dedis.ch/dela/contracts/access/controller" + db "go.dedis.ch/dela/core/store/kv/controller" + pool "go.dedis.ch/dela/core/txn/pool/controller" + signed "go.dedis.ch/dela/core/txn/signed/controller" + mino "go.dedis.ch/dela/mino/minogrpc/controller" + proxy "go.dedis.ch/dela/mino/proxy/http/controller" - _ "github.com/c4dt/d-voting/services/shuffle/neff/json" + _ "github.com/dedis/d-voting/services/shuffle/neff/json" - gapi "github.com/c4dt/dela-apps/gapi/controller" + gapi "go.dedis.ch/dela-apps/gapi/controller" ) func main() { diff --git a/cli/dvoting/mod_test.go b/cli/dvoting/mod_test.go index 5c1f8c2bf..08919672f 100644 --- a/cli/dvoting/mod_test.go +++ b/cli/dvoting/mod_test.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "encoding/base64" "fmt" "io" "net" @@ -14,6 +15,7 @@ import ( "time" "github.com/stretchr/testify/require" + "go.dedis.ch/kyber/v3/pairing/bn256" ) func TestDvoting_Main(t *testing.T) { @@ -61,7 +63,7 @@ func TestDvoting_Scenario_SetupAndTransactions(t *testing.T) { shareCert(t, node3, node1, "//127.0.0.1:2111") shareCert(t, node5, node1, "//127.0.0.1:2111") - // Setup the chain with nodes 1 and 2. + // Set up the chain with nodes 1 and 2. args := append(append( append( []string{os.Args[0], "--config", node1, "ordering", "setup"}, @@ -85,7 +87,26 @@ func TestDvoting_Scenario_SetupAndTransactions(t *testing.T) { err = run(args) require.NoError(t, err) + // Add the certificate and push two new blocks to make sure node4 is + // fully participating + shareCert(t, node4, node1, "//127.0.0.1:2111") + publicKey, err := bn256.NewSuiteG2().Point().MarshalBinary() + require.NoError(t, err) + publicKeyHex := base64.StdEncoding.EncodeToString(publicKey) + argsAccess := []string{ + os.Args[0], + "--config", node1, "access", "add", + "--identity", publicKeyHex, + } + for i := 0; i < 2; i++ { + err = runWithCfg(argsAccess, config{}) + require.NoError(t, err) + } + // Add node 5 which should be participating. + // This makes sure that node 4 is actually participating and caught up. + // If node 4 is not participating, there would be too many faulty nodes + // after adding node 5. args = append([]string{ os.Args[0], "--config", node1, "ordering", "roster", "add", @@ -96,10 +117,10 @@ func TestDvoting_Scenario_SetupAndTransactions(t *testing.T) { err = run(args) require.NoError(t, err) - // Run a few transactions. - for i := 0; i < 5; i++ { - err = runWithCfg(args, config{}) - require.EqualError(t, err, "command error: transaction refused: duplicate in roster: 127.0.0.1:2115") + // Run 2 new transactions + for i := 0; i < 2; i++ { + err = runWithCfg(argsAccess, config{}) + require.NoError(t, err) } // Test a timeout waiting for a transaction. @@ -155,7 +176,7 @@ func TestDvoting_Scenario_RestartNode(t *testing.T) { ) err = run(args) - require.EqualError(t, err, "command error: transaction refused: duplicate in roster: 127.0.0.1:2210") + require.EqualError(t, err, "command error: transaction refused: duplicate in roster: grpcs://127.0.0.1:2210") } // ----------------------------------------------------------------------------- diff --git a/cli/postinstall/mod.go b/cli/postinstall/mod.go index 66b6f0d12..560e57d40 100644 --- a/cli/postinstall/mod.go +++ b/cli/postinstall/mod.go @@ -5,15 +5,15 @@ import ( "path/filepath" "time" - evoting "github.com/c4dt/d-voting/contracts/evoting/controller" - prom "github.com/c4dt/d-voting/metrics/controller" - dkg "github.com/c4dt/d-voting/services/dkg/pedersen/controller" - neff "github.com/c4dt/d-voting/services/shuffle/neff/controller" - "github.com/c4dt/dela" - "github.com/c4dt/dela/cli" - "github.com/c4dt/dela/cli/node" - "github.com/c4dt/dela/mino/proxy" - "github.com/c4dt/dela/mino/proxy/http" + evoting "github.com/dedis/d-voting/contracts/evoting/controller" + prom "github.com/dedis/d-voting/metrics/controller" + dkg "github.com/dedis/d-voting/services/dkg/pedersen/controller" + neff "github.com/dedis/d-voting/services/shuffle/neff/controller" + "go.dedis.ch/dela" + "go.dedis.ch/dela/cli" + "go.dedis.ch/dela/cli/node" + "go.dedis.ch/dela/mino/proxy" + "go.dedis.ch/dela/mino/proxy/http" "golang.org/x/xerrors" ) diff --git a/contracts/evoting/controller/action.go b/contracts/evoting/controller/action.go index 19c537ca5..7073e16c9 100644 --- a/contracts/evoting/controller/action.go +++ b/contracts/evoting/controller/action.go @@ -17,30 +17,30 @@ import ( "go.dedis.ch/kyber/v3/sign/schnorr" "go.dedis.ch/kyber/v3/suites" - "github.com/c4dt/d-voting/contracts/evoting/types" - "github.com/c4dt/d-voting/internal/testing/fake" - eproxy "github.com/c4dt/d-voting/proxy" - "github.com/c4dt/d-voting/proxy/txnmanager" - ptypes "github.com/c4dt/d-voting/proxy/types" - "github.com/c4dt/d-voting/services/dkg" - "github.com/c4dt/d-voting/services/shuffle" - "github.com/c4dt/dela" - "github.com/c4dt/dela/cli/node" - "github.com/c4dt/dela/core/ordering" - "github.com/c4dt/dela/core/ordering/cosipbft/authority" - "github.com/c4dt/dela/core/ordering/cosipbft/blockstore" - "github.com/c4dt/dela/core/txn" - "github.com/c4dt/dela/core/txn/pool" - "github.com/c4dt/dela/core/txn/signed" - "github.com/c4dt/dela/core/validation" - "github.com/c4dt/dela/crypto" - "github.com/c4dt/dela/crypto/bls" - "github.com/c4dt/dela/crypto/loader" - "github.com/c4dt/dela/mino" - "github.com/c4dt/dela/mino/proxy" - "github.com/c4dt/dela/serde" - sjson "github.com/c4dt/dela/serde/json" + "github.com/dedis/d-voting/contracts/evoting/types" + "github.com/dedis/d-voting/internal/testing/fake" + eproxy "github.com/dedis/d-voting/proxy" + "github.com/dedis/d-voting/proxy/txnmanager" + ptypes "github.com/dedis/d-voting/proxy/types" + "github.com/dedis/d-voting/services/dkg" + "github.com/dedis/d-voting/services/shuffle" "github.com/gorilla/mux" + "go.dedis.ch/dela" + "go.dedis.ch/dela/cli/node" + "go.dedis.ch/dela/core/ordering" + "go.dedis.ch/dela/core/ordering/cosipbft/authority" + "go.dedis.ch/dela/core/ordering/cosipbft/blockstore" + "go.dedis.ch/dela/core/txn" + "go.dedis.ch/dela/core/txn/pool" + "go.dedis.ch/dela/core/txn/signed" + "go.dedis.ch/dela/core/validation" + "go.dedis.ch/dela/crypto" + "go.dedis.ch/dela/crypto/bls" + "go.dedis.ch/dela/crypto/loader" + "go.dedis.ch/dela/mino" + "go.dedis.ch/dela/mino/proxy" + "go.dedis.ch/dela/serde" + sjson "go.dedis.ch/dela/serde/json" "golang.org/x/xerrors" ) @@ -303,7 +303,7 @@ func (a *scenarioTestAction) Execute(ctx node.Context) error { fmt.Fprintln(ctx.Out, "Get form") - form, err = getForm(serdecontext, formFac, formID, service) + form, err = types.FormFromStore(serdecontext, formFac, formID, service.GetStore()) if err != nil { return xerrors.Errorf(getFormErr, err) } @@ -420,12 +420,16 @@ func (a *scenarioTestAction) Execute(ctx node.Context) error { dela.Logger.Info().Msg(responseBody + respBody) - form, err = getForm(serdecontext, formFac, formID, service) + form, err = types.FormFromStore(serdecontext, formFac, formID, service.GetStore()) if err != nil { return xerrors.Errorf(getFormErr, err) } - encryptedBallots := form.Suffragia.Ciphervotes + suff, err := form.Suffragia(serdecontext, service.GetStore()) + if err != nil { + return xerrors.Errorf(getFormErr, err) + } + encryptedBallots := suff.Ciphervotes dela.Logger.Info().Msg("Length encrypted ballots: " + strconv.Itoa(len(encryptedBallots))) dela.Logger.Info().Msgf("Ballot of user1: %s", encryptedBallots[0]) dela.Logger.Info().Msgf("Ballot of user2: %s", encryptedBallots[1]) @@ -440,12 +444,12 @@ func (a *scenarioTestAction) Execute(ctx node.Context) error { return xerrors.Errorf("failed to close form: %v", err) } - form, err = getForm(serdecontext, formFac, formID, service) + form, err = types.FormFromStore(serdecontext, formFac, formID, service.GetStore()) if err != nil { return xerrors.Errorf(getFormErr, err) } - dela.Logger.Info().Msg("Title of the form: " + form.Configuration.MainTitle) + dela.Logger.Info().Msg("Title of the form: " + form.Configuration.Title.En) dela.Logger.Info().Msg("Status of the form: " + strconv.Itoa(int(form.Status))) // ###################################### SHUFFLE BALLOTS ################## @@ -478,14 +482,18 @@ func (a *scenarioTestAction) Execute(ctx node.Context) error { // time.Sleep(20 * time.Second) - form, err = getForm(serdecontext, formFac, formID, service) + form, err = types.FormFromStore(serdecontext, formFac, formID, service.GetStore()) if err != nil { return xerrors.Errorf(getFormErr, err) } logFormStatus(form) dela.Logger.Info().Msg("Number of shuffled ballots : " + strconv.Itoa(len(form.ShuffleInstances))) - dela.Logger.Info().Msg("Number of encrypted ballots : " + strconv.Itoa(len(form.Suffragia.Ciphervotes))) + suff, err = form.Suffragia(serdecontext, service.GetStore()) + if err != nil { + return xerrors.Errorf(getFormErr, err) + } + dela.Logger.Info().Msg("Number of encrypted ballots : " + strconv.Itoa(len(suff.Ciphervotes))) // ###################################### REQUEST PUBLIC SHARES ############ @@ -498,7 +506,7 @@ func (a *scenarioTestAction) Execute(ctx node.Context) error { time.Sleep(10 * time.Second) - form, err = getForm(serdecontext, formFac, formID, service) + form, err = types.FormFromStore(serdecontext, formFac, formID, service.GetStore()) if err != nil { return xerrors.Errorf(getFormErr, err) } @@ -517,7 +525,7 @@ func (a *scenarioTestAction) Execute(ctx node.Context) error { return xerrors.Errorf("failed to combine shares: %v", err) } - form, err = getForm(serdecontext, formFac, formID, service) + form, err = types.FormFromStore(serdecontext, formFac, formID, service.GetStore()) if err != nil { return xerrors.Errorf(getFormErr, err) } @@ -532,7 +540,7 @@ func (a *scenarioTestAction) Execute(ctx node.Context) error { fmt.Fprintln(ctx.Out, "Get form result") - form, err = getForm(serdecontext, formFac, formID, service) + form, err = types.FormFromStore(serdecontext, formFac, formID, service.GetStore()) if err != nil { return xerrors.Errorf(getFormErr, err) } @@ -631,7 +639,7 @@ func setupSimpleForm(ctx node.Context, secret kyber.Scalar, proxyAddr1 string, return "", types.Form{}, nil, xerrors.Errorf("failed to decode formID '%s': %v", formID, err) } - form, err := getForm(serdecontext, formFac, formID, service) + form, err := types.FormFromStore(serdecontext, formFac, formID, service.GetStore()) if err != nil { return "", types.Form{}, nil, xerrors.Errorf(getFormErr, err) } @@ -642,7 +650,7 @@ func setupSimpleForm(ctx node.Context, secret kyber.Scalar, proxyAddr1 string, return "", types.Form{}, nil, xerrors.Errorf("formID mismatch: %s != %s", form.FormID, formID) } - fmt.Fprintf(ctx.Out, "Title of the form: "+form.Configuration.MainTitle) + fmt.Fprintf(ctx.Out, "Title of the form: "+form.Configuration.Title.En) fmt.Fprintf(ctx.Out, "ID of the form: "+form.FormID) fmt.Fprintf(ctx.Out, "Status of the form: "+strconv.Itoa(int(form.Status))) @@ -650,7 +658,7 @@ func setupSimpleForm(ctx node.Context, secret kyber.Scalar, proxyAddr1 string, } func logFormStatus(form types.Form) { - dela.Logger.Info().Msg("Title of the form : " + form.Configuration.MainTitle) + dela.Logger.Info().Msg("Title of the form : " + form.Configuration.Title.En) dela.Logger.Info().Msg("ID of the form : " + form.FormID) dela.Logger.Info().Msg("Status of the form : " + strconv.Itoa(int(form.Status))) } @@ -803,41 +811,6 @@ func updateDKG(secret kyber.Scalar, proxyAddr, formIDHex, action string) (int, e return 0, nil } -// getForm gets the form from the snap. Returns the form ID NOT hex -// encoded. -func getForm(ctx serde.Context, formFac serde.Factory, formIDHex string, - srv ordering.Service) (types.Form, error) { - - var form types.Form - - formID, err := hex.DecodeString(formIDHex) - if err != nil { - return form, xerrors.Errorf("failed to decode formIDHex: %v", err) - } - - proof, err := srv.GetProof(formID) - if err != nil { - return form, xerrors.Errorf("failed to get proof: %v", err) - } - - formBuff := proof.GetValue() - if len(formBuff) == 0 { - return form, xerrors.Errorf("form does not exist") - } - - message, err := formFac.Deserialize(ctx, formBuff) - if err != nil { - return form, xerrors.Errorf("failed to deserialize Form: %v", err) - } - - form, ok := message.(types.Form) - if !ok { - return form, xerrors.Errorf("wrong message type: %T", message) - } - - return form, nil -} - func createSignedErr(err error) error { return xerrors.Errorf("failed to create signed request: %v", err) } diff --git a/contracts/evoting/controller/mod.go b/contracts/evoting/controller/mod.go index 27526b5f3..24d37b7a0 100644 --- a/contracts/evoting/controller/mod.go +++ b/contracts/evoting/controller/mod.go @@ -1,11 +1,11 @@ package controller import ( - "github.com/c4dt/dela/cli" - "github.com/c4dt/dela/cli/node" - "github.com/c4dt/dela/core/access" - "github.com/c4dt/dela/core/ordering" - "github.com/c4dt/dela/core/validation" + "go.dedis.ch/dela/cli" + "go.dedis.ch/dela/cli/node" + "go.dedis.ch/dela/core/access" + "go.dedis.ch/dela/core/ordering" + "go.dedis.ch/dela/core/validation" ) // NewController returns a new controller initializer diff --git a/contracts/evoting/controller/mod_test.go b/contracts/evoting/controller/mod_test.go index 85dc75ac7..d95da7b7d 100644 --- a/contracts/evoting/controller/mod_test.go +++ b/contracts/evoting/controller/mod_test.go @@ -3,8 +3,8 @@ package controller import ( "testing" - "github.com/c4dt/dela/cli/node" "github.com/stretchr/testify/require" + "go.dedis.ch/dela/cli/node" ) func TestController_OnStart(t *testing.T) { diff --git a/contracts/evoting/evoting.go b/contracts/evoting/evoting.go index 3c6aee364..03e7e4cc7 100644 --- a/contracts/evoting/evoting.go +++ b/contracts/evoting/evoting.go @@ -11,19 +11,19 @@ import ( "math/rand" "strings" - "github.com/c4dt/dela" - + "go.dedis.ch/dela" + "go.dedis.ch/dela/core/ordering/cosipbft/contracts/viewchange" "go.dedis.ch/kyber/v3/share" - "github.com/c4dt/d-voting/contracts/evoting/types" - "github.com/c4dt/dela/core/execution" - "github.com/c4dt/dela/core/execution/native" - "github.com/c4dt/dela/core/ordering/cosipbft/authority" - "github.com/c4dt/dela/core/store" - "github.com/c4dt/dela/core/txn" - "github.com/c4dt/dela/cosi/threshold" - "github.com/c4dt/dela/crypto/bls" - "github.com/c4dt/dela/serde" + "github.com/dedis/d-voting/contracts/evoting/types" + "go.dedis.ch/dela/core/execution" + "go.dedis.ch/dela/core/execution/native" + "go.dedis.ch/dela/core/ordering/cosipbft/authority" + "go.dedis.ch/dela/core/store" + "go.dedis.ch/dela/core/txn" + "go.dedis.ch/dela/cosi/threshold" + "go.dedis.ch/dela/crypto/bls" + "go.dedis.ch/dela/serde" "go.dedis.ch/kyber/v3/proof" "go.dedis.ch/kyber/v3/shuffle" "golang.org/x/xerrors" @@ -60,7 +60,7 @@ func (e evotingCommand) createForm(snap store.Snapshot, step execution.Step) err return xerrors.Errorf(errWrongTx, msg) } - rosterBuf, err := snap.Get(e.rosterKey) + rosterBuf, err := snap.Get(viewchange.GetRosterKey()) if err != nil { return xerrors.Errorf("failed to get roster") } @@ -91,7 +91,6 @@ func (e evotingCommand) createForm(snap store.Snapshot, step execution.Step) err Status: types.Initial, // Pubkey is set by the opening command BallotSize: tx.Configuration.MaxBallotSize(), - Suffragia: types.Suffragia{}, PubsharesUnits: units, ShuffleInstances: []types.ShuffleInstance{}, DecryptedBallots: []types.Ballot{}, @@ -131,7 +130,10 @@ func (e evotingCommand) createForm(snap store.Snapshot, step execution.Step) err } } - formsMetadata.FormsIDs.Add(form.FormID) + err = formsMetadata.FormsIDs.Add(form.FormID) + if err != nil { + return xerrors.Errorf("couldn't add new form: %v", err) + } formMetadataJSON, err := json.Marshal(formsMetadata) if err != nil { @@ -228,7 +230,10 @@ func (e evotingCommand) castVote(snap store.Snapshot, step execution.Step) error len(tx.Ballot), form.ChunksPerBallot()) } - form.Suffragia.CastVote(tx.UserID, tx.Ballot) + err = form.CastVote(e.context, snap, tx.UserID, tx.Ballot) + if err != nil { + return xerrors.Errorf("couldn't cast vote: %v", err) + } formBuf, err := form.Serialize(e.context) if err != nil { @@ -240,7 +245,7 @@ func (e evotingCommand) castVote(snap store.Snapshot, step execution.Step) error return xerrors.Errorf("failed to set value: %v", err) } - PromFormBallots.WithLabelValues(form.FormID).Set(float64(len(form.Suffragia.Ciphervotes))) + PromFormBallots.WithLabelValues(form.FormID).Set(float64(form.BallotCount)) return nil } @@ -269,7 +274,8 @@ func (e evotingCommand) shuffleBallots(snap store.Snapshot, step execution.Step) } if form.Status != types.Closed { - return xerrors.Errorf("the form is not closed") + return xerrors.Errorf("the form is not in state closed (current: %d != closed: %d)", + form.Status, types.Closed) } // Round starts at 0 @@ -358,7 +364,11 @@ func (e evotingCommand) shuffleBallots(snap store.Snapshot, step execution.Step) var ciphervotes []types.Ciphervote if tx.Round == 0 { - ciphervotes = form.Suffragia.Ciphervotes + suff, err := form.Suffragia(e.context, snap) + if err != nil { + return xerrors.Errorf("couldn't get ballots: %v", err) + } + ciphervotes = suff.Ciphervotes } else { // get the form's last shuffled ballots lastIndex := len(form.ShuffleInstances) - 1 @@ -466,7 +476,7 @@ func (e evotingCommand) closeForm(snap store.Snapshot, step execution.Step) erro return xerrors.Errorf("the form is not open, current status: %d", form.Status) } - if len(form.Suffragia.Ciphervotes) <= 1 { + if form.BallotCount <= 1 { return xerrors.Errorf("at least two ballots are required") } @@ -838,26 +848,11 @@ func (e evotingCommand) getForm(formIDHex string, return form, nil, xerrors.Errorf("failed to decode formIDHex: %v", err) } - formBuff, err := snap.Get(formIDBuf) + form, err = types.FormFromStore(e.context, e.formFac, formIDHex, snap) if err != nil { return form, nil, xerrors.Errorf("failed to get key %q: %v", formIDBuf, err) } - message, err := e.formFac.Deserialize(e.context, formBuff) - if err != nil { - return form, nil, xerrors.Errorf("failed to deserialize Form: %v", err) - } - - form, ok := message.(types.Form) - if !ok { - return form, nil, xerrors.Errorf("wrong message type: %T", message) - } - - if formIDHex != form.FormID { - return form, nil, xerrors.Errorf("formID do not match: %q != %q", - formIDHex, form.FormID) - } - return form, formIDBuf, nil } diff --git a/contracts/evoting/json/ciphervote.go b/contracts/evoting/json/ciphervote.go index bc9140655..b8c51b65d 100644 --- a/contracts/evoting/json/ciphervote.go +++ b/contracts/evoting/json/ciphervote.go @@ -1,8 +1,8 @@ package json import ( - "github.com/c4dt/d-voting/contracts/evoting/types" - "github.com/c4dt/dela/serde" + "github.com/dedis/d-voting/contracts/evoting/types" + "go.dedis.ch/dela/serde" "golang.org/x/xerrors" ) diff --git a/contracts/evoting/json/forms.go b/contracts/evoting/json/forms.go index 374bc86e5..63f05b9ef 100644 --- a/contracts/evoting/json/forms.go +++ b/contracts/evoting/json/forms.go @@ -1,12 +1,13 @@ package json import ( + "encoding/hex" "encoding/json" - "github.com/c4dt/d-voting/contracts/evoting/types" - "github.com/c4dt/dela/core/ordering/cosipbft/authority" - ctypes "github.com/c4dt/dela/core/ordering/cosipbft/types" - "github.com/c4dt/dela/serde" + "github.com/dedis/d-voting/contracts/evoting/types" + "go.dedis.ch/dela/core/ordering/cosipbft/authority" + ctypes "go.dedis.ch/dela/core/ordering/cosipbft/types" + "go.dedis.ch/dela/serde" "go.dedis.ch/kyber/v3" "go.dedis.ch/kyber/v3/suites" "golang.org/x/xerrors" @@ -35,9 +36,14 @@ func (formFormat) Encode(ctx serde.Context, message serde.Message) ([]byte, erro } } - suffragia, err := encodeSuffragia(ctx, m.Suffragia) - if err != nil { - return nil, xerrors.Errorf("failed to encode suffragia: %v", err) + suffragias := make([]string, len(m.SuffragiaStoreKeys)) + for i, suf := range m.SuffragiaStoreKeys { + suffragias[i] = hex.EncodeToString(suf) + } + + suffragiaHashes := make([]string, len(m.SuffragiaHashes)) + for i, sufH := range m.SuffragiaHashes { + suffragiaHashes[i] = hex.EncodeToString(sufH) } shuffleInstances, err := encodeShuffleInstances(ctx, m.ShuffleInstances) @@ -62,7 +68,9 @@ func (formFormat) Encode(ctx serde.Context, message serde.Message) ([]byte, erro Status: uint16(m.Status), Pubkey: pubkey, BallotSize: m.BallotSize, - Suffragia: suffragia, + Suffragias: suffragias, + SuffragiaHashes: suffragiaHashes, + BallotCount: m.BallotCount, ShuffleInstances: shuffleInstances, ShuffleThreshold: m.ShuffleThreshold, PubsharesUnits: pubsharesUnits, @@ -100,9 +108,20 @@ func (formFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) } } - suffragia, err := decodeSuffragia(ctx, formJSON.Suffragia) - if err != nil { - return nil, xerrors.Errorf("failed to decode suffragia: %v", err) + suffragias := make([][]byte, len(formJSON.Suffragias)) + for i, suff := range formJSON.Suffragias { + suffragias[i], err = hex.DecodeString(suff) + if err != nil { + return nil, xerrors.Errorf("failed to decode suffragia-address: %v", err) + } + } + + suffragiaHashes := make([][]byte, len(formJSON.SuffragiaHashes)) + for i, suffH := range formJSON.SuffragiaHashes { + suffragiaHashes[i], err = hex.DecodeString(suffH) + if err != nil { + return nil, xerrors.Errorf("failed to decode suffragia-hash: %v", err) + } } shuffleInstances, err := decodeShuffleInstances(ctx, formJSON.ShuffleInstances) @@ -127,17 +146,19 @@ func (formFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) } return types.Form{ - Configuration: formJSON.Configuration, - FormID: formJSON.FormID, - Status: types.Status(formJSON.Status), - Pubkey: pubKey, - BallotSize: formJSON.BallotSize, - Suffragia: suffragia, - ShuffleInstances: shuffleInstances, - ShuffleThreshold: formJSON.ShuffleThreshold, - PubsharesUnits: pubSharesSubmissions, - DecryptedBallots: formJSON.DecryptedBallots, - Roster: roster, + Configuration: formJSON.Configuration, + FormID: formJSON.FormID, + Status: types.Status(formJSON.Status), + Pubkey: pubKey, + BallotSize: formJSON.BallotSize, + SuffragiaStoreKeys: suffragias, + SuffragiaHashes: suffragiaHashes, + BallotCount: formJSON.BallotCount, + ShuffleInstances: shuffleInstances, + ShuffleThreshold: formJSON.ShuffleThreshold, + PubsharesUnits: pubSharesSubmissions, + DecryptedBallots: formJSON.DecryptedBallots, + Roster: roster, }, nil } @@ -157,7 +178,15 @@ type FormJSON struct { // to pad smaller ballots such that all ballots cast have the same size BallotSize int - Suffragia SuffragiaJSON + // Suffragias are the hex-encoded addresses of the Suffragia storages. + Suffragias []string + + // BallotCount represents the total number of ballots cast. + BallotCount uint32 + + // SuffragiaHashes are the hex-encoded sha256-hashes of the ballots + // in every Suffragia. + SuffragiaHashes []string // ShuffleInstances is all the shuffles, along with their proof and identity // of shuffler. @@ -179,62 +208,6 @@ type FormJSON struct { RosterBuf []byte } -// SuffragiaJSON defines the JSON representation of a suffragia. -type SuffragiaJSON struct { - UserIDs []string - Ciphervotes []json.RawMessage -} - -func encodeSuffragia(ctx serde.Context, suffragia types.Suffragia) (SuffragiaJSON, error) { - ciphervotes := make([]json.RawMessage, len(suffragia.Ciphervotes)) - - for i, ciphervote := range suffragia.Ciphervotes { - buff, err := ciphervote.Serialize(ctx) - if err != nil { - return SuffragiaJSON{}, xerrors.Errorf("failed to serialize ciphervote: %v", err) - } - - ciphervotes[i] = buff - } - return SuffragiaJSON{ - UserIDs: suffragia.UserIDs, - Ciphervotes: ciphervotes, - }, nil -} - -func decodeSuffragia(ctx serde.Context, suffragiaJSON SuffragiaJSON) (types.Suffragia, error) { - var res types.Suffragia - fac := ctx.GetFactory(types.CiphervoteKey{}) - - factory, ok := fac.(types.CiphervoteFactory) - if !ok { - return res, xerrors.Errorf("invalid ciphervote factory: '%T'", fac) - } - - ciphervotes := make([]types.Ciphervote, len(suffragiaJSON.Ciphervotes)) - - for i, ciphervoteJSON := range suffragiaJSON.Ciphervotes { - msg, err := factory.Deserialize(ctx, ciphervoteJSON) - if err != nil { - return res, xerrors.Errorf("failed to deserialize ciphervote json: %v", err) - } - - ciphervote, ok := msg.(types.Ciphervote) - if !ok { - return res, xerrors.Errorf("wrong type: '%T'", msg) - } - - ciphervotes[i] = ciphervote - } - - res = types.Suffragia{ - UserIDs: suffragiaJSON.UserIDs, - Ciphervotes: ciphervotes, - } - - return res, nil -} - // ShuffleInstanceJSON defines the JSON representation of a shuffle instance type ShuffleInstanceJSON struct { // ShuffledBallots contains the list of shuffled ciphertext for this round diff --git a/contracts/evoting/json/mod.go b/contracts/evoting/json/mod.go index 12e706781..5906611ce 100644 --- a/contracts/evoting/json/mod.go +++ b/contracts/evoting/json/mod.go @@ -1,14 +1,15 @@ package json import ( - "github.com/c4dt/d-voting/contracts/evoting/types" - "github.com/c4dt/dela/serde" + "github.com/dedis/d-voting/contracts/evoting/types" + "go.dedis.ch/dela/serde" ) // Register the JSON formats for the form, ciphervote, and transaction func init() { types.RegisterFormFormat(serde.FormatJSON, formFormat{}) + types.RegisterSuffragiaFormat(serde.FormatJSON, suffragiaFormat{}) types.RegisterCiphervoteFormat(serde.FormatJSON, ciphervoteFormat{}) types.RegisterTransactionFormat(serde.FormatJSON, transactionFormat{}) } diff --git a/contracts/evoting/json/suffragia.go b/contracts/evoting/json/suffragia.go new file mode 100644 index 000000000..7510f18bb --- /dev/null +++ b/contracts/evoting/json/suffragia.go @@ -0,0 +1,97 @@ +package json + +import ( + "encoding/json" + + "github.com/dedis/d-voting/contracts/evoting/types" + "go.dedis.ch/dela/serde" + "golang.org/x/xerrors" +) + +type suffragiaFormat struct{} + +func (suffragiaFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, error) { + switch m := msg.(type) { + case types.Suffragia: + sJson, err := encodeSuffragia(ctx, m) + if err != nil { + return nil, xerrors.Errorf("couldn't encode suffragia: %v", err) + } + + buff, err := ctx.Marshal(&sJson) + if err != nil { + return nil, xerrors.Errorf("failed to marshal form: %v", err) + } + + return buff, nil + default: + return nil, xerrors.Errorf("Unknown format: %T", msg) + } +} + +func (suffragiaFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) { + var sJson SuffragiaJSON + + err := ctx.Unmarshal(data, &sJson) + if err != nil { + return nil, xerrors.Errorf("failed to unmarshal form: %v", err) + } + + return decodeSuffragia(ctx, sJson) +} + +// SuffragiaJSON defines the JSON representation of a suffragia. +type SuffragiaJSON struct { + UserIDs []string + Ciphervotes []json.RawMessage +} + +func encodeSuffragia(ctx serde.Context, suffragia types.Suffragia) (SuffragiaJSON, error) { + ciphervotes := make([]json.RawMessage, len(suffragia.Ciphervotes)) + + for i, ciphervote := range suffragia.Ciphervotes { + buff, err := ciphervote.Serialize(ctx) + if err != nil { + return SuffragiaJSON{}, xerrors.Errorf("failed to serialize ciphervote: %v", err) + } + + ciphervotes[i] = buff + } + return SuffragiaJSON{ + UserIDs: suffragia.UserIDs, + Ciphervotes: ciphervotes, + }, nil +} + +func decodeSuffragia(ctx serde.Context, suffragiaJSON SuffragiaJSON) (types.Suffragia, error) { + var res types.Suffragia + fac := ctx.GetFactory(types.CiphervoteKey{}) + + factory, ok := fac.(types.CiphervoteFactory) + if !ok { + return res, xerrors.Errorf("invalid ciphervote factory: '%T'", fac) + } + + ciphervotes := make([]types.Ciphervote, len(suffragiaJSON.Ciphervotes)) + + for i, ciphervoteJSON := range suffragiaJSON.Ciphervotes { + msg, err := factory.Deserialize(ctx, ciphervoteJSON) + if err != nil { + return res, xerrors.Errorf("failed to deserialize ciphervote json: %v", err) + } + + ciphervote, ok := msg.(types.Ciphervote) + if !ok { + return res, xerrors.Errorf("wrong type: '%T'", msg) + } + + ciphervotes[i] = ciphervote + } + + res = types.Suffragia{ + UserIDs: suffragiaJSON.UserIDs, + Ciphervotes: ciphervotes, + } + + return res, nil +} diff --git a/contracts/evoting/json/transaction.go b/contracts/evoting/json/transaction.go index 08b084321..6bbe77a98 100644 --- a/contracts/evoting/json/transaction.go +++ b/contracts/evoting/json/transaction.go @@ -3,8 +3,8 @@ package json import ( "encoding/json" - "github.com/c4dt/d-voting/contracts/evoting/types" - "github.com/c4dt/dela/serde" + "github.com/dedis/d-voting/contracts/evoting/types" + "go.dedis.ch/dela/serde" "golang.org/x/xerrors" ) diff --git a/contracts/evoting/mod.go b/contracts/evoting/mod.go index 58957fb4f..544269deb 100644 --- a/contracts/evoting/mod.go +++ b/contracts/evoting/mod.go @@ -1,24 +1,25 @@ package evoting import ( - dvoting "github.com/c4dt/d-voting" - "github.com/c4dt/d-voting/contracts/evoting/types" - "github.com/c4dt/d-voting/services/dkg" - "github.com/c4dt/dela/core/access" - "github.com/c4dt/dela/core/execution" - "github.com/c4dt/dela/core/execution/native" - "github.com/c4dt/dela/core/ordering/cosipbft/authority" - "github.com/c4dt/dela/core/store" - "github.com/c4dt/dela/serde" - "github.com/c4dt/dela/serde/json" + dvoting "github.com/dedis/d-voting" + "github.com/dedis/d-voting/contracts/evoting/types" + "github.com/dedis/d-voting/services/dkg" "github.com/prometheus/client_golang/prometheus" + "go.dedis.ch/dela/core/access" + "go.dedis.ch/dela/core/execution" + "go.dedis.ch/dela/core/execution/native" + "go.dedis.ch/dela/core/ordering/cosipbft/authority" + "go.dedis.ch/dela/core/store" + "go.dedis.ch/dela/core/store/prefixed" + "go.dedis.ch/dela/serde" + "go.dedis.ch/dela/serde/json" "go.dedis.ch/kyber/v3/proof" "go.dedis.ch/kyber/v3/suites" "golang.org/x/xerrors" // Register the JSON format for the form - _ "github.com/c4dt/d-voting/contracts/evoting/json" + _ "github.com/dedis/d-voting/contracts/evoting/json" ) var ( @@ -67,8 +68,11 @@ const ( var suite = suites.MustFind("Ed25519") const ( + // ContractUID is the UID of the contract + ContractUID = "EVOT" + // ContractName is the name of the contract. - ContractName = "github.com/c4dt/dela.Evoting" + ContractName = "go.dedis.ch/dela.Evoting" // CmdArg is the argument's name to indicate the kind of command we want to // run on the contract. Should be one of the Command type. @@ -126,8 +130,8 @@ const ( // NewCreds creates new credentials for a evoting contract execution. We might // want to use in the future a separate credential for each command. -func NewCreds(id []byte) access.Credential { - return access.NewContractCreds(id, ContractName, credentialAllCommand) +func NewCreds() access.Credential { + return access.NewContractCreds([]byte(ContractUID), ContractName, credentialAllCommand) } // RegisterContract registers the value contract to the given execution service. @@ -143,15 +147,10 @@ type Contract struct { // access is the access control service managing this smart contract access access.Service - // accessKey is the access identifier allowed to use this smart contract - accessKey []byte - cmd commands pedersen dkg.DKG - rosterKey []byte - context serde.Context formFac serde.Factory @@ -160,7 +159,7 @@ type Contract struct { } // NewContract creates a new Value contract -func NewContract(accessKey, rosterKey []byte, srvc access.Service, +func NewContract(srvc access.Service, pedersen dkg.DKG, rosterFac authority.Factory) Contract { ctx := json.NewContext() @@ -170,11 +169,8 @@ func NewContract(accessKey, rosterKey []byte, srvc access.Service, transactionFac := types.NewTransactionFactory(ciphervoteFac) contract := Contract{ - access: srvc, - accessKey: accessKey, - pedersen: pedersen, - - rosterKey: rosterKey, + access: srvc, + pedersen: pedersen, context: ctx, @@ -190,7 +186,7 @@ func NewContract(accessKey, rosterKey []byte, srvc access.Service, // Execute implements native.Contract func (c Contract) Execute(snap store.Snapshot, step execution.Step) error { - creds := NewCreds(c.accessKey) + creds := NewCreds() err := c.access.Match(snap, creds, step.Current.GetIdentity()) if err != nil { @@ -203,6 +199,8 @@ func (c Contract) Execute(snap store.Snapshot, step execution.Step) error { return xerrors.Errorf("%q not found in tx arg", CmdArg) } + snap = prefixed.NewSnapshot(ContractUID, snap) + switch Command(cmd) { case CmdCreateForm: err = c.cmd.createForm(snap, step) @@ -256,6 +254,13 @@ func (c Contract) Execute(snap store.Snapshot, step execution.Step) error { return nil } +// UID returns the unique 4-bytes contract identifier. +// +// - implements native.Contract +func (c Contract) UID() string { + return ContractUID +} + func init() { dvoting.PromCollectors = append(dvoting.PromCollectors, PromFormStatus, diff --git a/contracts/evoting/mod_test.go b/contracts/evoting/mod_test.go index 6c5d078a3..f752ea62f 100644 --- a/contracts/evoting/mod_test.go +++ b/contracts/evoting/mod_test.go @@ -7,23 +7,23 @@ import ( "strconv" "testing" - "github.com/c4dt/d-voting/contracts/evoting/types" - "github.com/c4dt/d-voting/internal/testing/fake" - "github.com/c4dt/d-voting/services/dkg" - "github.com/c4dt/dela/core/access" - "github.com/c4dt/dela/core/execution" - "github.com/c4dt/dela/core/execution/native" - "github.com/c4dt/dela/core/ordering" - "github.com/c4dt/dela/core/ordering/cosipbft/authority" - "github.com/c4dt/dela/core/store" - "github.com/c4dt/dela/core/txn" - "github.com/c4dt/dela/core/txn/signed" - "github.com/c4dt/dela/crypto" - "github.com/c4dt/dela/crypto/bls" - "github.com/c4dt/dela/serde" - sjson "github.com/c4dt/dela/serde/json" + "github.com/dedis/d-voting/contracts/evoting/types" + "github.com/dedis/d-voting/internal/testing/fake" + "github.com/dedis/d-voting/services/dkg" "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stretchr/testify/require" + "go.dedis.ch/dela/core/access" + "go.dedis.ch/dela/core/execution" + "go.dedis.ch/dela/core/execution/native" + "go.dedis.ch/dela/core/ordering" + "go.dedis.ch/dela/core/ordering/cosipbft/authority" + "go.dedis.ch/dela/core/store" + "go.dedis.ch/dela/core/txn" + "go.dedis.ch/dela/core/txn/signed" + "go.dedis.ch/dela/crypto" + "go.dedis.ch/dela/crypto/bls" + "go.dedis.ch/dela/serde" + sjson "go.dedis.ch/dela/serde/json" "go.dedis.ch/kyber/v3" "go.dedis.ch/kyber/v3/proof" "go.dedis.ch/kyber/v3/util/random" @@ -63,20 +63,17 @@ func TestExecute(t *testing.T) { actor: fakeDkgActor{}, err: nil, } - var evotingAccessKey = [32]byte{3} - rosterKey := [32]byte{} - service := fakeAccess{err: fake.GetError()} rosterFac := fakeAuthorityFactory{} - contract := NewContract(evotingAccessKey[:], rosterKey[:], service, fakeDkg, rosterFac) + contract := NewContract(service, fakeDkg, rosterFac) err := contract.Execute(fakeStore{}, makeStep(t)) require.EqualError(t, err, "identity not authorized: fake.PublicKey ("+fake.GetError().Error()+")") service = fakeAccess{} - contract = NewContract(evotingAccessKey[:], rosterKey[:], service, fakeDkg, rosterFac) + contract = NewContract(service, fakeDkg, rosterFac) err = contract.Execute(fakeStore{}, makeStep(t)) require.EqualError(t, err, "\"evoting:command\" not found in tx arg") @@ -129,13 +126,10 @@ func TestCommand_CreateForm(t *testing.T) { data, err := createForm.Serialize(ctx) require.NoError(t, err) - var evotingAccessKey = [32]byte{3} - rosterKey := [32]byte{} - service := fakeAccess{err: fake.GetError()} rosterFac := fakeAuthorityFactory{} - contract := NewContract(evotingAccessKey[:], rosterKey[:], service, fakeDkg, rosterFac) + contract := NewContract(service, fakeDkg, rosterFac) cmd := evotingCommand{ Contract: &contract, @@ -301,11 +295,13 @@ func TestCommand_CastVote(t *testing.T) { form, ok := message.(types.Form) require.True(t, ok) - require.Len(t, form.Suffragia.Ciphervotes, 1) - require.True(t, castVote.Ballot.Equal(form.Suffragia.Ciphervotes[0])) + require.Equal(t, uint32(1), form.BallotCount) + suff, err := form.Suffragia(ctx, snap) + require.NoError(t, err) + require.True(t, castVote.Ballot.Equal(suff.Ciphervotes[0])) - require.Equal(t, castVote.UserID, form.Suffragia.UserIDs[0]) - require.Equal(t, float64(len(form.Suffragia.Ciphervotes)), testutil.ToFloat64(PromFormBallots)) + require.Equal(t, castVote.UserID, suff.UserIDs[0]) + require.Equal(t, float64(form.BallotCount), testutil.ToFloat64(PromFormBallots)) } func TestCommand_CloseForm(t *testing.T) { @@ -370,8 +366,8 @@ func TestCommand_CloseForm(t *testing.T) { err = cmd.closeForm(snap, makeStep(t, FormArg, string(data))) require.EqualError(t, err, "at least two ballots are required") - dummyForm.Suffragia.CastVote("dummyUser1", types.Ciphervote{}) - dummyForm.Suffragia.CastVote("dummyUser2", types.Ciphervote{}) + require.NoError(t, dummyForm.CastVote(ctx, snap, "dummyUser1", types.Ciphervote{})) + require.NoError(t, dummyForm.CastVote(ctx, snap, "dummyUser2", types.Ciphervote{})) formBuf, err = dummyForm.Serialize(ctx) require.NoError(t, err) @@ -399,15 +395,13 @@ func TestCommand_CloseForm(t *testing.T) { func TestCommand_ShuffleBallotsCannotShuffleTwice(t *testing.T) { k := 3 - form, shuffleBallots, contract := initGoodShuffleBallot(t, k) + snap, form, shuffleBallots, contract := initGoodShuffleBallot(t, k) cmd := evotingCommand{ Contract: &contract, prover: fakeProver, } - snap := fake.NewSnapshot() - // Attempts to shuffle twice : shuffleBallots.Round = 1 @@ -445,15 +439,13 @@ func TestCommand_ShuffleBallotsValidScenarios(t *testing.T) { k := 3 // Simple Shuffle from round 0 : - form, shuffleBallots, contract := initGoodShuffleBallot(t, k) + snap, form, shuffleBallots, contract := initGoodShuffleBallot(t, k) cmd := evotingCommand{ Contract: &contract, prover: fakeProver, } - snap := fake.NewSnapshot() - formBuf, err := form.Serialize(ctx) require.NoError(t, err) @@ -555,7 +547,7 @@ func TestCommand_ShuffleBallotsFormatErrors(t *testing.T) { require.NoError(t, err) err = cmd.shuffleBallots(snap, makeStep(t, FormArg, string(data))) - require.EqualError(t, err, "the form is not closed") + require.EqualError(t, err, "the form is not in state closed (current: 0 != closed: 2)") // Wrong round : form.Status = types.Closed @@ -703,7 +695,6 @@ func TestCommand_ShuffleBallotsFormatErrors(t *testing.T) { form.Pubkey = pubKey shuffleBallots.Round = 0 form.ShuffleInstances = make([]types.ShuffleInstance, 0) - form.Suffragia.Ciphervotes = make([]types.Ciphervote, 0) data, err = shuffleBallots.Serialize(ctx) require.NoError(t, err) @@ -719,9 +710,9 @@ func TestCommand_ShuffleBallotsFormatErrors(t *testing.T) { // > With only one shuffled ballot the shuffling can't happen - form.Suffragia.CastVote("user1", types.Ciphervote{ + require.NoError(t, form.CastVote(ctx, snap, "user1", types.Ciphervote{ types.EGPair{K: suite.Point(), C: suite.Point()}, - }) + })) data, err = shuffleBallots.Serialize(ctx) require.NoError(t, err) @@ -1124,25 +1115,21 @@ func initFormAndContract() (types.Form, Contract) { FormID: fakeFormID, Status: 0, Pubkey: nil, - Suffragia: types.Suffragia{}, ShuffleInstances: make([]types.ShuffleInstance, 0), DecryptedBallots: nil, ShuffleThreshold: 0, Roster: fake.Authority{}, } - var evotingAccessKey = [32]byte{3} - rosterKey := [32]byte{} - service := fakeAccess{err: fake.GetError()} rosterFac := fakeAuthorityFactory{} - contract := NewContract(evotingAccessKey[:], rosterKey[:], service, fakeDkg, rosterFac) + contract := NewContract(service, fakeDkg, rosterFac) return dummyForm, contract } -func initGoodShuffleBallot(t *testing.T, k int) (types.Form, types.ShuffleBallots, Contract) { +func initGoodShuffleBallot(t *testing.T, k int) (store.Snapshot, types.Form, types.ShuffleBallots, Contract) { form, shuffleBallots, contract := initBadShuffleBallot(3) form.Status = types.Closed @@ -1165,12 +1152,13 @@ func initGoodShuffleBallot(t *testing.T, k int) (types.Form, types.ShuffleBallot shuffleBallots.Round = 0 form.ShuffleInstances = make([]types.ShuffleInstance, 0) + snap := fake.NewSnapshot() for i := 0; i < k; i++ { ballot := types.Ciphervote{types.EGPair{ K: Ks[i], C: Cs[i], }} - form.Suffragia.CastVote(fmt.Sprintf("user%d", i), ballot) + require.NoError(t, form.CastVote(ctx, snap, fmt.Sprintf("user%d", i), ballot)) } // Valid Signature of shuffle @@ -1198,7 +1186,7 @@ func initGoodShuffleBallot(t *testing.T, k int) (types.Form, types.ShuffleBallot } shuffleBallots.RandomVector.LoadFromScalars(e) - return form, shuffleBallots, contract + return snap, form, shuffleBallots, contract } func initBadShuffleBallot(sizeOfForm int) (types.Form, types.ShuffleBallots, Contract) { diff --git a/contracts/evoting/types/ballots.go b/contracts/evoting/types/ballots.go index 26fd3eed8..770af3698 100644 --- a/contracts/evoting/types/ballots.go +++ b/contracts/evoting/types/ballots.go @@ -41,7 +41,8 @@ type Ballot struct { } // Unmarshal decodes the given string according to the format described in -// "state of smart contract.md" +// "/docs/state_of_smart_contract.md" +// TODO: actually describe the format in there... func (b *Ballot) Unmarshal(marshalledBallot string, form Form) error { lines := strings.Split(marshalledBallot, "\n") @@ -88,7 +89,7 @@ func (b *Ballot) Unmarshal(marshalledBallot string, form Form) error { ID: ID(questionID), MaxN: q.GetMaxN(), MinN: q.GetMinN(), - Choices: make([]string, q.GetChoicesLength()), + Choices: make([]Choice, q.GetChoicesLength()), } results, err := selectQ.unmarshalAnswers(selections) @@ -107,7 +108,7 @@ func (b *Ballot) Unmarshal(marshalledBallot string, form Form) error { ID: ID(questionID), MaxN: q.GetMaxN(), MinN: q.GetMinN(), - Choices: make([]string, q.GetChoicesLength()), + Choices: make([]Choice, q.GetChoicesLength()), } results, err := rankQ.unmarshalAnswers(ranks) @@ -126,7 +127,7 @@ func (b *Ballot) Unmarshal(marshalledBallot string, form Form) error { MaxN: q.GetMaxN(), MinN: q.GetMinN(), MaxLength: 0, // TODO: Should the length check be also done at decryption? - Choices: make([]string, q.GetChoicesLength()), + Choices: make([]Choice, q.GetChoicesLength()), } results, err := textQ.unmarshalAnswers(texts) @@ -252,12 +253,33 @@ func (b *Ballot) Equal(other Ballot) bool { return true } +// Title contains the titles in different languages. +type Title struct { + En string + Fr string + De string + URL string +} + +// Hint contains explanations in different languages. +type Hint struct { + En string + Fr string + De string +} + +// Choice contains a choice and an optional URL +type Choice struct { + Choice string + URL string +} + // Subject is a wrapper around multiple questions that can be of type "select", // "rank", or "text". type Subject struct { ID ID - Title string + Title Title // Order defines the order of the different question, which all have a unique // identifier. This is purely for display purpose. @@ -311,30 +333,47 @@ func (s *Subject) MaxEncodedSize() int { //TODO : optimise by computing max size according to number of choices and maxN for _, rank := range s.Ranks { - size += len(rank.GetID() + "::") - size += len(rank.ID) - // at most 3 bytes (128) + ',' per choice + size += len(rank.GetID()) + // the ID arrives Base64-encoded, but rank.ID is decoded + // we need the size of the Base64-encoded string + size += len(base64.StdEncoding.EncodeToString([]byte(rank.ID))) + + // ':' separators ('id:id:choice') + size += 2 + + // 4 bytes per choice (choice and separating comma/newline) size += len(rank.Choices) * 4 } for _, selection := range s.Selects { - size += len(selection.GetID() + "::") - size += len(selection.ID) - // 1 bytes (0/1) + ',' per choice + size += len(selection.GetID()) + // the ID arrives Base64-encoded, but selection.ID is decoded + // we need the size of the Base64-encoded string + size += len(base64.StdEncoding.EncodeToString([]byte(selection.ID))) + + // ':' separators ('id:id:choice') + size += 2 + + // 2 bytes per choice (0/1 and separating comma/newline) size += len(selection.Choices) * 2 } for _, text := range s.Texts { - size += len(text.GetID() + "::") - size += len(text.ID) + size += len(text.GetID()) + // the ID arrives Base64-encoded, but text.ID is decoded + // we need the size of the Base64-encoded string + size += len(base64.StdEncoding.EncodeToString([]byte(text.ID))) + + // ':' separators ('id:id:choice') + size += 2 - // at most 4 bytes per character + ',' per answer + // 4 bytes per character and 1 byte for separating comma/newline maxTextPerAnswer := 4*int(text.MaxLength) + 1 size += maxTextPerAnswer*int(text.MaxN) + int(math.Max(float64(len(text.Choices)-int(text.MaxN)), 0)) } - // Last line has 2 '\n' + // additional '\n' on last line if size != 0 { size++ } @@ -413,11 +452,11 @@ func isValid(q Question) bool { type Select struct { ID ID - Title string + Title Title MaxN uint MinN uint - Choices []string - Hint string + Choices []Choice + Hint Hint } // GetID implements Question @@ -479,11 +518,11 @@ func (s Select) unmarshalAnswers(sforms []string) ([]bool, error) { type Rank struct { ID ID - Title string + Title Title MaxN uint MinN uint - Choices []string - Hint string + Choices []Choice + Hint Hint } func (r Rank) GetID() string { @@ -552,13 +591,13 @@ func (r Rank) unmarshalAnswers(ranks []string) ([]int8, error) { type Text struct { ID ID - Title string + Title Title MaxN uint MinN uint MaxLength uint Regex string - Choices []string - Hint string + Choices []Choice + Hint Hint } func (t Text) GetID() string { diff --git a/contracts/evoting/types/ballots_test.go b/contracts/evoting/types/ballots_test.go index fd64d16db..f47bddfda 100644 --- a/contracts/evoting/types/ballots_test.go +++ b/contracts/evoting/types/ballots_test.go @@ -56,34 +56,34 @@ func TestBallot_Unmarshal(t *testing.T) { Selects: []Select{{ ID: decodedQuestionID(1), - Title: "", + Title: Title{En: "", Fr: "", De: "", URL: ""}, MaxN: 2, MinN: 2, - Choices: make([]string, 3), + Choices: make([]Choice, 3), }, { ID: decodedQuestionID(2), - Title: "", + Title: Title{En: "", Fr: "", De: "", URL: ""}, MaxN: 3, MinN: 3, - Choices: make([]string, 5), + Choices: make([]Choice, 5), }}, Ranks: []Rank{{ ID: decodedQuestionID(3), - Title: "", + Title: Title{En: "", Fr: "", De: "", URL: ""}, MaxN: 4, MinN: 0, - Choices: make([]string, 4), + Choices: make([]Choice, 4), }}, Texts: []Text{{ ID: decodedQuestionID(4), - Title: "", + Title: Title{En: "", Fr: "", De: "", URL: ""}, MaxN: 2, MinN: 2, MaxLength: 10, Regex: "", - Choices: make([]string, 2), + Choices: make([]Choice, 2), }}, }, }} @@ -305,7 +305,7 @@ func TestSubject_MaxEncodedSize(t *testing.T) { subject := Subject{ Subjects: []Subject{{ ID: "", - Title: "", + Title: Title{En: "", Fr: "", De: "", URL: ""}, Order: nil, Subjects: []Subject{}, Selects: []Select{}, @@ -314,49 +314,49 @@ func TestSubject_MaxEncodedSize(t *testing.T) { }}, Selects: []Select{{ - ID: encodedQuestionID(1), - Title: "", + ID: decodedQuestionID(1), + Title: Title{En: "", Fr: "", De: "", URL: ""}, MaxN: 3, MinN: 0, - Choices: make([]string, 3), + Choices: make([]Choice, 3), }, { - ID: encodedQuestionID(2), - Title: "", + ID: decodedQuestionID(2), + Title: Title{En: "", Fr: "", De: "", URL: ""}, MaxN: 5, MinN: 0, - Choices: make([]string, 5), + Choices: make([]Choice, 5), }}, Ranks: []Rank{{ - ID: encodedQuestionID(3), - Title: "", + ID: decodedQuestionID(3), + Title: Title{En: "", Fr: "", De: "", URL: ""}, MaxN: 4, MinN: 0, - Choices: make([]string, 4), + Choices: make([]Choice, 4), }}, Texts: []Text{{ - ID: encodedQuestionID(4), - Title: "", + ID: decodedQuestionID(4), + Title: Title{En: "", Fr: "", De: "", URL: ""}, MaxN: 2, MinN: 0, MaxLength: 10, Regex: "", - Choices: make([]string, 2), + Choices: make([]Choice, 2), }, { - ID: encodedQuestionID(5), - Title: "", + ID: decodedQuestionID(5), + Title: Title{En: "", Fr: "", De: "", URL: ""}, MaxN: 1, MinN: 0, MaxLength: 10, Regex: "", - Choices: make([]string, 3), + Choices: make([]Choice, 3), }}, } conf := Configuration{ - MainTitle: "", - Scaffold: []Subject{subject}, + Title: Title{En: "", Fr: "", De: "", URL: ""}, + Scaffold: []Subject{subject}, } size := conf.MaxBallotSize() @@ -368,7 +368,7 @@ func TestSubject_MaxEncodedSize(t *testing.T) { func TestSubject_IsValid(t *testing.T) { mainSubject := &Subject{ ID: ID(base64.StdEncoding.EncodeToString([]byte("S1"))), - Title: "", + Title: Title{En: "", Fr: "", De: "", URL: ""}, Order: []ID{}, Subjects: []Subject{}, Selects: []Select{}, @@ -378,7 +378,7 @@ func TestSubject_IsValid(t *testing.T) { subSubject := &Subject{ ID: ID(base64.StdEncoding.EncodeToString([]byte("S2"))), - Title: "", + Title: Title{En: "", Fr: "", De: "", URL: ""}, Order: []ID{}, Subjects: []Subject{}, Selects: []Select{}, @@ -387,8 +387,8 @@ func TestSubject_IsValid(t *testing.T) { } configuration := Configuration{ - MainTitle: "", - Scaffold: []Subject{*mainSubject, *subSubject}, + Title: Title{En: "", Fr: "", De: "", URL: ""}, + Scaffold: []Subject{*mainSubject, *subSubject}, } valid := configuration.IsValid() @@ -400,18 +400,18 @@ func TestSubject_IsValid(t *testing.T) { mainSubject.Selects = []Select{{ ID: encodedQuestionID(1), - Title: "", + Title: Title{En: "", Fr: "", De: "", URL: ""}, MaxN: 0, MinN: 0, - Choices: make([]string, 0), + Choices: make([]Choice, 0), }} mainSubject.Ranks = []Rank{{ ID: encodedQuestionID(1), - Title: "", + Title: Title{En: "", Fr: "", De: "", URL: ""}, MaxN: 0, MinN: 0, - Choices: make([]string, 0), + Choices: make([]Choice, 0), }} configuration.Scaffold = []Subject{*mainSubject} @@ -423,10 +423,10 @@ func TestSubject_IsValid(t *testing.T) { mainSubject.Ranks[0] = Rank{ ID: encodedQuestionID(2), - Title: "", + Title: Title{En: "", Fr: "", De: "", URL: ""}, MaxN: 0, MinN: 2, - Choices: make([]string, 0), + Choices: make([]Choice, 0), } configuration.Scaffold = []Subject{*mainSubject} @@ -439,10 +439,10 @@ func TestSubject_IsValid(t *testing.T) { mainSubject.Ranks = []Rank{} mainSubject.Selects[0] = Select{ ID: encodedQuestionID(1), - Title: "", + Title: Title{En: "", Fr: "", De: "", URL: ""}, MaxN: 1, MinN: 0, - Choices: make([]string, 0), + Choices: make([]Choice, 0), } configuration.Scaffold = []Subject{*mainSubject} @@ -455,12 +455,12 @@ func TestSubject_IsValid(t *testing.T) { mainSubject.Selects = []Select{} mainSubject.Texts = []Text{{ ID: encodedQuestionID(3), - Title: "", + Title: Title{En: "", Fr: "", De: "", URL: ""}, MaxN: 2, MinN: 4, MaxLength: 0, Regex: "", - Choices: make([]string, 0), + Choices: make([]Choice, 0), }} configuration.Scaffold = []Subject{*mainSubject} diff --git a/contracts/evoting/types/ciphervote.go b/contracts/evoting/types/ciphervote.go index 4cef2ae11..f4279ea7e 100644 --- a/contracts/evoting/types/ciphervote.go +++ b/contracts/evoting/types/ciphervote.go @@ -4,8 +4,8 @@ import ( "fmt" "io" - "github.com/c4dt/dela/serde" - "github.com/c4dt/dela/serde/registry" + "go.dedis.ch/dela/serde" + "go.dedis.ch/dela/serde/registry" "go.dedis.ch/kyber/v3" "golang.org/x/xerrors" ) diff --git a/contracts/evoting/types/election.go b/contracts/evoting/types/election.go index 9323ace18..64db99b23 100644 --- a/contracts/evoting/types/election.go +++ b/contracts/evoting/types/election.go @@ -1,12 +1,17 @@ package types import ( + "crypto/sha256" + "encoding/binary" + "encoding/hex" + "fmt" "io" - "github.com/c4dt/dela/core/ordering/cosipbft/authority" - ctypes "github.com/c4dt/dela/core/ordering/cosipbft/types" - "github.com/c4dt/dela/serde" - "github.com/c4dt/dela/serde/registry" + "go.dedis.ch/dela/core/ordering/cosipbft/authority" + ctypes "go.dedis.ch/dela/core/ordering/cosipbft/types" + "go.dedis.ch/dela/core/store" + "go.dedis.ch/dela/serde" + "go.dedis.ch/dela/serde/registry" "go.dedis.ch/kyber/v3" "go.dedis.ch/kyber/v3/suites" "golang.org/x/xerrors" @@ -21,7 +26,6 @@ type ID string type Status uint16 const ( - // DecryptedBallots = 4 // Initial is when the form has just been created Initial Status = 0 // Open is when the form is open, i.e. it fetched the public key @@ -34,10 +38,17 @@ const ( PubSharesSubmitted Status = 4 // ResultAvailable is when the ballots have been decrypted ResultAvailable Status = 5 - // Canceled is when the form has been cancel + // Canceled is when the form has been canceled Canceled Status = 6 ) +// BallotsPerBatch to improve performance, so that (de)serializing only touches +// 100 ballots at a time. +var BallotsPerBatch = uint32(100) + +// TestCastBallots if true, automatically fills every batch with ballots. +var TestCastBallots = false + // formFormat contains the supported formats for the form. Right now // only JSON is supported. var formFormat = registry.NewSimpleRegistry() @@ -67,8 +78,18 @@ type Form struct { // to pad smaller ballots such that all ballots cast have the same size BallotSize int - // Suffragia is a map from User ID to their encrypted ballot - Suffragia Suffragia + // SuffragiaStoreKeys holds a slice of storage-keys to 0 or more Suffragia. + // This is to optimize the time it takes to (De)serialize a Form. + SuffragiaStoreKeys [][]byte + + // BallotCount is the total number of ballots cast, including double + // ballots. + BallotCount uint32 + + // SuffragiaHashes holds a slice of hashes to all SuffragiaStoreKeys. + // In case a Form has also to be proven to be correct outside the nodes, + // the hashes are needed to prove the Suffragia are correct. + SuffragiaHashes [][]byte // ShuffleInstances is all the shuffles, along with their proof and identity // of shuffler. @@ -136,6 +157,39 @@ func (e FormFactory) Deserialize(ctx serde.Context, data []byte) (serde.Message, return message, nil } +// FormFromStore returns a form from the store given the formIDHex. +// An error indicates a wrong storage of the form. +func FormFromStore(ctx serde.Context, formFac serde.Factory, formIDHex string, + store store.Readable) (Form, error) { + + form := Form{} + + formIDBuf, err := hex.DecodeString(formIDHex) + if err != nil { + return form, xerrors.Errorf("failed to decode formIDHex: %v", err) + } + + formBuff, err := store.Get(formIDBuf) + if err != nil { + return form, xerrors.Errorf("while getting data for form: %v", err) + } + if len(formBuff) == 0 { + return form, xerrors.Errorf("no form found") + } + + message, err := formFac.Deserialize(ctx, formBuff) + if err != nil { + return form, xerrors.Errorf("failed to deserialize Form: %v", err) + } + + form, ok := message.(Form) + if !ok { + return form, xerrors.Errorf("wrong message type: %T", message) + } + + return form, nil +} + // ChunksPerBallot returns the number of chunks of El Gamal pairs needed to // represent an encrypted ballot, knowing that one chunk is 29 bytes at most. func (e *Form) ChunksPerBallot() int { @@ -146,6 +200,89 @@ func (e *Form) ChunksPerBallot() int { return e.BallotSize/29 + 1 } +// CastVote stores the new vote in the memory. +func (s *Form) CastVote(ctx serde.Context, st store.Snapshot, userID string, ciphervote Ciphervote) error { + var suff Suffragia + var batchID []byte + + if s.BallotCount%BallotsPerBatch == 0 { + // Need to create a random ID for storing the ballots. + // H( formID | ballotcount ) + // should be random enough, even if it's previsible. + id, err := hex.DecodeString(s.FormID) + if err != nil { + return xerrors.Errorf("couldn't decode formID: %v", err) + } + h := sha256.New() + h.Write(id) + binary.LittleEndian.PutUint32(id, s.BallotCount) + batchID = h.Sum(id[0:4])[:32] + + err = st.Set(batchID, []byte{}) + if err != nil { + return xerrors.Errorf("couldn't store new ballot batch: %v", err) + } + s.SuffragiaStoreKeys = append(s.SuffragiaStoreKeys, batchID) + s.SuffragiaHashes = append(s.SuffragiaHashes, []byte{}) + } else { + batchID = s.SuffragiaStoreKeys[len(s.SuffragiaStoreKeys)-1] + buf, err := st.Get(batchID) + if err != nil { + return xerrors.Errorf("couldn't get ballots batch: %v", err) + } + format := suffragiaFormat.Get(ctx.GetFormat()) + ctx = serde.WithFactory(ctx, CiphervoteKey{}, CiphervoteFactory{}) + msg, err := format.Decode(ctx, buf) + if err != nil { + return xerrors.Errorf("couldn't unmarshal ballots batch in cast: %v", err) + } + suff = msg.(Suffragia) + } + + suff.CastVote(userID, ciphervote) + if TestCastBallots { + for i := uint32(1); i < BallotsPerBatch; i++ { + suff.CastVote(fmt.Sprintf("%s-%d", userID, i), ciphervote) + } + s.BallotCount += BallotsPerBatch - 1 + } + buf, err := suff.Serialize(ctx) + if err != nil { + return xerrors.Errorf("couldn't marshal ballots batch: %v", err) + } + err = st.Set(batchID, buf) + if err != nil { + xerrors.Errorf("couldn't set new ballots batch: %v", err) + } + s.BallotCount += 1 + return nil +} + +// Suffragia returns all ballots from the storage. This should only +// be called rarely, as it might take a long time. +// It overwrites ballots cast by the same user and keeps only +// the latest ballot. +func (s *Form) Suffragia(ctx serde.Context, rd store.Readable) (Suffragia, error) { + var suff Suffragia + for _, id := range s.SuffragiaStoreKeys { + buf, err := rd.Get(id) + if err != nil { + return suff, xerrors.Errorf("couldn't get ballot batch: %v", err) + } + format := suffragiaFormat.Get(ctx.GetFormat()) + ctx = serde.WithFactory(ctx, CiphervoteKey{}, CiphervoteFactory{}) + msg, err := format.Decode(ctx, buf) + if err != nil { + return suff, xerrors.Errorf("couldn't unmarshal ballots batch in cast: %v", err) + } + suffTmp := msg.(Suffragia) + for i, uid := range suffTmp.UserIDs { + suff.CastVote(uid, suffTmp.Ciphervotes[i]) + } + } + return suff, nil +} + // RandomVector is a slice of kyber.Scalar (encoded) which is used to prove // and verify the proof of a shuffle type RandomVector [][]byte @@ -197,8 +334,9 @@ type ShuffleInstance struct { // Configuration contains the configuration of a new poll. type Configuration struct { - MainTitle string - Scaffold []Subject + Title Title + Scaffold []Subject + AdditionalInfo string } // MaxBallotSize returns the maximum number of bytes required to store a ballot @@ -239,80 +377,6 @@ func (c *Configuration) IsValid() bool { return true } -type Suffragia struct { - UserIDs []string - Ciphervotes []Ciphervote -} - -// CastVote adds a new vote and its associated user or updates a user's vote. -func (s *Suffragia) CastVote(userID string, ciphervote Ciphervote) { - for i, u := range s.UserIDs { - if u == userID { - s.Ciphervotes[i] = ciphervote - return - } - } - - s.UserIDs = append(s.UserIDs, userID) - s.Ciphervotes = append(s.Ciphervotes, ciphervote.Copy()) -} - -// CiphervotesFromPairs transforms two parallel lists of EGPoints to a list of -// Ciphervotes. -func CiphervotesFromPairs(X, Y [][]kyber.Point) ([]Ciphervote, error) { - if len(X) != len(Y) { - return nil, xerrors.Errorf("X and Y must have same length: %d != %d", - len(X), len(Y)) - } - - if len(X) == 0 { - return nil, xerrors.Errorf("ElGamal pairs are empty") - } - - NQ := len(X) // sequence size - k := len(X[0]) // number of votes - res := make([]Ciphervote, k) - - for i := 0; i < k; i++ { - x := make([]kyber.Point, NQ) - y := make([]kyber.Point, NQ) - - for j := 0; j < NQ; j++ { - x[j] = X[j][i] - y[j] = Y[j][i] - } - - ciphervote, err := ciphervoteFromPairs(x, y) - if err != nil { - return nil, xerrors.Errorf("failed to init from ElGamal pairs: %v", err) - } - - res[i] = ciphervote - } - - return res, nil -} - -// ciphervoteFromPairs transforms two parallel lists of EGPoints to a list of -// ElGamal pairs. -func ciphervoteFromPairs(ks []kyber.Point, cs []kyber.Point) (Ciphervote, error) { - if len(ks) != len(cs) { - return Ciphervote{}, xerrors.Errorf("ks and cs must have same length: %d != %d", - len(ks), len(cs)) - } - - res := make(Ciphervote, len(ks)) - - for i := range ks { - res[i] = EGPair{ - K: ks[i], - C: cs[i], - } - } - - return res, nil -} - // Pubshare represents a public share. type Pubshare kyber.Point diff --git a/contracts/evoting/types/suffragia.go b/contracts/evoting/types/suffragia.go new file mode 100644 index 000000000..c8a507440 --- /dev/null +++ b/contracts/evoting/types/suffragia.go @@ -0,0 +1,119 @@ +package types + +import ( + "crypto/sha256" + + "go.dedis.ch/dela/serde" + "go.dedis.ch/dela/serde/registry" + "go.dedis.ch/kyber/v3" + "golang.org/x/xerrors" +) + +// suffragiaFormat contains the supported formats for the form. Right now +// only JSON is supported. +var suffragiaFormat = registry.NewSimpleRegistry() + +// RegisterSuffragiaFormat registers the engine for the provided format +func RegisterSuffragiaFormat(format serde.Format, engine serde.FormatEngine) { + suffragiaFormat.Register(format, engine) +} + +type Suffragia struct { + UserIDs []string + Ciphervotes []Ciphervote +} + +// Serialize implements the serde.Message +func (s Suffragia) Serialize(ctx serde.Context) ([]byte, error) { + format := suffragiaFormat.Get(ctx.GetFormat()) + + data, err := format.Encode(ctx, s) + if err != nil { + return nil, xerrors.Errorf("failed to encode form: %v", err) + } + + return data, nil +} + +// CastVote adds a new vote and its associated user or updates a user's vote. +func (s *Suffragia) CastVote(userID string, ciphervote Ciphervote) { + for i, u := range s.UserIDs { + if u == userID { + s.Ciphervotes[i] = ciphervote + return + } + } + + s.UserIDs = append(s.UserIDs, userID) + s.Ciphervotes = append(s.Ciphervotes, ciphervote.Copy()) +} + +// Hash returns the hash of this list of ballots. +func (s *Suffragia) Hash(ctx serde.Context) ([]byte, error) { + h := sha256.New() + for i, u := range s.UserIDs { + h.Write([]byte(u)) + buf, err := s.Ciphervotes[i].Serialize(ctx) + if err != nil { + return nil, xerrors.Errorf("couldn't serialize ciphervote: %v", err) + } + h.Write(buf) + } + return h.Sum(nil), nil +} + +// CiphervotesFromPairs transforms two parallel lists of EGPoints to a list of +// Ciphervotes. +func CiphervotesFromPairs(X, Y [][]kyber.Point) ([]Ciphervote, error) { + if len(X) != len(Y) { + return nil, xerrors.Errorf("X and Y must have same length: %d != %d", + len(X), len(Y)) + } + + if len(X) == 0 { + return nil, xerrors.Errorf("ElGamal pairs are empty") + } + + NQ := len(X) // sequence size + k := len(X[0]) // number of votes + res := make([]Ciphervote, k) + + for i := 0; i < k; i++ { + x := make([]kyber.Point, NQ) + y := make([]kyber.Point, NQ) + + for j := 0; j < NQ; j++ { + x[j] = X[j][i] + y[j] = Y[j][i] + } + + ciphervote, err := ciphervoteFromPairs(x, y) + if err != nil { + return nil, xerrors.Errorf("failed to init from ElGamal pairs: %v", err) + } + + res[i] = ciphervote + } + + return res, nil +} + +// ciphervoteFromPairs transforms two parallel lists of EGPoints to a list of +// ElGamal pairs. +func ciphervoteFromPairs(ks []kyber.Point, cs []kyber.Point) (Ciphervote, error) { + if len(ks) != len(cs) { + return Ciphervote{}, xerrors.Errorf("ks and cs must have same length: %d != %d", + len(ks), len(cs)) + } + + res := make(Ciphervote, len(ks)) + + for i := range ks { + res[i] = EGPair{ + K: ks[i], + C: cs[i], + } + } + + return res, nil +} diff --git a/contracts/evoting/types/transactions.go b/contracts/evoting/types/transactions.go index 12d39738d..48ff3733d 100644 --- a/contracts/evoting/types/transactions.go +++ b/contracts/evoting/types/transactions.go @@ -6,8 +6,8 @@ import ( "io" "strconv" - "github.com/c4dt/dela/serde" - "github.com/c4dt/dela/serde/registry" + "go.dedis.ch/dela/serde" + "go.dedis.ch/dela/serde/registry" "golang.org/x/xerrors" ) diff --git a/deb-package/README.md b/deb-package/README.md index ef6ec6462..a6222c9f3 100644 --- a/deb-package/README.md +++ b/deb-package/README.md @@ -170,9 +170,9 @@ To be done on each node. PK=<> # taken from the "ordering export", the part after ":" sudo dvoting --config /var/opt/dedis/dvoting/data/dela pool add \ --key $keypath \ - --args github.com/c4dt/dela.ContractArg --args github.com/c4dt/dela.Access \ - --args access:grant_id --args 0300000000000000000000000000000000000000000000000000000000000000 \ - --args access:grant_contract --args github.com/c4dt/dela.Evoting \ + --args go.dedis.ch/dela.ContractArg --args go.dedis.ch/dela.Access \ + --args access:grant_id --args 45564f54 \ + --args access:grant_contract --args go.dedis.ch/dela.Evoting \ --args access:grant_command --args all \ --args access:identity --args $PK \ --args access:command --args GRANT @@ -198,7 +198,7 @@ sudo apt install rubygems build-essential git ## Get the code ```sh -git clone https://github.com/c4dt/d-voting.git +git clone https://github.com/dedis/d-voting.git ``` ## Build the deb package diff --git a/deb-package/build-deb.sh b/deb-package/build-deb.sh index 0a61454ec..a374e11fa 100755 --- a/deb-package/build-deb.sh +++ b/deb-package/build-deb.sh @@ -45,7 +45,7 @@ fpm \ --after-install pkg/after-install.sh \ --before-remove pkg/before-remove.sh \ --after-remove pkg/after-remove.sh \ - --url https://dedis.github.com/c4dt/dvoting \ + --url https://dedis.github.com/dedis/dvoting \ --description 'D-Voting package' \ --package dist . diff --git a/docker-compose/docker-compose.debug.yml b/docker-compose/docker-compose.debug.yml index 66a1ea7f5..642823d52 100644 --- a/docker-compose/docker-compose.debug.yml +++ b/docker-compose/docker-compose.debug.yml @@ -65,7 +65,7 @@ services: ipv4_address: 172.19.44.251 frontend: # web service frontend - image: ghcr.io/c4dt/d-voting-frontend:latest + image: ghcr.io/dedis/d-voting-frontend:latest build: dockerfile: Dockerfiles/Dockerfile.frontend context: ../ @@ -83,7 +83,7 @@ services: ipv4_address: 172.19.44.2 backend: # web service backend - image: ghcr.io/c4dt/d-voting-backend:latest + image: ghcr.io/dedis/d-voting-backend:latest build: dockerfile: Dockerfiles/Dockerfile.backend context: ../ diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index 2e86e7634..88fd088e9 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -1,6 +1,6 @@ version: "3.8" x-dela: &dela - image: ghcr.io/c4dt/d-voting-dela:latest + image: ghcr.io/dedis/d-voting-dela:latest env_file: ../.env x-dela-env: &dela-env PROXYKEY: ${PUBLIC_KEY} @@ -55,7 +55,7 @@ services: ipv4_address: 172.19.44.251 frontend: # web service frontend - image: ghcr.io/c4dt/d-voting-frontend:latest + image: ghcr.io/dedis/d-voting-frontend:latest build: dockerfile: Dockerfiles/Dockerfile.frontend context: ../ @@ -67,7 +67,7 @@ services: ipv4_address: 172.19.44.2 backend: # web service backend - image: ghcr.io/c4dt/d-voting-backend:latest + image: ghcr.io/dedis/d-voting-backend:latest build: dockerfile: Dockerfiles/Dockerfile.backend context: ../ diff --git a/docs/ballot_encoding.md b/docs/ballot_encoding.md index 84b105122..6e1f69e47 100644 --- a/docs/ballot_encoding.md +++ b/docs/ballot_encoding.md @@ -13,7 +13,7 @@ The answers to questions are encoded in the following way, with one question per TYPE = "select"|"text"|"rank" SEP = ":" -ID = 3 bytes, encoded in base64 +ID = 8 bytes UUID encoded in base64 = 12 bytes ANSWERS = [","]* ANSWER = || SELECT_ANSWER = "0"|"1" @@ -39,11 +39,11 @@ For the following questions : A possible encoding of an answer would be (by string concatenation): ``` -"select:3fb2:0,0,0,1,0\n" + +"select:base64(D0Da4H6o):0,0,0,1,0\n" + -"rank:19c7:0,1,2\n" + +"rank:base64(19c7cd13):0,1,2\n" + -"text:cd13:base64("Noémien"),base64("Pierluca")\n" +"text:base64(wSfBs25a):base64("Noémien"),base64("Pierluca")\n" ``` ## Size of the ballot @@ -53,15 +53,15 @@ voting process, it is important that all encrypted ballots have the same size. T the form has an attribute called "BallotSize" which is the size that all ballots should have before they're encrypted. Smaller ballots should therefore be padded in order to reach this size. To denote the end of the ballot and the start of the padding, -we use an empty line (\n\n). For a ballot size of 117, our ballot from the previous example +we use an empty line (\n\n). For a ballot size of 144, our ballot from the previous example would then become: ``` -"select:3fb2:0,0,0,1,0\n" + +"select:base64(D0Da4H6o):0,0,0,1,0\n" + -"rank:19c7:0,1,2\n" + +"rank:base64(19c7cd13):0,1,2\n" + -"text:cd13:base64("Noémien"),base64("Pierluca")\n\n" + +"text:base64(wSfBs25a):base64("Noémien"),base64("Pierluca")\n\n" + "ndtTx5uxmvnllH1T7NgLORuUWbN" ``` @@ -70,4 +70,4 @@ would then become: The encoded ballot must then be divided into chunks of 29 or less bytes since the maximum size supported by the kyber library for the encryption is of 29 bytes. -For the previous example we would then have 5 chunks, the first 4 would contain 29 bytes, while the last chunk would contain a single byte. +For the previous example we would then have 5 chunks, the first 4 would contain 29 bytes, while the last chunk would contain 28 bytes. diff --git a/docs/coverpage.md b/docs/coverpage.md index 4462113e3..df717a4c8 100644 --- a/docs/coverpage.md +++ b/docs/coverpage.md @@ -5,7 +5,7 @@

- +

- An open platform to run voting instances on a blockchain diff --git a/docs/index.html b/docs/index.html index 4a414ee1a..1fd7617bc 100644 --- a/docs/index.html +++ b/docs/index.html @@ -13,7 +13,7 @@