Files

391 lines
12 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" />
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; connect-src wss: https:; img-src 'self' data:; style-src 'self' 'unsafe-inline'; font-src 'self' data:; object-src 'none'; base-uri 'none'; frame-ancestors 'none';" />
<meta name="referrer" content="no-referrer" />
<title>Verify Key-Link Event</title>
<link rel="stylesheet" href="./css/client.css" />
<link rel="shortcut icon" type="image/x-icon" href="data:image/x-icon;," />
<style>
#divHeaderText {
font-size: calc(max(var(--header-height), var(--header-min-height)) * 0.5) !important;
}
#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: 760px;
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(--primary-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; }
/* Braille spinner shown inside a button while its action is running.
Frames are advanced by JS (setButtonSpinner); this class just styles
the inline spinner glyph. */
.pq-btn-spinner {
display: inline-block;
color: var(--accent-color);
font-size: 16px;
line-height: 1;
margin-right: 6px;
vertical-align: middle;
}
/* Keep the spinner fully visible even while the button is dimmed
(disabled) during a running action. */
.pq-button:disabled .pq-btn-spinner { opacity: 1; }
.pq-button-row {
display: flex;
gap: 10px;
margin-top: 10px;
}
.pq-button-row .pq-button { width: auto; }
.pq-input {
width: 100%;
background: var(--secondary-color);
border: var(--border);
border-radius: var(--border-radius);
padding: 12px;
font-size: 14px;
color: var(--primary-color);
margin: 10px 0;
box-sizing: border-box;
}
.pq-input:focus {
outline: none;
border-color: var(--accent-color);
}
.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: 160px;
resize: vertical;
font-family: monospace;
box-sizing: border-box;
}
.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(--secondary-color);
color: var(--primary-color);
border: var(--border);
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-result-pending {
border-left: 4px solid #f0ad4e;
}
/* Multi-line OTS / summary cards wrap their detail text. */
.pq-result-item.pq-result-detail {
display: block;
line-height: 1.6;
white-space: normal;
}
.pq-result-detail .pq-result-head {
font-weight: bold;
margin-bottom: 4px;
}
.pq-result-detail .pq-result-body {
font-size: 13px;
font-weight: normal;
}
.pq-summary-card {
font-size: 16px;
font-weight: bold;
padding: 15px 18px;
}
.pq-event-preview {
background: var(--secondary-color);
color: var(--primary-color);
border: var(--border);
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;
}
.pq-tabs {
display: flex;
gap: 4px;
margin-bottom: 15px;
border-bottom: var(--border);
}
.pq-tab {
padding: 10px 20px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
color: var(--muted-color);
border-bottom: 3px solid transparent;
transition: color 0.2s, border-color 0.2s;
}
.pq-tab:hover { color: var(--primary-color); }
.pq-tab.pq-tab-active {
color: var(--primary-color);
border-bottom-color: var(--primary-color);
}
.pq-tab-panel { display: none; }
.pq-tab-panel.pq-tab-panel-active { display: block; }
</style>
</head>
<body>
<!-- HEADER -->
<div id="divHeader">
<div id="divHeaderFlexLeft"></div>
<div id="divHeaderFlexCenter">
<div class="divHeaderText" id="divHeaderText">Verify Post-Quantum Key Link</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 Post-Quantum Key Link</div>
<div class="pq-tabs">
<div class="pq-tab pq-tab-active" id="pqTabRelay">Query Relay</div>
<div class="pq-tab" id="pqTabPaste">Paste Event JSON</div>
<div class="pq-tab" id="pqTabSignIn">Sign In</div>
</div>
<!-- TAB 1: Query Relay -->
<div class="pq-tab-panel pq-tab-panel-active" id="pqPanelRelay">
<div class="pq-info-text">
Enter a Nostr pubkey (hex or npub) and one or more relay URLs. The page will query the
relay(s) for the latest kind 9999 event from that pubkey and verify the signatures are
valid, and whether it has been submitted to Open Time Stamps.
</div>
<input type="text" class="pq-input" id="pqPubkeyInput"
placeholder="Pubkey (hex or npub), e.g. 3bf0c63f869a8972... or npub1..." />
<input type="text" class="pq-input" id="pqRelayInput"
value="wss://laantungir.net/relay/,wss://sendit.nosflare.com/,wss://nos.lol"
placeholder="wss://relay1.com,wss://relay2.com" />
<button class="pq-button" id="pqQueryBtn">Query & Verify</button>
<div id="pqQueryStatus"></div>
</div>
<!-- TAB 2: Paste Event JSON -->
<div class="pq-tab-panel" id="pqPanelPaste">
<div class="pq-info-text">
Paste a kind 9999 event JSON below to verify all signatures.
</div>
<textarea class="pq-textarea" id="pqEventInput" placeholder='Paste kind 9999 event JSON here, e.g.:
{
"id": "...",
"pubkey": "...",
"kind": 9999,
"content": "{\"id\":\"...\",\"pubkey\":\"...\",\"kind\":1,\"content\":\"I am signaling...\",\"tags\":[...],\"sig\":\"...\"}",
"tags": [
["e", "<kind 1 event id>"],
["sha256", "<hash of full kind 1 event>"],
["ots", "<base64 .ots proof>"]
],
"sig": "..."
}'></textarea>
<button class="pq-button" id="pqVerifyBtn">Verify Signatures</button>
<div id="pqVerifyStatus"></div>
<div class="pq-info-text" style="margin-top: 12px; font-size: 13px;">
Or import a proof archive file (offline verification without a relay):
</div>
<input type="file" id="pqArchiveFileInput" accept="application/json,.json" style="margin-top: 6px; font-size: 13px;" />
</div>
<!-- TAB 3: SIGN IN -->
<div class="pq-tab-panel" id="pqPanelSignIn">
<div class="pq-info-text">
Sign in with your Nostr identity (NIP-07 extension or nostr-login-lite) to look up your
own kind 9999 event automatically.
</div>
<div id="pqSignInError"></div>
<button class="pq-button" id="pqSignInBtn">Sign In</button>
<div id="pqSignInStatus"></div>
</div>
<!-- Full event JSON (shown at the top once an event is loaded) -->
<div id="pqEventJsonWrap" style="display: none; margin-top: 15px;">
<div class="pq-info-text" style="margin-bottom: 5px;"><strong>Full event JSON:</strong></div>
<div class="pq-event-preview" id="pqEventJson"></div>
</div>
<!-- Shared results area: signature + PQ checks + OTS cards, all in
one continuous card list. The OTS stamp/upgraded cards are
appended into pqResultList by JS so the flow is unbroken. -->
<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>
<!-- OTS upgrade actions (cards live in pqResultList above) -->
<div id="pqOtsUpgradeWrap" style="display: none; margin-top: 10px;">
<div class="pq-button-row">
<button class="pq-button" id="pqOtsUpgradeBtn">Upgrade OTS Proof</button>
<button class="pq-button" id="pqOtsRepublishBtn" style="display:none;">Publish Upgraded Event</button>
</div>
<div id="pqOtsUpgradeStatus"></div>
</div>
<!-- Summary card (shown once verification completes) -->
<div id="pqSummaryWrap" style="display: none; margin-top: 15px;">
<div class="pq-result-item pq-summary-card" id="pqSummaryCard"></div>
</div>
<hr style="border: var(--border); margin: 25px 0 15px;" />
<div class="pq-info-text" style="color: var(--accent-color); font-size: 13px;">
<strong>Note:</strong> Passing every check does not mean that your current nostr identity
is now post-quantum secure. This tool is the preparation step. Full quantum safety for
day-to-day Nostr use still requires companion protocols that are not yet built.
</div>
<div class="pq-info-text">
<a href="./" class="pq-link">Back to preparation page</a>
</div>
</div>
</div>
</div>
<!-- SCRIPTS -->
<script src="/nostr-login-lite/nostr.bundle.js" integrity="sha384-LNnPDD++DaWxljIhMLmfaoEoKB0B1HmACC6gNGLG+Qnmosprf/AX7u84pVY8xMpM" crossorigin="anonymous"></script>
<script src="/nostr-login-lite/nostr-lite.js" integrity="sha384-IQwa65eDC5trGjKn4cuEazmFiHTHD65gIqsjpzDtouc+j0MlyI927SZgHMWSi2aC" crossorigin="anonymous"></script>
<!-- Version display (G56-08: extracted to external file, self-initializing) -->
<script src="./js/version-display.js"></script>
<!-- Main application module (G56-08: extracted to external file for CSP compliance) -->
<script type="module" src="./js/verify-app.mjs"></script>
</body>
</html>