Problem
The @anonvote/crypto package is the foundation every other repo
in the AnonVote ecosystem depends on — and it is entirely empty.
core cannot encrypt votes without encryptVote. contracts cannot
be tested without the TypeScript types. protocol cannot reference
concrete implementations in its specs. The package exports
nothing. The type definitions file is a placeholder. There are
no tests. Until this package ships working implementations,
every other repo in the org is blocked from meaningful progress.
The absence of these primitives also means the privacy model
that AnonVote is built on has never been exercised in code —
it exists only as a description in the protocol repo.
Solution
Implement all five core crypto functions and the three shared
TypeScript types that form the complete @anonvote/crypto surface,
with full test coverage and correct exports so every consuming
repo can immediately depend on this package.
Crypto Functions
-
hashIdentifier(identifier: string): string
SHA-256 hash of a raw voter identifier. Must normalize input
by trimming whitespace and lowercasing before hashing. Output
is always a 64-character lowercase hex string. This function
is the privacy boundary — raw identifiers must never be
accessible after it returns.
-
generateToken(): string
Generates a cryptographically secure 32-byte random token
using crypto.randomBytes(32). Returns a 64-character lowercase
hex string. Must produce a unique value on every call —
never cache or reuse token values.
-
hashToken(token: string): string
SHA-256 hash of a raw token. Same algorithm and output
format as hashIdentifier. Kept as a separate function to
make the two-step token design explicit and independently
testable.
-
encryptVote(option: string, key: string): EncryptedPayload
AES-256-GCM encryption of a raw vote option string. Generates
a fresh 12-byte IV on every call via crypto.randomBytes(12).
Returns a typed EncryptedPayload with ciphertext, iv, and
authTag all as hex strings. The same option encrypted twice
must produce two different ciphertexts due to unique IVs.
-
decryptVote(payload: EncryptedPayload, key: string): string
Decrypts an EncryptedPayload back to the original option
string. Must verify the GCM auth tag — throw a descriptive
error if verification fails. A tampered ciphertext or auth
tag must never silently produce wrong output.
TypeScript Types
- Token: { value: string; hash: string }
- Vote: { ballotId: string; option: string; timestamp: number }
- EncryptedPayload: { ciphertext: string; iv: string; authTag: string }
All types and functions must be exported as named exports from
src/index.ts. Use only Node.js built-in crypto module — no
external cryptography dependencies.
Tests
- hashIdentifier: deterministic output, 64-char hex, normalizes
whitespace and casing, different inputs produce different hashes
- generateToken: 64-char hex output, 1000 consecutive calls
produce 1000 unique values
- hashToken: deterministic, 64-char hex, different tokens
produce different hashes
- encryptVote / decryptVote: full roundtrip passes, two
encryptions of same option produce different ciphertexts,
tampered ciphertext throws, tampered auth tag throws
- All types correctly exported and usable in consuming TypeScript
files without type errors
Acceptance Criteria
Note for contributors
Use only the Node.js built-in crypto module. Do not introduce
external cryptography libraries — the entire point of this
package is to be a minimal, auditable, zero-dependency
primitive layer. Any PR that adds a crypto dependency will
be closed.
Problem
The @anonvote/crypto package is the foundation every other repo
in the AnonVote ecosystem depends on — and it is entirely empty.
core cannot encrypt votes without encryptVote. contracts cannot
be tested without the TypeScript types. protocol cannot reference
concrete implementations in its specs. The package exports
nothing. The type definitions file is a placeholder. There are
no tests. Until this package ships working implementations,
every other repo in the org is blocked from meaningful progress.
The absence of these primitives also means the privacy model
that AnonVote is built on has never been exercised in code —
it exists only as a description in the protocol repo.
Solution
Implement all five core crypto functions and the three shared
TypeScript types that form the complete @anonvote/crypto surface,
with full test coverage and correct exports so every consuming
repo can immediately depend on this package.
Crypto Functions
hashIdentifier(identifier: string): string
SHA-256 hash of a raw voter identifier. Must normalize input
by trimming whitespace and lowercasing before hashing. Output
is always a 64-character lowercase hex string. This function
is the privacy boundary — raw identifiers must never be
accessible after it returns.
generateToken(): string
Generates a cryptographically secure 32-byte random token
using crypto.randomBytes(32). Returns a 64-character lowercase
hex string. Must produce a unique value on every call —
never cache or reuse token values.
hashToken(token: string): string
SHA-256 hash of a raw token. Same algorithm and output
format as hashIdentifier. Kept as a separate function to
make the two-step token design explicit and independently
testable.
encryptVote(option: string, key: string): EncryptedPayload
AES-256-GCM encryption of a raw vote option string. Generates
a fresh 12-byte IV on every call via crypto.randomBytes(12).
Returns a typed EncryptedPayload with ciphertext, iv, and
authTag all as hex strings. The same option encrypted twice
must produce two different ciphertexts due to unique IVs.
decryptVote(payload: EncryptedPayload, key: string): string
Decrypts an EncryptedPayload back to the original option
string. Must verify the GCM auth tag — throw a descriptive
error if verification fails. A tampered ciphertext or auth
tag must never silently produce wrong output.
TypeScript Types
All types and functions must be exported as named exports from
src/index.ts. Use only Node.js built-in crypto module — no
external cryptography dependencies.
Tests
whitespace and casing, different inputs produce different hashes
produce 1000 unique values
produce different hashes
encryptions of same option produce different ciphertexts,
tampered ciphertext throws, tampered auth tag throws
files without type errors
Acceptance Criteria
errors, or return values beyond their intended function
for the same input
Note for contributors
Use only the Node.js built-in crypto module. Do not introduce
external cryptography libraries — the entire point of this
package is to be a minimal, auditable, zero-dependency
primitive layer. Any PR that adds a crypto dependency will
be closed.