import { SignedData, VoteData } from "./types"; /** * Gets the WebCrypto API regardless of environment (Node vs Browser) */ const getCrypto = () => { return (globalThis as any).crypto; }; export const verifyVote = async (data: any, signatureStr: string, publicKey: CryptoKey) => { const encoder = new TextEncoder(); const encodedData = encoder.encode(JSON.stringify(data)); // Convert Base64 back to Uint8Array const signature = Uint8Array.from(atob(signatureStr), c => c.charCodeAt(0)); return await getCrypto().subtle.verify( "RSASSA-PKCS1-v1_5", publicKey, signature, encodedData ); }; /** * Verifies a specific vote within an array of votes by * reconstructing the "signed state" at that point in time. */ export const verifyChainedVote = async ( voteData: SignedData[], index: number, pubKey: CryptoKey ) => { const voteToVerify = voteData[index]; console.log("Verifying vote: " + voteToVerify) if(voteToVerify) { // 1. Reconstruct the exact data state the user signed // We need the array exactly as it was when they pushed their vote const historicalState = voteData.slice(0, index + 1).map((v, i) => { if (i === index) { // For the current vote, the signature must be empty string // because it wasn't signed yet when passed to signVote return { ...v, signature: "" }; } return v; }); try { // 3. Verify: Does this historicalState match the signature? return await verifyVote(historicalState, voteToVerify.signature, pubKey); } catch (err) { console.error("Verification failed") console.error(err); return false; } } console.error("Vote is undefined or null"); return false; }; /** * Converts a Base64 string back into a usable CryptoKey object * @param keyStr The Base64 string (without PEM headers) * @param type 'public' or 'private' */ export const stringToCryptoKey = async (keyStr: string, type: 'public' | 'private'): Promise => { // 1. Convert Base64 string to a Uint8Array (binary) const bytes = Buffer.from(keyStr, 'base64'); // 2. Identify the format based on the key type // Public keys usually use 'spki', Private keys use 'pkcs8' const format = type === 'public' ? 'spki' : 'pkcs8'; const usages: KeyUsage[] = type === 'public' ? ['verify'] : ['sign']; // 3. Import the key return await getCrypto().subtle.importKey( format, bytes, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256", }, true, // extractable (set to false if you want to lock it in memory) usages ); };