fix: Comprehensive ElGamal encryption system - DRY & KISS principles
Major improvements applying DRY (Don't Repeat Yourself) and KISS (Keep It Simple, Stupid): BACKEND CHANGES: - Fixed public key storage: Store as base64-encoded bytes in LargeBinary column (not double-encoding) - ElGamal key generation now produces proper "p:g:h" format with colons - Removed all double base64-encoding issues - Simplified API responses to decode bytes to UTF-8 strings for JSON serialization FRONTEND CHANGES: - Refactored ElGamalEncryption.encrypt() to use extracted helper methods (_decodeBase64, _parsePublicKey) - Eliminated nested error handling - now uses clear, composable private methods - Improved error messages with specific format validation - Simplified cryptographic operations by reducing code duplication TESTING: - Verified public key format: "p:g:h" properly encoded as base64 - Full vote submission flow tested and working - Blockchain integration confirmed functional - No encryption errors during vote submission This fixes the original "Invalid public key format" error that was preventing vote submission. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
3aa988442f
commit
b1103a420a
@ -423,14 +423,17 @@ async def setup_election(
|
||||
# Générer les clés ElGamal si nécessaire
|
||||
if not election.public_key:
|
||||
elgamal = ElGamal()
|
||||
election.public_key = elgamal.public_key_bytes
|
||||
# Store as base64-encoded bytes (database column is LargeBinary)
|
||||
# public_key_bytes returns UTF-8 "p:g:h", then encode to base64
|
||||
election.public_key = base64.b64encode(elgamal.public_key_bytes)
|
||||
db.add(election)
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"status": "initialized",
|
||||
"election_id": election_id,
|
||||
"public_keys": {
|
||||
"elgamal_pubkey": base64.b64encode(election.public_key).decode() if election.public_key else None
|
||||
"elgamal_pubkey": election.public_key.decode('utf-8') if election.public_key else None
|
||||
},
|
||||
"blockchain_blocks": blockchain.get_block_count()
|
||||
}
|
||||
@ -467,7 +470,7 @@ async def get_public_keys(
|
||||
)
|
||||
|
||||
return {
|
||||
"elgamal_pubkey": base64.b64encode(election.public_key).decode()
|
||||
"elgamal_pubkey": election.public_key.decode('utf-8') if election.public_key else None
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -67,52 +67,57 @@ export class ElGamalEncryption {
|
||||
}
|
||||
|
||||
try {
|
||||
// Decode the base64 public key
|
||||
// Format from backend: base64("p:g:h") where p, g, h are decimal numbers
|
||||
let publicKeyStr: string;
|
||||
try {
|
||||
publicKeyStr = atob(publicKeyBase64);
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to decode public key from base64: ${e}`);
|
||||
}
|
||||
// Decode base64: base64("p:g:h") → "p:g:h"
|
||||
const publicKeyStr = this._decodeBase64(publicKeyBase64);
|
||||
const [p, g, h] = this._parsePublicKey(publicKeyStr);
|
||||
|
||||
// Parse public key (format: p:g:h separated by colons)
|
||||
const publicKeyData = publicKeyStr.split(":");
|
||||
// Generate random r for encryption
|
||||
const r = BigInt(Math.floor(Math.random() * (Number(p) - 2)) + 1);
|
||||
|
||||
if (publicKeyData.length < 3) {
|
||||
throw new Error(
|
||||
`Invalid public key format. Expected "p:g:h" but got "${publicKeyStr}"`
|
||||
);
|
||||
}
|
||||
|
||||
const p = BigInt(publicKeyData[0]); // Prime
|
||||
const g = BigInt(publicKeyData[1]); // Generator
|
||||
const h = BigInt(publicKeyData[2]); // Public key = g^x mod p
|
||||
|
||||
// Validate parameters
|
||||
if (p <= 0n || g <= 0n || h <= 0n) {
|
||||
throw new Error("Invalid public key parameters: p, g, h must be positive");
|
||||
}
|
||||
|
||||
// Generate random number for encryption
|
||||
const maxRandom = Number(p) - 2;
|
||||
if (maxRandom <= 0) {
|
||||
throw new Error("Public key prime p is too small");
|
||||
}
|
||||
const r = BigInt(Math.floor(Math.random() * maxRandom) + 1);
|
||||
|
||||
// ElGamal encryption: (c1, c2) = (g^r mod p, m * h^r mod p)
|
||||
// ElGamal: (c1, c2) = (g^r mod p, m * h^r mod p)
|
||||
const c1 = this._modPow(g, r, p);
|
||||
const c2 = (BigInt(vote) * this._modPow(h, r, p)) % p;
|
||||
|
||||
// Return as "c1:c2" in base64
|
||||
const encrypted = `${c1.toString()}:${c2.toString()}`;
|
||||
return btoa(encrypted);
|
||||
// Return encrypted as base64("c1:c2")
|
||||
return btoa(`${c1}:${c2}`);
|
||||
} catch (error) {
|
||||
console.error("ElGamal encryption failed:", error);
|
||||
throw new Error(
|
||||
`Encryption failed: ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
const msg = error instanceof Error ? error.message : String(error);
|
||||
throw new Error(`ElGamal encryption failed: ${msg}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode base64 string with clear error handling
|
||||
*/
|
||||
private static _decodeBase64(base64: string): string {
|
||||
try {
|
||||
return atob(base64);
|
||||
} catch (e) {
|
||||
throw new Error(`Invalid base64: ${base64.substring(0, 20)}... - ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse public key from "p:g:h" format
|
||||
*/
|
||||
private static _parsePublicKey(keyStr: string): [bigint, bigint, bigint] {
|
||||
const parts = keyStr.split(":");
|
||||
if (parts.length !== 3) {
|
||||
throw new Error(`Expected "p:g:h" format, got: ${keyStr}`);
|
||||
}
|
||||
|
||||
try {
|
||||
const p = BigInt(parts[0]);
|
||||
const g = BigInt(parts[1]);
|
||||
const h = BigInt(parts[2]);
|
||||
|
||||
if (p <= 0n || g <= 0n || h <= 0n) {
|
||||
throw new Error("p, g, h must all be positive");
|
||||
}
|
||||
|
||||
return [p, g, h];
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to parse key: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user