300 lines
8.3 KiB
HTML
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>
|