mirror of
https://github.com/siv-org/siv.git
synced 2026-01-10 19:07:57 -05:00
/malware-check: Encode data as | delimited, instead of json
This commit is contained in:
@@ -17,8 +17,9 @@ type CheckResult = {
|
||||
}
|
||||
|
||||
type VoteData = {
|
||||
s?: Record<string, { p: string; r: string }> // short for s=selections, p=plaintext, r=randomizer
|
||||
v?: string // short for v=verification_number
|
||||
randomizers: Record<string, string> // questionId -> randomizer (base64url)
|
||||
selections: Record<string, string> // questionId -> plaintext
|
||||
verificationNumber: string
|
||||
}
|
||||
|
||||
export default function MalwareCheckPage() {
|
||||
@@ -43,7 +44,30 @@ export default function MalwareCheckPage() {
|
||||
|
||||
try {
|
||||
const decoded = decodeBase64URL(hash)
|
||||
const data: VoteData = JSON.parse(decoded)
|
||||
// Parse compact format: tracking|questionId|plaintext|randomizer|questionId|plaintext|randomizer|...
|
||||
const parts = decoded.split('|')
|
||||
if (parts.length < 1 || (parts.length - 1) % 3 !== 0) {
|
||||
throw new Error('Invalid data format')
|
||||
}
|
||||
|
||||
const verificationNumber = parts[0]
|
||||
const selections: Record<string, string> = {}
|
||||
const randomizers: Record<string, string> = {}
|
||||
|
||||
// Process in groups of 3: questionId, plaintext, randomizer
|
||||
for (let i = 1; i < parts.length; i += 3) {
|
||||
const questionId = parts[i]
|
||||
const plaintext = parts[i + 1]
|
||||
const randomizer = parts[i + 2]
|
||||
selections[questionId] = plaintext
|
||||
randomizers[questionId] = randomizer
|
||||
}
|
||||
|
||||
const data: VoteData = {
|
||||
randomizers,
|
||||
selections,
|
||||
verificationNumber,
|
||||
}
|
||||
setVoteData(data)
|
||||
|
||||
// Move private data into document memory and clear URL
|
||||
@@ -80,18 +104,16 @@ export default function MalwareCheckPage() {
|
||||
const public_key = RP.fromHex(electionInfo.threshold_public_key)
|
||||
const recalculated: Record<string, CipherStrings> = {}
|
||||
|
||||
// Un-shorten the vote data
|
||||
const verificationNumber = voteData.v
|
||||
const selections = voteData.s
|
||||
const { randomizers, selections, verificationNumber } = voteData
|
||||
|
||||
if (!verificationNumber || !selections)
|
||||
if (!verificationNumber || !selections || !randomizers)
|
||||
return setCheckResult({ error: 'Invalid vote data format', match: false })
|
||||
|
||||
// Recalculate encryption for each selection
|
||||
for (const [questionId, selection] of Object.entries(selections)) {
|
||||
const plaintext = selection.p
|
||||
for (const questionId of Object.keys(selections)) {
|
||||
const plaintext = selections[questionId]
|
||||
// Convert base64url encoded randomizer back to bigint
|
||||
const randomizerBigInt = base64URLToBigint(selection.r)
|
||||
const randomizerBigInt = base64URLToBigint(randomizers[questionId])
|
||||
|
||||
// Encode: stringToPoint(`${tracking}:${plaintext}`)
|
||||
const encoded = stringToPoint(`${verificationNumber}:${plaintext}`)
|
||||
@@ -229,10 +251,9 @@ export default function MalwareCheckPage() {
|
||||
</thead>
|
||||
<tbody>
|
||||
{columns.map((columnId) => {
|
||||
const selection = voteData.s?.[columnId]
|
||||
if (!selection) return null
|
||||
const plaintext = voteData.selections?.[columnId]
|
||||
if (!plaintext) return null
|
||||
|
||||
const plaintext = selection.p
|
||||
const displayValue = unTruncateSelection(plaintext, electionInfo.ballot_design || [], columnId)
|
||||
|
||||
// Find question title from ballot design
|
||||
|
||||
@@ -57,21 +57,18 @@ export const MalwareCheck = ({
|
||||
const qrUrl = useMemo(() => {
|
||||
if (!state.tracking || !state.plaintext || !state.randomizer) return ''
|
||||
|
||||
// Build vote data object with shortened keys for compact encoding
|
||||
// v = verification_number, s = selections, p = plaintext, r = randomizer (base64url encoded)
|
||||
const voteData: Record<string, unknown> = {
|
||||
s: Object.keys(state.plaintext).reduce((acc, questionId) => {
|
||||
acc[questionId] = {
|
||||
p: state.plaintext[questionId],
|
||||
r: bigintToBase64URL(BigInt(state.randomizer[questionId])),
|
||||
}
|
||||
return acc
|
||||
}, {} as Record<string, { p: string; r: string }>),
|
||||
v: state.tracking.replace(/-/g, ''),
|
||||
// Build compact format: tracking|questionId|plaintext|randomizer|questionId|plaintext|randomizer|...
|
||||
// Using | as delimiter since it's unlikely to appear in plaintext or question IDs
|
||||
const parts: string[] = [state.tracking.replace(/-/g, '')]
|
||||
|
||||
for (const questionId of Object.keys(state.plaintext)) {
|
||||
parts.push(questionId)
|
||||
parts.push(state.plaintext[questionId])
|
||||
parts.push(bigintToBase64URL(BigInt(state.randomizer[questionId])))
|
||||
}
|
||||
|
||||
// Encode vote data as JSON and base64url encode for hash
|
||||
const encodedData = encodeBase64URL(JSON.stringify(voteData))
|
||||
// Encode compact format as base64url
|
||||
const encodedData = encodeBase64URL(parts.join('|'))
|
||||
|
||||
// Build URL: siv.org/malware-check/$election_id/$auth_token/#url_encoded_vote_data
|
||||
const protocol = typeof window !== 'undefined' && window.location.protocol === 'https:' ? 'https:' : 'http:'
|
||||
|
||||
Reference in New Issue
Block a user