{"id":15010624,"url":"https://github.com/nodejs/caritat","last_synced_at":"2025-10-19T22:31:02.042Z","repository":{"id":57095270,"uuid":"449053520","full_name":"nodejs/caritat","owner":"nodejs","description":"A voting software for remote teams","archived":false,"fork":false,"pushed_at":"2024-10-07T03:50:12.000Z","size":956,"stargazers_count":5,"open_issues_count":8,"forks_count":4,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-10-29T17:21:46.049Z","etag":null,"topics":["caritat","shamir-secret-sharing","vote"],"latest_commit_sha":null,"homepage":"https://nodejs.github.io/caritat/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nodejs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-01-17T21:27:41.000Z","updated_at":"2024-07-01T18:28:12.000Z","dependencies_parsed_at":"2024-04-12T11:21:17.685Z","dependency_job_id":"f77b0746-4956-48d0-bc92-577e78b94312","html_url":"https://github.com/nodejs/caritat","commit_stats":{"total_commits":457,"total_committers":5,"mean_commits":91.4,"dds":"0.39387308533916854","last_synced_commit":"376fd8e36f4f01c8db9cc5ba5faa8ca45259eac2"},"previous_names":["aduh95/caritat","nodejs/caritat","stduhpf/caritat"],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nodejs%2Fcaritat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nodejs%2Fcaritat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nodejs%2Fcaritat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nodejs%2Fcaritat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nodejs","download_url":"https://codeload.github.com/nodejs/caritat/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":237221166,"owners_count":19274447,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["caritat","shamir-secret-sharing","vote"],"created_at":"2024-09-24T19:35:07.753Z","updated_at":"2025-10-19T22:30:56.616Z","avatar_url":"https://github.com/nodejs.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Caritat\n\nThe name comes from Marie Jean Antoine Nicolas de Caritat, Marquis of Condorcet,\nFrench philosopher and mathematician, notably known for championing an election\nmethod that now named after him.\n\nThe goal of this project is to allow organisations of people working remotely to\ncast votes is a secure and transparent way, using a git repository to collect and\nauthenticate votes.\n\n## Usage\n\n### Participate to a vote using Caritat\n\n#### Node.js CLI\n\nRequires [Node.js](https://nodejs.org) 16+ and [git](https://git-scm.com).\n\nIf the vote is setup on a GitHub pull request and you have\n[`gh`](https://cli.github.com) locally installed and logged in to your GitHub\naccount:\n\n```sh\nnpx --package=@node-core/caritat-cli voteOnGitHub \u003cpr-url\u003e\n```\n\nOtherwise, you can specify the details manually:\n\n```sh\nnpx --package=@node-core/caritat-cli voteUsingGit \\\n  --repo=\u003crepo-url\u003e --branch=\u003cbranch-name\u003e \\\n  --path=\u003csubfolder-where-the-vote-data-is-stored\u003e \\\n  --handle=\u003cyour-github-handle\u003e\n```\n\n#### Shell scripts\n\nYou can use one of the shell script from the `sh/` folder. Requires `openssl`\n(LibreSSL CLI is not compatible) and `git` to be available on the local machine.\n\nOn a Unix-like OS:\n\n```sh\nsh/voteUsingGit.sh \u003cyour-github-handle\u003e \u003crepo-url\u003e \u003cbranch-name\u003e \u003csubfolder-where-the-vote-data-is-stored\u003e\n```\n\nOn Windows:\n\n```sh\nsh/voteUsingGit.ps1 \u003cyour-github-handle\u003e \u003crepo-url\u003e \u003cbranch-name\u003e \u003csubfolder-where-the-vote-data-is-stored\u003e\n```\n\n#### Web UI\n\nOnly works for vote that are hosted on a GitHub pull request.\n\nVisit \u003chttps://nodejs.github.io/caritat/\u003e, paste the URL of the pull request,\nand create the JSON file containing your encrypted ballot using GitHub web UI.\n\n### Setup a vote using Caritat\n\n#### Node.js script\n\n```sh\nnpx --package=@node-core/caritat-cli generateNewVoteFolder \\\n --repo \"\u003crepo-url\u003e\" --branch \"\u003cnew-vote-branch-name\u003e\" \\\n --directory \"\u003crelative-path-to-new-vote-folder\u003e\" \\\n --subject \"Vote subject\" \\\n --candidate \"Candidate 1\" --candidate \"etc.\" \\\n --allowed-voter \"voter@example.com\" --allowed-voter \"etc@example.com\" \\\n --shareholder \"shareholder@example.com\" --shareholder \"etc@example.com\" \\\n --shareholders-threshold 2\n```\n\n#### Shell script / `git`-less setup\n\nRequires `openssl` (LibreSSL CLI is not compatible), `gpg`, and `node`.\nUse the `sh/generateNewVoteFolder.sh` script.\n\n```sh\nsh/generateNewVoteFolder.sh \u003cpath-to-dir\u003e\n```\n\nThis will generate three files that will be used to let participants vote. You\ncan then commit those files and push this to a new branch (optionally open the\nvote pull request). If you are participating to that vote yourself, you should\ncast your vote right away using one of the methods described above.\n\n### Using the API\n\nYou can build your own CLI to interface with Caritat so it's more fitted to your\nuse-case and is more user-friendly.\n\nSince we are using TypeScript, please rely on the type definition for\ndocumentation on how to use the API.\n\n## FAQ\n\n### Who do I need to trust?\n\n- As a Voter, you need to trust the Vote Instigator for:\n  - using a reliable hardware and software to generate the Vote Private Key and\n    encrypt it, and to not store it anywhere.\n  - not leaking the Vote Private Key before the vote closes if they have kept it.\n  - not basing their vote in function of what other has voted (having the\n    instigator always vote first helps alleviate this issue).\n- As a Voter, you need to trust the panel of Secret Holders for:\n  - not reconstitue the Vote Private Key before the vote closes.\n  - not leaking the Vote Private Key before the vote closes (if they have\n    reconstructed it, which they should not do).\n  - not basing their vote in function of what other has voted (if they have\n    reconstructed the Vote Private Key, which they should not do).\n- As a Voter or the Vote Instigator, you need to trust the git commits are genuine,\n  and therefor you need to trust that the server hosting the vote repository is\n  not compromised.\n\n### Can a participant tamper with the votes?\n\nWhen using git, one could force push (or otherwise alter the git tree if they\nhave direct access to the server) the branch and remove or modify\nballots from other participants. Adding protection on the branch on which the\nvote is happening can help prevent this.\n\n### When voting using this tool, are my choices public?\n\nBallots are encrypted using a public key generated for the vote, only someone in\npossession for the Vote Private Key is theoretically able to decipher the ballot.\nTypically, no one should be in possession of the Vote Prive Key (although\nthere is no way of ensuring that, see \"Who do I need to trust?\" section) until\nthe vote closes. Unless the vote needs to stay private, a recommended practice\nis to publish the Vote Private Key, effectively making everyone's choices\npublic.\n\nMaking the non-encrypted ballot available publicly is a great way to ensure the\nelection was not rigged. Everyone can check that the ballot counted as their has\nnot been altered and that the result adds up. It's still possible to not make\nthem public (to keep the vote anonymous), but that requires to trust a single\nauthority (the Vote Instigator).\n\n### How is a vote initialized?\n\nWhen setting up the vote, the Vote Instigator creates the following keys:\n\n- the Vote Private Key (RSA 2048 bits)\n- the Vote Public Key (derived from the Vote Private Key)\n- the Vote Secret (a random binary string)\n- the Vote Secret Key Parts (derived from the Vote Secret, as many key parts as\n  there are Secret Holders)\n\nThen a `vote.yml` file is created:\n\n- the Vote Public Key,\n- the Vote Private Key encrypted using the Vote Secret,\n- the Vote Secret Key Parts encrypted using PGP (each key part is encrypted\n  using Secret Holder public key),\n- a list of candidates (only those candidates are allowed in a ballot),\n- a list of allowed Voters,\n- a vote subject, header and footer instructions to give more context to the\n  voter directly in the ballot,\n- the method to count the ballots (only Condorcet is supported at the time of\n  writing),\n- miscellaneous vote options, such as `canShuffleCandidates`.\n\nThe Vote Instigator pushes to a newly created vote branch (when using git) the\n`vote.yml`, as well as a file containing the public key and a ballot example.\nThe two other files can be used to vote without parsing the YAML file.\n\n### Why are the ballots encrypted?\n\nEncrypting the ballot is necessary to ensure people voting early do not\ninterfere or influence folks voting after them. At the end of the vote, the\nthe Vote Private Key can be made public, so anyone can decrypt the ballots and verify\nthe result themself. Or it can decided that the Vote Private Key won't be shared in\norder to keep the votes anonymous, and a large enough panel of Secret Holders\n(depending on the vote settings) need to share their key parts, decrypt the\nballots, and share the vote result without disclosing the content of the ballots.\n\n### What's inside an encrypted ballot?\n\nAn encrypted ballot is a JSON object which contains at least two keys:\n\n- `encryptedSecret`: the Ballot Secret encrypted using the Vote Public Key.\n- `data`: the YAML ballot, encrypted using the Ballot Secret.\n\nThere could be other keys in that JSON object, they will be ignored.\n\n### How are the votes authenticated?\n\nVoters can sign their commit using PGP. When doing the counting, the system uses\nthe git commit metadata to attribute a ballot to a voter. If a voter casts\nseveral ballots, the system only counts the most recent one.\n\n### What happens if the Secret Holders lose their key parts?\n\nThe vote ballots cannot be deciphered, the process needs to start again (unless\nyou have a quantum computer at home to break the RSA encryption).\n\n### Could this tool used for any elections?\n\nThe license makes no restrictions on how this tool should be used, but keep in\nmind that, as any electronic voting system, it can only be trusted as long as\nthe unanonymized vote ballots are made public as soon as the vote closes, which\nmay or may not be OK depending on the type of election you are using this for.\n\n## How does the tool pick the winner?\n\nThe default vote counting system is the Condorcet method (preferential ranked votes). This repository is probably not the best place to\nlearn about vote theory, but all you need to know it's the best option to pick an option that\nmajority of voters will agree with.\n\nLet's for the sake of simplification take a situation where you are the only voter, and there are\nthree candidates (A, B, and C). You set score 100000 to candidate A, score -100000 to candidate B,\nand 0 to candidate C. Here's how Caritat will count the votes:\n\n1. Caritat puts up A against B, see that you set a higher score to A, and count your vote for A.\n   Because in this example, you're the only one voter, there's no other ballot to count.\n   _A gets 1 vote, B gets 0 votes, A wins the duel against B._\n2. Caritat puts up B against C, see that you set a higher score to C, and count your vote for C.\n   _C gets 1 vote, B gets 0 votes, C wins the duel against B._\n3. Caritat puts up A against C, see that you set a higher score to A, and count your vote for A.\n   _A gets 1 vote, C gets 0 votes, A wins the duel against C._\n\nCaritat will show the following table of result:\n\n| Candidate | Number of won duels |\n| --------- | ------------------- |\n| A         | 2                   |\n| B         | 0                   |\n| C         | 1                   |\n\nThe winner is _A_, as it is the candidate that wins the most duels (also called the Condorcet winner).\n\nIt's worth noting that the exact numbers on the ballot (like 100000, 0, and -100000) \ndon't change the result of the vote. What really matters is how these numbers stack up \nagainst the other candidates' numbers in your ballot: if they are bigger, smaller, or the\nsame. The numbers used in the other ballots have no effect on how your ballot is counted.\n\nIn practice, there will be probably more than one voter, and potentially more than three candidates,\nbut the same principles apply: each pair of candidates is evaluated, the candidate that wins the\nmost duels is the winner.\n\nSometimes, the vote result can include more than one winner:\n\n- It's possible to get an exact equality between two options (e.g. 3 voters prefer A against B, 9\n  voters are indifferent, 3 prefer B against A), in which case the duel is won by neither option.\n  Having an equality is not really a big deal, unless there are no other candidate that wins against\n  both options. In that case, Caritat will show more than one winner. At this point, re-running the\n  vote should yield the same result, unless the list of voter changes.\n- Another situation would be a Condorcet triangle (e.g. A wins against B, B wins against C, and C\n  wins against A). As in love, those triangles are unlikely to get resolved in a way that keeps\n  everyone happy.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnodejs%2Fcaritat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnodejs%2Fcaritat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnodejs%2Fcaritat/lists"}