ZKP-License-System/test-frontend/public/index.html

1029 lines
No EOL
41 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>License Verification ZKP System - Real Implementation</title>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/snarkjs@0.7.5/build/snarkjs.min.js"></script>
<script src="/js/zkp-bundle.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: #f0ead6;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
h1 {
color: white;
text-align: center;
margin-bottom: 10px;
font-size: 2.5rem;
text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
}
.subtitle {
color: rgba(255,255,255,0.9);
text-align: center;
margin-bottom: 30px;
font-size: 1.1rem;
}
.mode-indicator {
text-align: center;
margin-bottom: 20px;
}
.mode-badge {
display: inline-block;
padding: 8px 20px;
background: white;
border-radius: 20px;
font-weight: bold;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
.mode-badge.real {
background: #10b981;
color: white;
}
.mode-badge.mock {
background: #f59e0b;
color: white;
}
.dashboard {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.card {
background: white;
padding: 25px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
transition: transform 0.3s ease;
}
.card:hover {
transform: translateY(0px);
}
.card h2 {
color: #333;
margin-bottom: 20px;
font-size: 1.5rem;
border-bottom: 2px solid #667eea;
padding-bottom: 10px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
color: #555;
font-weight: 500;
}
input, select {
width: 100%;
padding: 10px 15px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 14px;
transition: border-color 0.3s ease;
}
input:focus, select:focus {
outline: none;
border-color: #667eea;
}
button {
width: 100%;
padding: 12px 20px;
background: #77a3bd;
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
button:disabled {
background: #ccc;
cursor: not-allowed;
transform: none;
}
.status {
margin-top: 20px;
padding: 15px;
border-radius: 8px;
background: #f5f5f5;
min-height: 60px;
}
.status.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.status.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.status.loading {
background: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
.metrics {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 15px;
margin-top: 20px;
}
.metric {
background: #f8f9fa;
padding: 15px;
border-radius: 8px;
text-align: center;
border: 1px solid #e9ecef;
}
.metric-value {
font-size: 24px;
font-weight: bold;
color: #667eea;
}
.metric-label {
font-size: 12px;
color: #666;
margin-top: 5px;
}
.proof-details {
margin-top: 20px;
padding: 15px;
background: #f8f9fa;
border-radius: 8px;
font-family: 'Courier New', monospace;
font-size: 12px;
max-height: 300px;
overflow-y: auto;
}
.timeline {
margin-top: 20px;
padding: 20px;
background: white;
border-radius: 8px;
border: 2px dashed #667eea;
}
.timeline-item {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.timeline-icon {
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 15px;
font-weight: bold;
}
.timeline-icon.pending {
background: #e0e0e0;
color: #666;
}
.timeline-icon.active {
background: #f59e0b;
color: white;
animation: pulse 1s infinite;
}
.timeline-icon.complete {
background: #10b981;
color: white;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
.loader {
border: 3px solid #f3f3f3;
border-radius: 50%;
border-top: 3px solid #667eea;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 20px auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.warning-box {
background: #fef3c7;
border: 1px solid #fbbf24;
color: #92400e;
padding: 12px;
border-radius: 8px;
margin-bottom: 15px;
display: flex;
align-items: center;
}
.warning-box svg {
width: 20px;
height: 20px;
margin-right: 10px;
}
</style>
</head>
<body>
<div class="container">
<h1> License Verification ZKP</h1>
<div class="dashboard">
<!-- System Status Card -->
<div class="card">
<h2>System Status</h2>
<div id="systemStatus">
<div class="metrics">
<div class="metric">
<div class="metric-value" id="zkpStatus">-</div>
<div class="metric-label">ZKP Engine</div>
</div>
<div class="metric">
<div class="metric-value" id="merkleStatus">-</div>
<div class="metric-label">Merkle Tree</div>
</div>
<div class="metric">
<div class="metric-value" id="circuitStatus">-</div>
<div class="metric-label">Circuit</div>
</div>
<div class="metric">
<div class="metric-value" id="treeLeaves">-</div>
<div class="metric-label">Licenses</div>
</div>
</div>
<button onclick="checkSystemStatus()" style="margin-top: 15px;">Refresh Status</button>
</div>
</div>
<!-- License Verification Card -->
<div class="card">
<h2>Verify License</h2>
<div class="warning-box" id="mockWarning" style="display: none;">
<svg fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path>
</svg>
<span>Running in mock mode - compile circuits for real proofs</span>
</div>
<div class="form-group">
<label for="licenseNumber">License Number:</label>
<input type="text" id="licenseNumber" placeholder="LIC-00000001" value="LIC-00000001">
</div>
<div class="form-group">
<label for="practitionerName">Practitioner Name:</label>
<input type="text" id="practitionerName" placeholder="Dr. Jane Smith" value="Test Practitioner 1">
</div>
<div class="form-group">
<label for="jurisdiction">Jurisdiction:</label>
<select id="jurisdiction">
<option value="CA">California</option>
<option value="NY">New York</option>
<option value="TX">Texas</option>
<option value="FL">Florida</option>
</select>
</div>
<div class="form-group">
<label for="issuedDate">Issued Date:</label>
<input type="number" id="issuedDate" value="1737590400" min="1" max="100000000000">
</div>
<div class="form-group">
<label for="expiryDate">Expirey Date:</label>
<input type="number" id="expiryDate" value="1832198400" min="1" max="100000000000">
</div>
<!-- <div class="form-group">
<label for="validityDays">Minimum Validity (days):</label>
<input type="number" id="validityDays" value="30" min="1" max="365">
</div> -->
<button onclick="generateAndVerifyProof()" id="verifyBtn">Generate & Verify Proof</button>
<div id="verificationStatus" class="status"></div>
<div id="proofTimeline" class="timeline" style="display: none;">
<div class="timeline-item" id="step1">
<div class="timeline-icon pending">1</div>
<div>Fetching Merkle proof...</div>
</div>
<div class="timeline-item" id="step2">
<div class="timeline-icon pending">2</div>
<div>Generating ZK proof...</div>
</div>
<div class="timeline-item" id="step3">
<div class="timeline-icon pending">3</div>
<div>Verifying proof...</div>
</div>
</div>
</div>
</div>
<!-- Proof Details Card -->
<div class="card">
<h2>Proof Details</h2>
<div id="proofDetails" class="proof-details">
<p style="color: #999;">No proof generated yet</p>
</div>
</div>
<br>
<!-- Performance Metrics Card -->
<!-- <div class="card">
<h2>Performance Metrics</h2>
<div class="metrics">
<div class="metric">
<div class="metric-value" id="lastGenTime">-</div>
<div class="metric-label">Last Generation (ms)</div>
</div>
<div class="metric">
<div class="metric-value" id="lastVerifyTime">-</div>
<div class="metric-label">Last Verification (ms)</div>
</div>
<div class="metric">
<div class="metric-value" id="proofSize">-</div>
<div class="metric-label">Proof Size (bytes)</div>
</div>
<div class="metric">
<div class="metric-value" id="totalProofs">0</div>
<div class="metric-label">Total Proofs</div>
</div>
</div>
<button onclick="runBenchmark()" style="margin-top: 20px;">Run Performance Benchmark</button>
<div id="benchmarkResults" style="margin-top: 15px;"></div>
</div> -->
</div>
<script>
// Configuration
const ZKP_API = 'http://localhost:8080';
const MERKLE_API = 'http://localhost:8082';
const CIRCUIT_BASE_URL = 'http://localhost:3000';
// State
let proofCount = 0;
let poseidon = null;
let poseidonF = null;
let circuitFiles = null;
let vKey = null;
async function initPoseidon() {
if (!poseidon) {
try {
poseidon = await window.zkpLibs.buildPoseidon();
poseidonF = poseidon.F;
console.log('Poseidon initialized successfully');
} catch (error) {
console.error('Failed to initialize Poseidon:', error);
}
}
return poseidon;
}
async function computeLeafHash(licenseData) {
await initPoseidon();
const inputs = [
stringToFieldElement(licenseData.licenseNumber),
stringToFieldElement(licenseData.practitionerName),
licenseData.issuedDate.toString(),
licenseData.expiryDate.toString(),
stringToFieldElement(licenseData.jurisdiction)
];
const leafHash = poseidon.F.toString(poseidon(inputs));
return leafHash;
}
async function loadCircuitFiles() {
if (circuitFiles) return circuitFiles;
try {
console.log('Loading circuit files...');
// Load WASM and zkey files
const files = await window.zkpLibs.loadCircuitFiles(
`${CIRCUIT_BASE_URL}/circuits/license_verification_js/license_verification.wasm`,
`${CIRCUIT_BASE_URL}/keys/license_verification.zkey`
);
// Load verification key
const vKeyResponse = await axios.get(`${CIRCUIT_BASE_URL}/api/circuit/vkey`);
vKey = vKeyResponse.data;
circuitFiles = files;
console.log('Circuit files loaded successfully');
return files;
} catch (error) {
console.error('Failed to load circuit files:', error);
return null;
}
}
async function generateRealProof(input) {
const circuit = await loadCircuitFiles();
if (!circuit) return null;
try {
const circuitInputs = formatCircuitInputs(input);
console.log('[ZKP] ===== LEAF HASH VERIFICATION =====');
// Compute what the leaf hash SHOULD be
await initPoseidon();
const expectedLeafHash = poseidon.F.toString(poseidon([
circuitInputs.licenseNumber,
circuitInputs.practitionerName,
circuitInputs.issuedDate,
circuitInputs.expiryDate,
circuitInputs.jurisdiction
]));
console.log('[ZKP] Expected leaf hash (from Poseidon in JS):', expectedLeafHash);
console.log('[ZKP] Leaf from Merkle proof:', input.merkleProof?.leaf || 'not provided');
console.log('[ZKP] Do they match?', expectedLeafHash === input.merkleProof?.leaf);
console.log('[ZKP] ===== END VERIFICATION =====');
// In generateRealProof, before fullProve:
console.log('[ZKP DEBUG] Merkle Path Verification:');
console.log('[ZKP DEBUG] Leaf would be hash of:', {
licenseNumber: circuitInputs.licenseNumber,
practitionerName: circuitInputs.practitionerName,
issuedDate: circuitInputs.issuedDate,
expiryDate: circuitInputs.expiryDate,
jurisdiction: circuitInputs.jurisdiction
});
console.log('[ZKP DEBUG] Path has', circuitInputs.pathElements.length, 'levels');
console.log('[ZKP DEBUG] All path indices:', circuitInputs.pathIndices);
console.log('[ZKP DEBUG] Note: ALL indices are "1" - is this correct?');
console.log('[ZKP] Circuit inputs prepared:', circuitInputs);
const response = await snarkjs.groth16.fullProve(
circuitInputs,
circuit.wasm,
circuit.zkey
);
console.log('[ZKP] Circuit outputs:', response);
const { proof, publicSignals } = response;
// Correct indices based on your circuit output
const isValid = publicSignals[0];
const debugExpectedRoot = publicSignals[1];
const debugComputedRoot = publicSignals[2];
const merkleRoot = publicSignals[3];
const currentTimestamp = publicSignals[4];
const minExpiryTimestamp = publicSignals[5];
console.log('[ZKP DEBUG] =====================================');
console.log('[ZKP DEBUG] isValid:', isValid);
console.log('[ZKP DEBUG] Expected Root:', debugExpectedRoot);
console.log('[ZKP DEBUG] Computed Root:', debugComputedRoot);
console.log('[ZKP DEBUG] Roots Match:', debugExpectedRoot === debugComputedRoot);
console.log('[ZKP DEBUG] Input merkleRoot:', circuitInputs.merkleRoot);
console.log('[ZKP DEBUG] =====================================');
return { proof, publicSignals };
} catch (error) {
console.error('[ZKP] Real proof generation failed:', error);
throw error;
}
}
// Format and validate circuit inputs
function formatCircuitInputs(input) {
// Ensure all inputs are properly formatted as strings (field elements)
const formatted = {
// Private inputs - ensure they're strings
licenseNumber: ensureFieldElement(input.licenseNumber),
practitionerName: ensureFieldElement(input.practitionerName),
issuedDate: ensureFieldElement(input.issuedDate),
expiryDate: ensureFieldElement(input.expiryDate),
jurisdiction: ensureFieldElement(input.jurisdiction),
// Merkle proof - ensure exactly 17 elements
pathElements: formatMerklePathElements(input.pathElements),
pathIndices: formatMerklePathIndices(input.pathIndices),
// Public inputs
merkleRoot: ensureFieldElement(input.merkleRoot),
currentTimestamp: ensureFieldElement(input.currentTimestamp),
minExpiryTimestamp: ensureFieldElement(input.minExpiryTimestamp)
};
return formatted;
}
// Ensure a value is formatted as a field element string
function ensureFieldElement(value) {
if (value === null || value === undefined) {
return "0";
}
// If it's already a string representation of a number, return it
if (typeof value === 'string' && /^\d+$/.test(value)) {
return value;
}
// If it's a hex string, convert to decimal
if (typeof value === 'string' && value.startsWith('0x')) {
console.log(`[ZKP] ensureFieldElement ${value}`);
return BigInt(value).toString();
}
// If it's a number, convert to string
if (typeof value === 'number') {
return Math.floor(value).toString();
}
// If it's a BigInt, convert to string
if (typeof value === 'bigint') {
return value.toString();
}
// Default: convert to string
return value.toString();
}
// Format Merkle path elements to ensure exactly 20 elements
function formatMerklePathElements(pathElements) {
const MERKLE_DEPTH = 17;
const formatted = [];
if (!pathElements || !Array.isArray(pathElements)) {
console.warn('[ZKP] No path elements provided, using zeros');
return Array(MERKLE_DEPTH).fill("0");
}
for (let i = 0; i < MERKLE_DEPTH; i++) {
if (i < pathElements.length && pathElements[i] !== undefined) {
// Convert hex strings to decimal field elements
let element = pathElements[i];
if (typeof element === 'string') {
// Remove any 0x prefix
element = element.replace(/^0x/i, '');
// If it looks like hex (contains non-decimal chars), convert it
if (/[a-fA-F]/.test(element)) {
// Convert hex to decimal string
console.log(`[ZKP] formatMerklePathElements ${element}`);
element = BigInt('0x' + element).toString();
} else if (element === '') {
element = "0";
}
} else if (typeof element === 'number') {
element = Math.floor(element).toString();
} else {
element = "0";
}
formatted.push(element);
} else {
// Pad with zeros if not enough elements
formatted.push("0");
}
}
return formatted;
}
// Format Merkle path indices to ensure exactly 20 elements
function formatMerklePathIndices(pathIndices) {
const MERKLE_DEPTH = 17;
const formatted = [];
if (!pathIndices || !Array.isArray(pathIndices)) {
console.warn('[ZKP] No path indices provided, using zeros');
return Array(MERKLE_DEPTH).fill("0");
}
for (let i = 0; i < MERKLE_DEPTH; i++) {
if (i < pathIndices.length && pathIndices[i] !== undefined) {
let index = pathIndices[i];
if (index === 0 || index === "0" || index === false) {
formatted.push("0");
} else if (index === 1 || index === "1" || index === true) {
formatted.push("1");
} else {
console.warn(`[ZKP] Invalid path index at position ${i}: ${index}, defaulting to 0`);
formatted.push("0");
}
} else {
// Default to 0 if not enough indices
formatted.push("0");
}
}
return formatted;
}
function stringToFieldElement(str) {
// Convert string to BigInt representation
let result = BigInt(0);
console.log(`[ZKP] stringToFieldElement ${str}`);
for (let i = 0; i < Math.min(str.length, 31); i++) { // Limit to fit in field
result = result * BigInt(256) + BigInt(str.charCodeAt(i));
}
return result.toString();
}
// Check system status
async function checkSystemStatus() {
try {
const [zkpHealth, merkleInfo, circuitStatus, merkleStats] = await Promise.all([
axios.get(`${ZKP_API}/health`).catch(() => ({data: {status: 'error'}})),
axios.get(`${MERKLE_API}/api/tree-info`).catch(() => ({data: {}})),
axios.get(`${ZKP_API}/api/circuit-status`).catch(() => ({data: {}})),
axios.get(`${MERKLE_API}/api/stats`).catch(() => ({data: {}}))
]);
// Update status indicators
document.getElementById('zkpStatus').textContent = zkpHealth.data.status === 'healthy' ? 'Y' : 'X';
document.getElementById('merkleStatus').textContent = merkleInfo.data.isBuilt ? 'Y' : 'X';
document.getElementById('circuitStatus').textContent = circuitStatus.data.hasCircuit ? 'Y' : 'X';
document.getElementById('treeLeaves').textContent = merkleInfo.data.leafCount || '0';
// Log details
console.log('System Status:', {
zkp: zkpHealth.data,
merkle: merkleInfo.data,
circuit: circuitStatus.data,
stats: merkleStats.data
});
} catch (error) {
console.error('Status check failed:', error);
document.getElementById('zkpStatus').textContent = 'X';
document.getElementById('merkleStatus').textContent = 'X';
}
}
// Update timeline step
function updateTimelineStep(stepId, status) {
const step = document.getElementById(stepId);
const icon = step.querySelector('.timeline-icon');
icon.className = `timeline-icon ${status}`;
if (status === 'complete') {
icon.textContent = '✓';
}
}
async function generateProof(req){
const startTime = performance.now();
const {
licenseData,
merkleProof,
merkleRoot,
currentTimestamp,
minExpiryTimestamp
} = req;
// Dont validate allow to generate
// if (!licenseData || !licenseData.licenseNumber) {
// return res.status(400).json({ error: 'License data required' });
// }
// Get or generate merkle root
let actualMerkleRoot = merkleRoot;
// Ensure pathElements and pathIndices are properly formatted
let pathElements = merkleProof?.pathElements || [];
let pathIndices = merkleProof?.pathIndices || [];
// Prepare circuit inputs with proper formatting
const circuitInputs = {
// Private inputs
licenseNumber: stringToFieldElement(licenseData?.licenseNumber || ''),
practitionerName: stringToFieldElement(licenseData?.practitionerName || ''),
issuedDate: (licenseData?.issuedDate || Math.floor(Date.now() / 1000) - 31536000).toString(),
expiryDate: (licenseData?.expiryDate || Math.floor(Date.now() / 1000) + 31536000).toString(),
jurisdiction: stringToFieldElement(licenseData?.jurisdiction || 'Unknown'),
pathElements: pathElements, // Already formatted by the helper functions
pathIndices: pathIndices,
// Public inputs
merkleRoot: actualMerkleRoot,
currentTimestamp: (currentTimestamp || Math.floor(Date.now() / 1000)).toString(),
minExpiryTimestamp: (minExpiryTimestamp || Math.floor(Date.now() / 1000) + 86400).toString()
};
// Log the formatted inputs for debugging
console.log('[ZKP] Formatted circuit inputs:', {
merkleRoot: actualMerkleRoot,
pathElementsCount: circuitInputs.pathElements.length,
pathIndicesCount: circuitInputs.pathIndices.length,
pathElementsSample: circuitInputs.pathElements.slice(0, 2),
pathIndicesSample: circuitInputs.pathIndices.slice(0, 2)
});
// Try real proof generation first, fall back to mock if needed
let proofData;
let isRealProof = false;
try {
proofData = await generateRealProof(circuitInputs);
if (proofData) {
isRealProof = true;
console.log('[ZKP] Generated real proof');
}
} catch (err) {
console.log('[ZKP] Real proof failed:', err.message);
proofData = { proof: '', };
}
const generationTime = performance.now() - startTime;
console.log(`[ZKP] Proof generated in ${generationTime.toFixed(0)}ms (${isRealProof ? 'real' : 'mock'})`);
return {
data: {
proof: proofData.proof,
publicSignals: proofData.publicSignals,
generationTimeMs: generationTime,
proofSize: JSON.stringify(proofData.proof).length,
isRealProof,
merkleRoot: actualMerkleRoot
}
}
}
// Generate and verify proof
async function generateAndVerifyProof() {
const statusDiv = document.getElementById('verificationStatus');
const timeline = document.getElementById('proofTimeline');
const detailsDiv = document.getElementById('proofDetails');
const btn = document.getElementById('verifyBtn');
// Reset timeline
['step1', 'step2', 'step3'].forEach(id => updateTimelineStep(id, 'pending'));
btn.disabled = true;
timeline.style.display = 'block';
statusDiv.className = 'status loading';
statusDiv.innerHTML = '<div class="loader"></div><p>Starting verification process...</p>';
try {
const licenseNumber = document.getElementById('licenseNumber').value;
const practitionerName = document.getElementById('practitionerName').value;
const jurisdiction = document.getElementById('jurisdiction').value;
const issuedDate = parseInt(document.getElementById('issuedDate').value);
const expiryDate = parseInt(document.getElementById('expiryDate').value);
// const validityDays = parseInt(document.getElementById('validityDays').value);
let licenseData = {
"licenseNumber": licenseNumber,
"practitionerName": practitionerName,
"issuedDate": issuedDate,
"expiryDate": expiryDate,
"jurisdiction": jurisdiction
}
const leafHash = await computeLeafHash(licenseData);
// Step 1: Get Merkle proof
updateTimelineStep('step1', 'active');
const merkleStartTime = performance.now();
// const merkleResponse = await axios.get(
// `${MERKLE_API}/api/merkle-proof/${licenseNumber}`
// );
const merkleResponse = await axios.get(
`${MERKLE_API}/api/merkle-proof-by-hash/${leafHash}`
);
console.log('[Frontend] Merkle response:', merkleResponse.data);
// console.log(merkleResponse)
const merkleTime = performance.now() - merkleStartTime;
updateTimelineStep('step1', 'complete');
// Step 2: Generate ZK proof
updateTimelineStep('step2', 'active');
statusDiv.innerHTML = '<div class="loader"></div><p>Generating zero-knowledge proof...</p>';
const currentTimestamp = Math.floor(Date.now() / 1000);
const minExpiryTimestamp = currentTimestamp + (30 * 86400);
console.log('[API] Merkle response:', {
root: merkleResponse.data.root,
leaf: merkleResponse.data.leaf,
leafIndex: merkleResponse.data.leafIndex
});
const proofStartTime = performance.now();
const proofResponse = await generateProof({
licenseData,
//merkleResponse.data.licenseData,
merkleProof: {
pathElements: merkleResponse.data.pathElements,
pathIndices: merkleResponse.data.pathIndices,
},
merkleRoot: merkleResponse.data.root,
currentTimestamp: currentTimestamp,
minExpiryTimestamp: minExpiryTimestamp
})
// const proofResponse = await axios.post(`${ZKP_API}/api/generate-proof`, {
// licenseData: {
// "licenseNumber": licenseNumber,
// "practitionerName": practitionerName,
// "issuedDate": issuedDate,
// "expiryDate": expiryDate,
// "jurisdiction": jurisdiction
// },
// //merkleResponse.data.licenseData,
// merkleProof: {
// pathElements: merkleResponse.data.pathElements,
// pathIndices: merkleResponse.data.pathIndices,
// leaf: merkleResponse.data.leaf
// },
// merkleRoot: merkleResponse.data.root,
// currentTimestamp: currentTimestamp,
// minExpiryTimestamp: minExpiryTimestamp
// });
const proofTime = performance.now() - proofStartTime;
updateTimelineStep('step2', 'complete');
// Step 3: Verify proof
updateTimelineStep('step3', 'active');
statusDiv.innerHTML = '<div class="loader"></div><p>Verifying proof...</p>';
const verifyStartTime = performance.now();
const verifyResponse = await axios.post(`${ZKP_API}/api/verify-proof`, {
proof: proofResponse.data.proof,
publicSignals: proofResponse.data.publicSignals
});
const verifyTime = performance.now() - verifyStartTime;
updateTimelineStep('step3', 'complete');
// Update metrics
// document.getElementById('lastGenTime').textContent = Math.round(proofTime);
// document.getElementById('lastVerifyTime').textContent = Math.round(verifyTime);
// document.getElementById('proofSize').textContent = proofResponse.data.proofSize;
// document.getElementById('totalProofs').textContent = ++proofCount;
// Show results
if (verifyResponse.data.valid) {
statusDiv.className = 'status success';
statusDiv.innerHTML = `
<strong> License Verified!</strong><br>
<small>
Mode: ${proofResponse.data.isRealProof ? 'Real ZK Proof' : 'Mock Proof'}<br>
Generation: ${Math.round(proofTime)}ms |
Verification: ${Math.round(verifyTime)}ms<br>
</small>
`;
// statusDiv.innerHTML = `
// <strong> License Verified!</strong><br>
// <small>
// Mode: ${proofResponse.data.isRealProof ? 'Real ZK Proof' : 'Mock Proof'}<br>
// Generation: ${Math.round(proofTime)}ms |
// Verification: ${Math.round(verifyTime)}ms<br>
// Valid until: ${new Date(verifyResponse.data.validUntil).toLocaleDateString()}
// </small>
// `;
} else {
statusDiv.className = 'status error';
statusDiv.innerHTML = '<strong> Verification Failed</strong>';
}
// Show proof details
detailsDiv.innerHTML = `
<strong>Public Signals:</strong><br>
Merkle Root: ${verifyResponse.data.merkleRoot?.substring(0, 20)}...<br><br>
<strong>Proof (truncated):</strong><br>
${JSON.stringify(proofResponse.data.proof, null, 2).substring(0, 500)}...
`;
// detailsDiv.innerHTML = `
// <strong>Public Signals:</strong><br>
// Merkle Root: ${verifyResponse.data.merkleRoot?.substring(0, 20)}...<br>
// Timestamp: ${verifyResponse.data.verifiedAt}<br>
// Valid Until: ${verifyResponse.data.validUntil}<br><br>
// <strong>Proof (truncated):</strong><br>
// ${JSON.stringify(proofResponse.data.proof, null, 2).substring(0, 500)}...
// `;
} catch (error) {
console.error('Verification failed:', error);
statusDiv.className = 'status error';
statusDiv.innerHTML = `<strong> Error:</strong> ${error.response?.data?.message || error.message}`;
['step1', 'step2', 'step3'].forEach(id => {
const icon = document.getElementById(id).querySelector('.timeline-icon');
if (icon.className.includes('active')) {
icon.className = 'timeline-icon error';
icon.style.background = '#ef4444';
}
});
} finally {
btn.disabled = false;
}
}
// Run benchmark
async function runBenchmark() {
const resultsDiv = document.getElementById('benchmarkResults');
resultsDiv.innerHTML = '<div class="loader"></div><p>Running benchmark...</p>';
try {
const response = await axios.get(`${ZKP_API}/api/benchmark`);
resultsDiv.innerHTML = `
<table style="width: 100%; margin-top: 10px;">
<thead>
<tr style="border-bottom: 2px solid #667eea;">
<th style="text-align: left; padding: 8px;">Operation</th>
<th style="text-align: right; padding: 8px;">Avg (ms)</th>
<th style="text-align: right; padding: 8px;">P95 (ms)</th>
<th style="text-align: right; padding: 8px;">Count</th>
</tr>
</thead>
<tbody>
${response.data.results.map(r => `
<tr>
<td style="padding: 8px;">${r.operation_type}</td>
<td style="text-align: right; padding: 8px;">${parseFloat(r.avg_ms).toFixed(0)}</td>
<td style="text-align: right; padding: 8px;">${parseFloat(r.p95_ms).toFixed(0)}</td>
<td style="text-align: right; padding: 8px;">${r.count}</td>
</tr>
`).join('')}
</tbody>
</table>
`;
} catch (error) {
resultsDiv.innerHTML = `<div class="status error">Benchmark failed: ${error.message}</div>`;
}
}
// Initialize on load
window.onload = function() {
checkSystemStatus();
initPoseidon();
setInterval(checkSystemStatus, 30000);
};
</script>
</body>
</html>