Jormungandr supports decentralized voting with privacy features.

The voting process is controlled by a committee whose private keys can be used to decrypt and certify the tally.

Creating committee keys


Please refer to jcli votes committee --help for help with the committee related cli operations and specification of arguments.

In this example we will be using 3 kind of keys for the private vote and tallying.

In order:

Committee communication key

jcli votes committee communication-key generate > ./

We can get its public representation with:

jcli votes committee communication-key to-public --input ./ > ./

Committee member key

jcli votes committee member-key generate --threshold 3 --crs "$crs" --index 0 --keys pk1 pk2 pk3 > ./

Where pkX are each of the committee communication public keys in bech32 format. The order of the keys shall be the same for every member invoking the command, and the --index parameter provides the 0-based index of the member this key is generated for. Note that all committee members shall use the same CRS.

We can also easily get its public representation as before:

jcli votes committee member-key to-public --input ./ ./

Election public key

This key (public) is the key every vote should be encrypted with.

jcli votes election-key --keys mpk1 mpk2 mpk3 > ./

Notice that we can always rebuild this key with the committee member public keys found within the voteplan certificate.

jcli rest v0 vote active plans > voteplan.json

Creating a vote plan

We need to provide a vote plan definition file to generate a new voteplan certificate. That file should be a yaml (or json) with the following format:

  "payload_type": "private",
  "vote_start": {
    "epoch": 1,
    "slot_id": 0
  "vote_end": {
    "epoch": 3,
    "slot_id": 0
  "committee_end": {
    "epoch": 6,
    "slot_id": 0
  "proposals": [
      "external_id": "d7fa4e00e408751319c3bdb84e95fd0dcffb81107a2561e691c33c1ae635c2cd",
      "options": 3,
      "action": "off_chain"
  "committee_member_public_keys": [


  • payload_type is either public or private
  • commitee_public_keys is only needed for private voting, can be empty for public.

Then, we can generate the voteplan certificate with:

jcli certificate new vote-plan voteplan_def.json --output voteplan.certificate

Casting votes

To generate a vote cast transaction:

  1. firstly you need to generate vote-cast certificate following this instructions.
  2. Storing it into the ’vote-cast.certificate`
  3. now you can generate a transaction following this intructions.

Note that a valid vote cast transaction MUST have only:

  • one input with the corresponding account of the voter
  • zero outputs
  • 1 corresponding witness.

Example ( contains a private key of the voter):

genesis_block_hash=$(jcli genesis hash < block0.bin)
vote_plan_id=$(jcli rest v0 vote active plans get --output-format json|jq '.[0].id')
voter_addr=$(jcli address account $(jcli key to-public <
voter_addr_counter=$(jcli rest v0 account get "$committee_addr" --output-format json|jq .counter)
jcli certificate new vote-cast public --choice 0 --proposal-index 0 --vote-plan-id "$vote_plan_id" --output vote-cast.certificate
jcli transaction new --staging vote-cast.staging
jcli transaction add-account "$committee_addr" 0 --staging vote-cast.staging
jcli transaction add-certificate $(< vote-cast.certificate) --staging vote-cast.staging
jcli transaction finalize --staging vote-cast.staging
jcli transaction data-for-witness --staging vote-cast.staging > vote-cast.witness-data
jcli transaction make-witness --genesis-block-hash "$genesis_block_hash" --type account --account-spending-counter
"$voter_addr_counter" $(< vote-cast.witness-data) vote-cast.witness
jcli transaction seal --staging vote-cast.staging
jcli transaction to-message --staging vote-cast.staging > vote-cast.fragment
jcli rest v0 message post --file vote-cast.fragment


Public vote plan

To tally public votes, a single committee member is sufficient. In the example below, the file contains the committee member’s private key in bech32 format, and block0.bin contains the genesis block of the voting chain.

genesis_block_hash=$(jcli genesis hash < block0.bin)
vote_plan_id=$(jcli rest v0 vote active plans get --output-format json|jq '.[0].id')
committee_addr=$(jcli address account $(jcli key to-public <
committee_addr_counter=$(jcli rest v0 account get "$committee_addr" --output-format json|jq .counter)
jcli certificate new vote-tally --vote-plan-id "$vote_plan_id" --output vote-tally.certificate
jcli transaction new --staging vote-tally.staging
jcli transaction add-account "$committee_addr" 0 --staging vote-tally.staging
jcli transaction add-certificate $(< vote-tally.certificate) --staging vote-tally.staging
jcli transaction finalize --staging vote-tally.staging
jcli transaction data-for-witness --staging vote-tally.staging > vote-tally.witness-data
jcli transaction make-witness --genesis-block-hash "$genesis_block_hash" --type account --account-spending-counter \
  "$committee_addr_counter" $(< vote-tally.witness-data) vote-tally.witness
jcli transaction add-witness --staging vote-tally.staging vote-tally.witness
jcli transaction seal --staging vote-tally.staging
jcli transaction auth --staging vote-tally.staging --key
jcli transaction to-message --staging vote-tally.staging > vote-tally.fragment
jcli rest v0 message post --file vote-tally.fragment

Private vote plan

To tally private votes, all committee members are needed. The process is similar to the public one, but we need to issue different certificates.

First, we need to retrieve vote plans info:

jcli rest v0 vote active plans > active_plans.json

If there is more than one vote plan in the file, we also need to provide the id of the vote plan we are interested in. We can get the id of the first vote plan with:

vote_plan_id=$(cat active_plans.json |jq '.[0].id')

Each committee member needs to generate their shares for the vote plan, which we will use later to decrypt the tally.

jcli votes tally decryption-shares --vote-plan active_plans.json --vote-plan-id $"vote_plan_id" --key --output-format json

Then, the committee members need to exchange their shares (only one full set of shares is needed). Once all shares are available, we need to merge them in a single file with the following command (needed even if there is only one set of shares):

jcli votes tally merge-shares  share_file1 share_file2 ... > merged_shares.json

With the merged shares file, we are finally able to process the final tally result as follows:

jcli votes tally decrypt-results \
--vote-plan active_plans.json \
--vote-plan-id $"vote_plan_id" \
--shares merged_shares.json \
--threshold number_of_committee_members \
--output-format json > result.json