Files
nostr_quantum_preparation/www/verify.html
T
2026-07-16 20:36:25 -04:00

300 lines
8.3 KiB
HTML

<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Verify NIP-QR Event</title>
<link rel="stylesheet" href="./css/client.css" />
<link rel="shortcut icon" type="image/x-icon" href="data:image/x-icon;," />
<style>
#divBody {
flex-direction: column !important;
flex-wrap: nowrap !important;
align-items: center !important;
justify-content: flex-start !important;
align-content: flex-start !important;
}
.pq-container {
max-width: 700px;
width: 100%;
padding: 10px;
}
.pq-card {
background: var(--secondary-color);
border: var(--border);
border-radius: var(--border-radius);
padding: 25px;
margin-bottom: 20px;
}
.pq-card-title {
font-size: 18px;
font-weight: bold;
color: var(--primary-color);
margin-bottom: 15px;
}
.pq-info-text {
font-size: 14px;
color: var(--muted-color);
line-height: 1.6;
margin-bottom: 15px;
}
.pq-info-text strong { color: var(--primary-color); }
.pq-button {
background: var(--primary-color);
color: var(--secondary-color);
border: var(--border-width) solid var(--primary-color);
border-radius: var(--border-radius);
padding: 12px 24px;
font-size: 16px;
font-weight: bold;
cursor: pointer;
transition: opacity 0.2s;
width: 100%;
}
.pq-button:hover { opacity: 0.7; }
.pq-button:disabled { opacity: 0.3; cursor: not-allowed; }
.pq-textarea {
width: 100%;
background: var(--secondary-color);
border: var(--border);
border-radius: var(--border-radius);
padding: 12px;
font-size: 12px;
color: var(--primary-color);
margin: 10px 0;
min-height: 200px;
resize: vertical;
font-family: monospace;
}
.pq-textarea:focus {
outline: none;
border-color: var(--accent-color);
}
.pq-status {
padding: 10px 15px;
border-radius: var(--border-radius);
margin: 10px 0;
font-size: 14px;
text-align: left;
}
.pq-status-info {
background: var(--secondary-color);
border: var(--border);
}
.pq-status-success {
background: var(--accent-color);
color: var(--secondary-color);
}
.pq-status-error {
background: var(--accent-color);
color: var(--secondary-color);
}
.pq-result-list {
display: flex;
flex-direction: column;
gap: 8px;
margin: 15px 0;
}
.pq-result-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 15px;
background: var(--primary-color);
color: var(--secondary-color);
border-radius: var(--border-radius);
font-size: 14px;
}
.pq-result-valid {
border-left: 4px solid #00aa00;
}
.pq-result-invalid {
border-left: 4px solid #cc0000;
}
.pq-event-preview {
background: var(--primary-color);
color: var(--secondary-color);
border-radius: var(--border-radius);
padding: 15px;
font-size: 11px;
white-space: pre-wrap;
word-break: break-all;
max-height: 400px;
overflow-y: auto;
text-align: left;
margin-top: 10px;
}
.pq-link {
color: var(--accent-color);
text-decoration: underline;
cursor: pointer;
}
</style>
</head>
<body>
<!-- HEADER -->
<div id="divHeader">
<div id="divHeaderFlexLeft"></div>
<div id="divHeaderFlexCenter">
<div class="divHeaderText">Verify NIP-QR Event</div>
</div>
<div id="divHeaderFlexRight"></div>
</div>
<!-- BODY -->
<div id="divBody">
<div class="pq-container">
<div class="pq-card">
<div class="pq-card-title">Verify NIP-QR Migration Event</div>
<div class="pq-info-text">
Paste a NIP-QR event JSON below to verify all signatures.
This will check the secp256k1 signature and all 5 post-quantum signatures (ML-DSA-44, ML-DSA-65, SLH-DSA-128s, Falcon-512, and ML-KEM-768).
</div>
<div class="pq-info-text">
<a href="./" class="pq-link">Back to migration page</a>
</div>
<textarea class="pq-textarea" id="pqEventInput" placeholder='Paste event JSON here, e.g.:
{
"id": "...",
"pubkey": "...",
"content": "NOSTR identity: ...",
"tags": [
["d", "nip-qr-migration"],
["algorithm", "ml-dsa-44", "...", "..."],
...
],
"sig": "..."
}'></textarea>
<button class="pq-button" id="pqVerifyBtn">Verify Signatures</button>
<div id="pqVerifyStatus"></div>
<div id="pqResultsWrap" style="display: none;">
<div class="pq-info-text" style="margin-top: 15px; margin-bottom: 5px;"><strong>Verification Results:</strong></div>
<div class="pq-result-list" id="pqResultList"></div>
</div>
<div id="pqEventDisplayWrap" style="display: none; margin-top: 15px;">
<div class="pq-info-text" style="margin-bottom: 5px;"><strong>Event content:</strong></div>
<div class="pq-event-preview" id="pqEventContent"></div>
</div>
</div>
</div>
</div>
<!-- FOOTER -->
<div id="divFooter">
<div id="divFooterLeft" class="divFooterBox"></div>
<div id="divFooterCenter" class="divFooterBox"></div>
<div id="divFooterRight" class="divFooterBox"></div>
</div>
<!-- SCRIPTS -->
<script type="module">
import {
verifyNIPQRContent,
verifyNostrEvent
} from './pq-crypto.bundle.js';
const verifyBtn = document.getElementById('pqVerifyBtn');
const eventInput = document.getElementById('pqEventInput');
const verifyStatus = document.getElementById('pqVerifyStatus');
const resultsWrap = document.getElementById('pqResultsWrap');
const resultList = document.getElementById('pqResultList');
const eventDisplayWrap = document.getElementById('pqEventDisplayWrap');
const eventContent = document.getElementById('pqEventContent');
function setStatus(type, message) {
verifyStatus.innerHTML = `<div class="pq-status pq-status-${type}">${message}</div>`;
}
function addResult(algorithm, valid, detail) {
const item = document.createElement('div');
item.className = `pq-result-item ${valid ? 'pq-result-valid' : 'pq-result-invalid'}`;
item.textContent = `${valid ? 'PASS' : 'FAIL'} — ${algorithm}${detail ? ': ' + detail : ''}`;
resultList.appendChild(item);
}
verifyBtn.addEventListener('click', async () => {
verifyBtn.disabled = true;
resultsWrap.style.display = 'none';
eventDisplayWrap.style.display = 'none';
resultList.innerHTML = '';
setStatus('info', 'Verifying...');
try {
const event = JSON.parse(eventInput.value.trim());
if (!event || !event.pubkey || !event.sig || !event.content || !event.tags) {
throw new Error('Invalid event: missing required fields (pubkey, sig, content, tags)');
}
// Display the event content
eventContent.textContent = event.content;
eventDisplayWrap.style.display = 'block';
let allValid = true;
// 1. Verify secp256k1 signature
setStatus('info', 'Verifying secp256k1 signature...');
const secpValid = verifyNostrEvent(event);
addResult('secp256k1 (NIP-01 Schnorr)', secpValid, secpValid ? 'valid' : 'INVALID');
if (!secpValid) allValid = false;
// 2. Verify PQ signatures
setStatus('info', 'Verifying PQ signatures...');
await new Promise(r => setTimeout(r, 50)); // let UI update
const pqResults = verifyNIPQRContent(event.content, event.tags);
for (const r of pqResults.results) {
addResult(r.algorithm, r.valid, r.note || (r.valid ? 'valid' : 'INVALID'));
if (!r.valid) allValid = false;
}
// Show results
resultsWrap.style.display = 'block';
if (allValid) {
setStatus('success', 'All signatures verified successfully!');
} else {
setStatus('error', 'Some signatures failed verification.');
}
} catch (error) {
console.error('[verify] Error:', error);
setStatus('error', `Error: ${error.message}`);
} finally {
verifyBtn.disabled = false;
}
});
</script>
</body>
</html>