Problem
hashIdentifier currently produces different SHA-256 outputs for
identifiers that should be treated as identical. The following
three inputs all represent the same voter but produce three
different hashes:
hashIdentifier("alice@example.com")
hashIdentifier("Alice@example.com")
hashIdentifier(" alice@example.com ")
This is a critical correctness bug. In production it means the
same voter could be issued multiple tokens if their identifier
is entered with different casing or leading whitespace at
different points in the eligibility upload flow. This breaks
the one-person-one-vote guarantee without any error being
raised — the system silently treats one voter as multiple
distinct people.
The bug exists because the current implementation hashes the
raw input string without any normalization. The function
receives the string exactly as provided and passes it directly
to crypto.createHash — no trim, no toLower, no canonical form.
Root Cause
src/crypto.ts line 4 — hashIdentifier passes the raw identifier
string to the hash update without normalization:
export function hashIdentifier(identifier: string): string {
return crypto.createHash('sha256').update(identifier).digest('hex')
}
Solution
Normalize the identifier before hashing — trim leading and
trailing whitespace and convert to lowercase:
export function hashIdentifier(identifier: string): string {
return crypto
.createHash('sha256')
.update(identifier.trim().toLowerCase())
.digest('hex')
}
This is a one-line fix but the implications are significant.
Any existing eligibility data hashed with the unnormalized
function will produce hashes that no longer match after this
fix is applied. core must be notified of this change so any
existing test fixtures or seeded eligibility data are
regenerated.
Tests to Add
Acceptance Criteria
Note for contributors
Do not change the hashing algorithm or output format — only
the normalization step. The fix is one line. The test suite
expansion is the majority of the work in this issue.
Problem
hashIdentifier currently produces different SHA-256 outputs for
identifiers that should be treated as identical. The following
three inputs all represent the same voter but produce three
different hashes:
hashIdentifier("alice@example.com")
hashIdentifier("Alice@example.com")
hashIdentifier(" alice@example.com ")
This is a critical correctness bug. In production it means the
same voter could be issued multiple tokens if their identifier
is entered with different casing or leading whitespace at
different points in the eligibility upload flow. This breaks
the one-person-one-vote guarantee without any error being
raised — the system silently treats one voter as multiple
distinct people.
The bug exists because the current implementation hashes the
raw input string without any normalization. The function
receives the string exactly as provided and passes it directly
to crypto.createHash — no trim, no toLower, no canonical form.
Root Cause
src/crypto.ts line 4 — hashIdentifier passes the raw identifier
string to the hash update without normalization:
export function hashIdentifier(identifier: string): string {
return crypto.createHash('sha256').update(identifier).digest('hex')
}
Solution
Normalize the identifier before hashing — trim leading and
trailing whitespace and convert to lowercase:
export function hashIdentifier(identifier: string): string {
return crypto
.createHash('sha256')
.update(identifier.trim().toLowerCase())
.digest('hex')
}
This is a one-line fix but the implications are significant.
Any existing eligibility data hashed with the unnormalized
function will produce hashes that no longer match after this
fix is applied. core must be notified of this change so any
existing test fixtures or seeded eligibility data are
regenerated.
Tests to Add
hashIdentifier("Alice@example.com")
hashIdentifier(" alice@example.com ")
hashIdentifier("alice@example.com")
Acceptance Criteria
and lowercase applied
regardless of casing or whitespace
fix is a breaking change for any existing hashed data
regenerate any test fixtures using hashIdentifier
Note for contributors
Do not change the hashing algorithm or output format — only
the normalization step. The fix is one line. The test suite
expansion is the majority of the work in this issue.