585 lines
18 KiB
HTML
585 lines
18 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>ZK Financial Proofs Demo - RuVector Edge</title>
|
||
<style>
|
||
:root {
|
||
--bg: #0a0a0f;
|
||
--card: #12121a;
|
||
--border: #2a2a3a;
|
||
--text: #e0e0e8;
|
||
--text-dim: #8888a0;
|
||
--accent: #8b5cf6;
|
||
--accent-glow: rgba(139, 92, 246, 0.3);
|
||
--success: #22c55e;
|
||
--warning: #f59e0b;
|
||
--error: #ef4444;
|
||
}
|
||
|
||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||
|
||
body {
|
||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
min-height: 100vh;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.container { max-width: 1200px; margin: 0 auto; padding: 2rem; }
|
||
|
||
header { text-align: center; margin-bottom: 3rem; }
|
||
|
||
h1 {
|
||
font-size: 2.5rem;
|
||
background: linear-gradient(135deg, var(--accent), #ec4899);
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
background-clip: text;
|
||
}
|
||
|
||
.subtitle { color: var(--text-dim); font-size: 1.1rem; margin-top: 0.5rem; }
|
||
|
||
.privacy-badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
background: rgba(139, 92, 246, 0.1);
|
||
border: 1px solid rgba(139, 92, 246, 0.3);
|
||
color: var(--accent);
|
||
padding: 0.5rem 1rem;
|
||
border-radius: 2rem;
|
||
margin-top: 1rem;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||
gap: 1.5rem;
|
||
margin-bottom: 2rem;
|
||
}
|
||
|
||
.card {
|
||
background: var(--card);
|
||
border: 1px solid var(--border);
|
||
border-radius: 1rem;
|
||
padding: 1.5rem;
|
||
}
|
||
|
||
.card h2 {
|
||
font-size: 1.2rem;
|
||
margin-bottom: 1rem;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.form-group { margin-bottom: 1rem; }
|
||
|
||
label {
|
||
display: block;
|
||
font-size: 0.9rem;
|
||
color: var(--text-dim);
|
||
margin-bottom: 0.25rem;
|
||
}
|
||
|
||
input, select {
|
||
background: var(--bg);
|
||
border: 1px solid var(--border);
|
||
color: var(--text);
|
||
padding: 0.75rem;
|
||
border-radius: 0.5rem;
|
||
font-size: 1rem;
|
||
width: 100%;
|
||
}
|
||
|
||
input:focus, select:focus {
|
||
outline: none;
|
||
border-color: var(--accent);
|
||
box-shadow: 0 0 0 3px var(--accent-glow);
|
||
}
|
||
|
||
button {
|
||
background: var(--accent);
|
||
color: white;
|
||
border: none;
|
||
padding: 0.75rem 1.5rem;
|
||
border-radius: 0.5rem;
|
||
font-size: 1rem;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
button:hover {
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 4px 20px var(--accent-glow);
|
||
}
|
||
|
||
button:disabled {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
transform: none;
|
||
}
|
||
|
||
button.secondary {
|
||
background: transparent;
|
||
border: 1px solid var(--border);
|
||
color: var(--text);
|
||
}
|
||
|
||
.proof-display {
|
||
background: var(--bg);
|
||
border: 1px solid var(--border);
|
||
border-radius: 0.5rem;
|
||
padding: 1rem;
|
||
font-family: 'Fira Code', monospace;
|
||
font-size: 0.85rem;
|
||
white-space: pre-wrap;
|
||
word-break: break-all;
|
||
max-height: 300px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.verification-result {
|
||
padding: 1rem;
|
||
border-radius: 0.5rem;
|
||
margin-top: 1rem;
|
||
}
|
||
|
||
.verification-result.valid {
|
||
background: rgba(34, 197, 94, 0.1);
|
||
border: 1px solid rgba(34, 197, 94, 0.3);
|
||
}
|
||
|
||
.verification-result.invalid {
|
||
background: rgba(239, 68, 68, 0.1);
|
||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||
}
|
||
|
||
.flow-diagram {
|
||
background: var(--bg);
|
||
border: 1px solid var(--border);
|
||
border-radius: 0.5rem;
|
||
padding: 1.5rem;
|
||
text-align: center;
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
.flow-step {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
padding: 0.5rem 1rem;
|
||
background: var(--card);
|
||
border-radius: 0.5rem;
|
||
margin: 0 0.25rem;
|
||
}
|
||
|
||
.flow-arrow {
|
||
color: var(--text-dim);
|
||
font-size: 1.5rem;
|
||
}
|
||
|
||
.tabs {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.tab {
|
||
padding: 0.5rem 1rem;
|
||
background: transparent;
|
||
border: 1px solid var(--border);
|
||
color: var(--text-dim);
|
||
cursor: pointer;
|
||
border-radius: 0.5rem;
|
||
}
|
||
|
||
.tab.active {
|
||
background: var(--accent);
|
||
border-color: var(--accent);
|
||
color: white;
|
||
}
|
||
|
||
.hidden { display: none; }
|
||
|
||
.info-box {
|
||
background: rgba(139, 92, 246, 0.05);
|
||
border: 1px solid rgba(139, 92, 246, 0.2);
|
||
border-radius: 0.5rem;
|
||
padding: 1rem;
|
||
margin-bottom: 1rem;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
footer {
|
||
text-align: center;
|
||
margin-top: 3rem;
|
||
padding-top: 2rem;
|
||
border-top: 1px solid var(--border);
|
||
color: var(--text-dim);
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<header>
|
||
<h1>🔐 Zero-Knowledge Financial Proofs</h1>
|
||
<p class="subtitle">Prove financial statements without revealing actual numbers</p>
|
||
<div class="privacy-badge">
|
||
🛡️ Your actual income, balance, and transactions are NEVER revealed
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Flow Diagram -->
|
||
<div class="flow-diagram">
|
||
<span class="flow-step">📊 Your Private Data</span>
|
||
<span class="flow-arrow">→</span>
|
||
<span class="flow-step">🔮 ZK Circuit (WASM)</span>
|
||
<span class="flow-arrow">→</span>
|
||
<span class="flow-step">📜 Proof (~1KB)</span>
|
||
<span class="flow-arrow">→</span>
|
||
<span class="flow-step">✅ Verifier</span>
|
||
</div>
|
||
|
||
<div class="grid">
|
||
<!-- Prover Panel -->
|
||
<div class="card">
|
||
<h2>👤 Prover (Your Data - Private)</h2>
|
||
|
||
<div class="info-box">
|
||
<strong>How it works:</strong> Enter your real financial data below.
|
||
The ZK system will generate a proof that ONLY reveals the statement is true,
|
||
not your actual numbers.
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Monthly Income ($)</label>
|
||
<input type="number" id="income" value="6500" placeholder="e.g., 6500">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Current Savings ($)</label>
|
||
<input type="number" id="savings" value="15000" placeholder="e.g., 15000">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Monthly Rent ($)</label>
|
||
<input type="number" id="rent" value="2000" placeholder="e.g., 2000">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Proof Type</label>
|
||
<select id="proof-type">
|
||
<option value="affordability">Rental Affordability (Income ≥ 3× Rent)</option>
|
||
<option value="income">Income Above Threshold</option>
|
||
<option value="savings">Savings Above Threshold</option>
|
||
<option value="no-overdraft">No Overdrafts (90 days)</option>
|
||
<option value="full-application">Complete Rental Application</option>
|
||
</select>
|
||
</div>
|
||
|
||
<button id="generate-btn" onclick="generateProof()">
|
||
🔮 Generate ZK Proof
|
||
</button>
|
||
|
||
<div id="prover-result" style="margin-top: 1rem;"></div>
|
||
</div>
|
||
|
||
<!-- Verifier Panel -->
|
||
<div class="card">
|
||
<h2>🏢 Verifier (Landlord/Bank - No Private Data)</h2>
|
||
|
||
<div class="info-box">
|
||
<strong>What verifier sees:</strong> Only the proof and statement.
|
||
Cannot determine actual income, savings, or any other numbers.
|
||
</div>
|
||
|
||
<div class="tabs">
|
||
<button class="tab active" onclick="showTab('paste')">Paste Proof</button>
|
||
<button class="tab" onclick="showTab('received')">Received Proof</button>
|
||
</div>
|
||
|
||
<div id="tab-paste">
|
||
<div class="form-group">
|
||
<label>Proof JSON</label>
|
||
<textarea id="proof-input" class="proof-display" rows="8"
|
||
placeholder="Paste proof JSON here..."></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="tab-received" class="hidden">
|
||
<div class="proof-display" id="received-proof">
|
||
No proof received yet. Generate one from the Prover panel.
|
||
</div>
|
||
</div>
|
||
|
||
<button onclick="verifyProof()">
|
||
✅ Verify Proof
|
||
</button>
|
||
|
||
<div id="verification-result"></div>
|
||
</div>
|
||
|
||
<!-- What's Proven vs Hidden -->
|
||
<div class="card">
|
||
<h2>🔍 What's Proven vs What's Hidden</h2>
|
||
|
||
<table style="width: 100%; border-collapse: collapse;">
|
||
<thead>
|
||
<tr style="border-bottom: 1px solid var(--border);">
|
||
<th style="padding: 0.5rem; text-align: left;">Statement</th>
|
||
<th style="padding: 0.5rem; text-align: center;">Proven</th>
|
||
<th style="padding: 0.5rem; text-align: center;">Hidden</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="proof-breakdown">
|
||
<tr>
|
||
<td style="padding: 0.5rem;">Income ≥ 3× Rent</td>
|
||
<td style="padding: 0.5rem; text-align: center; color: var(--success);">✓ Yes/No</td>
|
||
<td style="padding: 0.5rem; text-align: center; color: var(--error);">🔒 Exact amount</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<div style="margin-top: 1rem; padding: 1rem; background: var(--bg); border-radius: 0.5rem;">
|
||
<strong>Privacy Guarantee:</strong>
|
||
<p style="color: var(--text-dim); margin-top: 0.5rem; font-size: 0.9rem;">
|
||
The verifier mathematically CANNOT extract your actual numbers from the proof.
|
||
They only learn whether the statement is true or false.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Use Cases -->
|
||
<div class="card">
|
||
<h2>💡 Real-World Use Cases</h2>
|
||
|
||
<div style="display: flex; flex-direction: column; gap: 1rem;">
|
||
<div style="padding: 1rem; background: var(--bg); border-radius: 0.5rem;">
|
||
<strong>🏠 Rental Applications</strong>
|
||
<p style="color: var(--text-dim); font-size: 0.9rem;">
|
||
Prove you can afford rent without revealing exact salary
|
||
</p>
|
||
</div>
|
||
|
||
<div style="padding: 1rem; background: var(--bg); border-radius: 0.5rem;">
|
||
<strong>💳 Credit Applications</strong>
|
||
<p style="color: var(--text-dim); font-size: 0.9rem;">
|
||
Prove debt-to-income ratio without revealing all debts
|
||
</p>
|
||
</div>
|
||
|
||
<div style="padding: 1rem; background: var(--bg); border-radius: 0.5rem;">
|
||
<strong>💼 Employment Verification</strong>
|
||
<p style="color: var(--text-dim); font-size: 0.9rem;">
|
||
Prove you earn above minimum without revealing exact pay
|
||
</p>
|
||
</div>
|
||
|
||
<div style="padding: 1rem; background: var(--bg); border-radius: 0.5rem;">
|
||
<strong>🏦 Account Stability</strong>
|
||
<p style="color: var(--text-dim); font-size: 0.9rem;">
|
||
Prove no overdrafts without revealing transaction history
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<footer>
|
||
<p>Powered by <strong>RuVector Edge</strong> • Bulletproofs-style ZK Proofs • 100% Browser-Local</p>
|
||
</footer>
|
||
</div>
|
||
|
||
<script type="module">
|
||
// Simulated ZK proof generation (in production, uses WASM)
|
||
let lastProof = null;
|
||
|
||
window.generateProof = async function() {
|
||
const btn = document.getElementById('generate-btn');
|
||
btn.disabled = true;
|
||
btn.innerHTML = '⏳ Generating...';
|
||
|
||
const income = parseFloat(document.getElementById('income').value);
|
||
const savings = parseFloat(document.getElementById('savings').value);
|
||
const rent = parseFloat(document.getElementById('rent').value);
|
||
const proofType = document.getElementById('proof-type').value;
|
||
|
||
// Simulate proof generation
|
||
await new Promise(r => setTimeout(r, 500));
|
||
|
||
let statement, canProve;
|
||
|
||
switch (proofType) {
|
||
case 'affordability':
|
||
canProve = income >= rent * 3;
|
||
statement = `Income ≥ 3× monthly rent of $${rent}`;
|
||
break;
|
||
case 'income':
|
||
canProve = income >= 5000;
|
||
statement = `Average monthly income ≥ $5,000`;
|
||
break;
|
||
case 'savings':
|
||
canProve = savings >= rent * 2;
|
||
statement = `Current savings ≥ $${rent * 2}`;
|
||
break;
|
||
case 'no-overdraft':
|
||
canProve = savings > 0;
|
||
statement = `No overdrafts in the past 90 days`;
|
||
break;
|
||
case 'full-application':
|
||
canProve = income >= rent * 3 && savings >= rent * 2;
|
||
statement = `Complete rental application for $${rent}/month`;
|
||
break;
|
||
}
|
||
|
||
if (!canProve) {
|
||
document.getElementById('prover-result').innerHTML = `
|
||
<div style="color: var(--error); padding: 1rem; background: rgba(239,68,68,0.1); border-radius: 0.5rem;">
|
||
❌ Cannot generate proof: Your data doesn't meet the requirement.
|
||
<br><small>Actual numbers never leave your browser.</small>
|
||
</div>
|
||
`;
|
||
btn.disabled = false;
|
||
btn.innerHTML = '🔮 Generate ZK Proof';
|
||
return;
|
||
}
|
||
|
||
// Generate proof structure
|
||
lastProof = {
|
||
proof_type: proofType === 'affordability' ? 'Affordability' : 'Range',
|
||
proof_data: Array.from({length: 256}, () => Math.floor(Math.random() * 256)),
|
||
public_inputs: {
|
||
commitments: [{
|
||
point: Array.from({length: 32}, () => Math.floor(Math.random() * 256))
|
||
}],
|
||
bounds: [rent * 100, 3],
|
||
statement: statement,
|
||
},
|
||
generated_at: Math.floor(Date.now() / 1000),
|
||
expires_at: Math.floor(Date.now() / 1000) + 86400 * 30,
|
||
};
|
||
|
||
const proofJson = JSON.stringify(lastProof, null, 2);
|
||
|
||
document.getElementById('prover-result').innerHTML = `
|
||
<div style="color: var(--success); margin-bottom: 0.5rem;">
|
||
✅ Proof generated successfully!
|
||
</div>
|
||
<div class="proof-display" style="max-height: 200px;">
|
||
${proofJson}
|
||
</div>
|
||
<button class="secondary" style="margin-top: 0.5rem;" onclick="copyProof()">
|
||
📋 Copy Proof
|
||
</button>
|
||
`;
|
||
|
||
document.getElementById('received-proof').textContent = proofJson;
|
||
document.getElementById('proof-input').value = proofJson;
|
||
|
||
updateBreakdown(income, savings, rent, proofType);
|
||
|
||
btn.disabled = false;
|
||
btn.innerHTML = '🔮 Generate ZK Proof';
|
||
};
|
||
|
||
window.verifyProof = function() {
|
||
const proofJson = document.getElementById('proof-input').value ||
|
||
document.getElementById('received-proof').textContent;
|
||
|
||
if (!proofJson || proofJson.includes('No proof')) {
|
||
alert('Please generate or paste a proof first');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const proof = JSON.parse(proofJson);
|
||
|
||
// Simulate verification
|
||
const result = {
|
||
valid: true,
|
||
statement: proof.public_inputs.statement,
|
||
verified_at: Math.floor(Date.now() / 1000),
|
||
};
|
||
|
||
document.getElementById('verification-result').innerHTML = `
|
||
<div class="verification-result ${result.valid ? 'valid' : 'invalid'}">
|
||
<h3>${result.valid ? '✅ Proof Valid' : '❌ Proof Invalid'}</h3>
|
||
<p style="margin-top: 0.5rem;"><strong>Statement:</strong> ${result.statement}</p>
|
||
<p style="margin-top: 0.5rem; color: var(--text-dim); font-size: 0.9rem;">
|
||
${result.valid
|
||
? 'The prover has demonstrated the statement is TRUE without revealing actual values.'
|
||
: 'The proof could not be verified.'}
|
||
</p>
|
||
</div>
|
||
`;
|
||
} catch (e) {
|
||
document.getElementById('verification-result').innerHTML = `
|
||
<div class="verification-result invalid">
|
||
<h3>❌ Invalid Proof Format</h3>
|
||
<p>${e.message}</p>
|
||
</div>
|
||
`;
|
||
}
|
||
};
|
||
|
||
window.copyProof = function() {
|
||
const proofJson = JSON.stringify(lastProof);
|
||
navigator.clipboard.writeText(proofJson);
|
||
alert('Proof copied to clipboard!');
|
||
};
|
||
|
||
window.showTab = function(tab) {
|
||
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
||
document.querySelector(`[onclick="showTab('${tab}')"]`).classList.add('active');
|
||
|
||
document.getElementById('tab-paste').classList.toggle('hidden', tab !== 'paste');
|
||
document.getElementById('tab-received').classList.toggle('hidden', tab !== 'received');
|
||
};
|
||
|
||
function updateBreakdown(income, savings, rent, proofType) {
|
||
const tbody = document.getElementById('proof-breakdown');
|
||
|
||
const rows = {
|
||
'affordability': [
|
||
['Income ≥ 3× Rent', '✓ True/False', '🔒 $' + income.toLocaleString()],
|
||
['Rent amount', '✓ $' + rent.toLocaleString(), '—'],
|
||
],
|
||
'income': [
|
||
['Income ≥ $5,000', '✓ True/False', '🔒 $' + income.toLocaleString()],
|
||
],
|
||
'savings': [
|
||
['Savings ≥ $' + (rent * 2).toLocaleString(), '✓ True/False', '🔒 $' + savings.toLocaleString()],
|
||
],
|
||
'no-overdraft': [
|
||
['No overdrafts (90 days)', '✓ True/False', '🔒 All balances'],
|
||
],
|
||
'full-application': [
|
||
['Income ≥ 3× Rent', '✓ True/False', '🔒 $' + income.toLocaleString()],
|
||
['No overdrafts', '✓ True/False', '🔒 All balances'],
|
||
['Savings ≥ 2× Rent', '✓ True/False', '🔒 $' + savings.toLocaleString()],
|
||
],
|
||
};
|
||
|
||
tbody.innerHTML = (rows[proofType] || rows['affordability']).map(([stmt, proven, hidden]) => `
|
||
<tr>
|
||
<td style="padding: 0.5rem;">${stmt}</td>
|
||
<td style="padding: 0.5rem; text-align: center; color: var(--success);">${proven}</td>
|
||
<td style="padding: 0.5rem; text-align: center; color: var(--error);">${hidden}</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
// Initialize
|
||
updateBreakdown(6500, 15000, 2000, 'affordability');
|
||
</script>
|
||
</body>
|
||
</html>
|