From 6c9d0813c0f5639f828d7e038de37e29b38d085f Mon Sep 17 00:00:00 2001 From: k-deejah Date: Wed, 29 Jul 2026 03:39:46 -1100 Subject: [PATCH] fix(types): audit and align shared types, document canonical source (#27) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - README: fix stale encryptVote description (base64 -> EncryptedPayload hex object) - README: replace one-line types description with full canonical-source note explaining that AnonVote/core should import from @anonvote/crypto rather than defining local copies in core/shared/ - README: add types table listing all exported types with descriptions - README: update usage example to use EncryptedPayload object for encryptVote/decryptVote (matches actual API from issue #39) - README: add DECISIONS.md and client.ts to repo structure diagram - README: add contributor callout directing core away from local type copies Note: ElectionResult, BallotEvent, Token, Vote, AnonVoteCryptoError and all other required types were already added to src/types.ts and fully exported from src/index.ts in the fix/issue-39 branch. No type additions or removals needed — only documentation alignment. --- README.md | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8904b43..51ae771 100644 --- a/README.md +++ b/README.md @@ -29,12 +29,26 @@ This package is the canonical source of all crypto and token logic used across t | `hashIdentifier(id)` | SHA-256 hash of a voter identifier. Trims and lowercases before hashing. Never store originals — only hashes. | | `generateToken()` | Generates a 32-byte (256-bit) CSPRNG token as a hex string. Used for one-time voter tokens. | | `hashToken(token)` | SHA-256 hash of a raw token. Only the hash is ever persisted — the raw value is given to the voter and discarded. | -| `encryptVote(optionId, key)` | AES-256-GCM encryption of a vote option ID. Returns `iv:authTag:ciphertext` in base64. Requires a 32-byte hex key. | +| `encryptVote(optionId, key)` | AES-256-GCM encryption of a vote option ID. Returns an `EncryptedPayload` object with `iv`, `authTag`, and `ciphertext` — all lowercase hex strings (see `DECISIONS.md`). Requires a 32-byte hex key. | | `decryptVote(payload, key)` | Decrypts a vote payload produced by `encryptVote`. Used only by the result tally engine. | ### Types (`src/types.ts`) -Core shared TypeScript types for votes, tokens, ballots, and audit events — used by both the backend API and any future client SDKs. +`src/types.ts` is the **canonical type source for the entire AnonVote ecosystem**. All shared TypeScript types — votes, tokens, ballots, audit events, and tally results — are defined here and exported from this package. `AnonVote/core` and any future consumer **should import from `@anonvote/crypto`** rather than maintaining local copies. Defining types locally in `core/shared/` causes silent drift: a field rename in one place does not break the other at compile time and only fails at runtime. + +Key types exported: + +| Type | Description | +| ---- | ----------- | +| `EncryptedPayload` | AES-256-GCM output: `{ ciphertext, iv, authTag }` — all hex strings | +| `Token` | Token pair: `{ value, hash }` — raw value for voter, hash for storage | +| `Vote` | Ballot vote event: `{ ballotId, optionId, timestamp }` | +| `ElectionResult` | Tally output: `Record` | +| `BallotEvent` | Stellar audit trail event with `event_type`, `ballot_id`, `stellar_tx_id`, `created_at` | +| `AnonVoteCryptoError` | Typed error class with `code` field (`INVALID_KEY`, `DECRYPTION_FAILED`, `INVALID_PAYLOAD`) | +| `Ballot`, `Option`, `BallotStatus` | Core ballot domain types | +| `VoterToken`, `EligibilityEntry`, `EligibilityList` | Token and eligibility record types | +| `VoteRecord`, `Result`, `AuditEvent`, `AuditCounts` | Persistence and result types | --- @@ -55,6 +69,7 @@ import { hashToken, encryptVote, decryptVote, + type EncryptedPayload, } from "@anonvote/crypto"; // Hash a voter identifier before storing @@ -64,12 +79,12 @@ const identifierHash = hashIdentifier("alice@example.com"); const rawToken = generateToken(); // give this to the voter const storedHash = hashToken(rawToken); // store only this -// Encrypt a vote option +// Encrypt a vote option — returns { ciphertext, iv, authTag } all in hex const BALLOT_KEY = process.env.BALLOT_ENCRYPTION_KEY!; // 64-char hex -const encrypted = encryptVote("option-uuid-here", BALLOT_KEY); +const payload: EncryptedPayload = encryptVote("option-uuid-here", BALLOT_KEY); // Decrypt during result tally -const optionId = decryptVote(encrypted, BALLOT_KEY); +const optionId = decryptVote(payload, BALLOT_KEY); ``` --- @@ -119,14 +134,20 @@ npm run build js/ ├── src/ │ ├── crypto.ts # Core cryptographic functions -│ ├── types.ts # Shared TypeScript types +│ ├── types.ts # Canonical shared types for the AnonVote ecosystem +│ ├── client.ts # AnonVoteClient SDK │ └── index.ts # Public API re-exports ├── tests/ │ └── crypto.test.ts +├── DECISIONS.md # Architecture decisions (wire format, encoding choices) ├── package.json └── tsconfig.json ``` +> **For contributors to AnonVote/core:** import shared types from `@anonvote/crypto` rather than +> defining local copies in `core/shared/`. `src/types.ts` is the single source of truth — local +> copies drift silently and only fail at runtime. + --- ## License