391 lines
12 KiB
HTML
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>
|