617 lines
28 KiB
HTML
617 lines
28 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>Post-Quantum Nostr</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;
|
|
}
|
|
|
|
#divHeaderFlexRight {
|
|
padding-right: 20px;
|
|
}
|
|
|
|
#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;
|
|
}
|
|
|
|
/* CHECKLIST */
|
|
.pq-checklist {
|
|
background: var(--secondary-color);
|
|
border: var(--border);
|
|
border-radius: var(--border-radius);
|
|
padding: 20px 25px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.pq-checklist-title {
|
|
font-size: 16px;
|
|
font-weight: bold;
|
|
margin-bottom: 15px;
|
|
color: var(--primary-color);
|
|
}
|
|
|
|
.pq-checklist-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 8px 0;
|
|
font-size: 14px;
|
|
color: var(--muted-color);
|
|
border-bottom: 1px solid var(--border-color);
|
|
}
|
|
|
|
.pq-checklist-item:last-child { border-bottom: none; }
|
|
|
|
.pq-checklist-item.pq-done {
|
|
color: var(--primary-color);
|
|
}
|
|
|
|
.pq-checklist-box {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 20px;
|
|
height: 20px;
|
|
border: 2px solid var(--muted-color);
|
|
border-radius: 4px;
|
|
flex-shrink: 0;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.pq-checklist-item.pq-done .pq-checklist-box {
|
|
background: var(--accent-color);
|
|
border-color: var(--accent-color);
|
|
color: var(--secondary-color);
|
|
}
|
|
|
|
.pq-checklist-item.pq-active {
|
|
color: var(--primary-color);
|
|
font-weight: bold;
|
|
}
|
|
|
|
.pq-checklist-item.pq-active .pq-checklist-box {
|
|
border-color: var(--accent-color);
|
|
}
|
|
|
|
.pq-checklist-link {
|
|
color: var(--accent-color);
|
|
text-decoration: underline;
|
|
}
|
|
|
|
/* CARDS */
|
|
.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-warning {
|
|
background: var(--primary-color);
|
|
color: var(--secondary-color);
|
|
border-radius: var(--border-radius);
|
|
padding: 12px 15px;
|
|
margin: 15px 0;
|
|
font-size: 13px;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.pq-warning strong { color: var(--accent-color); }
|
|
|
|
.pq-seed-display {
|
|
background: var(--secondary-color);
|
|
color: var(--primary-color);
|
|
border: var(--border);
|
|
border-radius: var(--border-radius);
|
|
padding: 20px;
|
|
margin: 15px 0;
|
|
font-size: 16px;
|
|
line-height: 1.8;
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 4px 20px;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.pq-seed-word { display: flex; align-items: center; gap: 8px; }
|
|
.pq-seed-number { color: var(--muted-color); font-size: 12px; min-width: 20px; }
|
|
|
|
.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-button-secondary {
|
|
background: transparent;
|
|
color: var(--primary-color);
|
|
border: var(--border-width) solid var(--primary-color);
|
|
}
|
|
|
|
.pq-button-row { display: flex; gap: 10px; margin: 10px 0; }
|
|
.pq-button-row .pq-button { flex: 1; }
|
|
|
|
.pq-checkbox-row {
|
|
display: flex; align-items: center; gap: 8px;
|
|
margin: 15px 0; font-size: 14px; cursor: pointer; color: var(--primary-color);
|
|
}
|
|
.pq-checkbox-row input {
|
|
width: 18px; height: 18px; cursor: pointer;
|
|
appearance: none; -webkit-appearance: none;
|
|
border: var(--border-width) solid var(--primary-color);
|
|
background: transparent;
|
|
display: inline-grid;
|
|
place-content: center;
|
|
box-sizing: border-box;
|
|
}
|
|
.pq-checkbox-row input::before {
|
|
content: "";
|
|
width: 100%; height: 100%;
|
|
transform: scale(0);
|
|
transition: transform 0.1s ease-in-out;
|
|
background: #d00;
|
|
border-radius: 1px;
|
|
}
|
|
.pq-checkbox-row input:checked::before {
|
|
transform: scale(1);
|
|
}
|
|
.pq-checkbox-row input:checked {
|
|
border-color: #d00;
|
|
background: #d00;
|
|
}
|
|
|
|
.pq-seed-toggle {
|
|
flex: 1;
|
|
opacity: 0.5;
|
|
}
|
|
.pq-seed-toggle-active {
|
|
opacity: 1;
|
|
border-color: var(--accent-color);
|
|
color: var(--accent-color);
|
|
}
|
|
|
|
.pq-progress-bar {
|
|
background: var(--muted-color); border-radius: 4px; height: 8px; margin: 15px 0; overflow: hidden;
|
|
}
|
|
.pq-progress-fill { background: var(--accent-color); height: 100%; width: 0%; transition: width 0.3s; }
|
|
|
|
.pq-key-list { display: flex; flex-direction: column; gap: 10px; margin: 15px 0; }
|
|
|
|
.pq-key-item {
|
|
display: flex; align-items: flex-start; gap: 12px; padding: 12px;
|
|
background: var(--primary-color); color: var(--secondary-color); border-radius: var(--border-radius);
|
|
}
|
|
.pq-key-icon { font-size: 20px; flex-shrink: 0; }
|
|
.pq-key-details { flex: 1; min-width: 0; }
|
|
.pq-key-name { font-size: 14px; font-weight: bold; margin-bottom: 2px; }
|
|
.pq-key-meta { font-size: 12px; color: var(--primary-color); margin-bottom: 4px; }
|
|
.pq-key-pubkey { font-size: 11px; color: var(--primary-color); word-break: break-all; }
|
|
|
|
.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-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;
|
|
}
|
|
|
|
.pq-npub-display {
|
|
font-size: 12px; word-break: break-all;
|
|
background: var(--primary-color); color: var(--secondary-color);
|
|
padding: 8px 12px; border-radius: var(--border-radius); margin: 10px 0;
|
|
}
|
|
|
|
.pq-relay-input {
|
|
width: 100%; background: var(--secondary-color); border: var(--border);
|
|
border-radius: var(--border-radius); padding: 10px 14px; font-size: 14px;
|
|
color: var(--primary-color); margin: 8px 0;
|
|
}
|
|
.pq-relay-input:focus { outline: none; border-color: var(--accent-color); }
|
|
|
|
.pq-hidden { display: none !important; }
|
|
|
|
ol { margin: 10px 0 10px 20px; line-height: 1.8; }
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div id="divHeader">
|
|
<div id="divHeaderFlexLeft"></div>
|
|
<div id="divHeaderFlexCenter">
|
|
<div class="divHeaderText" id="divHeaderText">Post-Quantum Nostr</div>
|
|
</div>
|
|
<div id="divHeaderFlexRight">
|
|
<button class="pq-button pq-button-secondary pq-hidden" id="pqSignOutBtn" style="width: auto; padding: 6px 16px; font-size: 13px;">Sign Out</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="divBody">
|
|
<div class="pq-container">
|
|
|
|
<!-- ================================================================
|
|
PERSISTENT CHECKLIST (always visible at top)
|
|
================================================================ -->
|
|
<div class="pq-checklist">
|
|
<div class="pq-checklist-title">Migration Steps</div>
|
|
<div class="pq-checklist-item" id="step-1">
|
|
<span class="pq-checklist-box"></span>
|
|
<span>1. Sign in with your Nostr identity</span>
|
|
</div>
|
|
<div class="pq-checklist-item" id="step-2">
|
|
<span class="pq-checklist-box"></span>
|
|
<span>2. Generate a seed phrase</span>
|
|
</div>
|
|
<div class="pq-checklist-item" id="step-3">
|
|
<span class="pq-checklist-box"></span>
|
|
<span>3. Derive post-quantum keys from seed</span>
|
|
</div>
|
|
<div class="pq-checklist-item" id="step-4">
|
|
<span class="pq-checklist-box"></span>
|
|
<span>4. Sign the NIP-QR migration event</span>
|
|
</div>
|
|
<div class="pq-checklist-item" id="step-5">
|
|
<span class="pq-checklist-box"></span>
|
|
<span>5. Publish events to relays</span>
|
|
</div>
|
|
<div class="pq-checklist-item" id="step-6">
|
|
<span class="pq-checklist-box"></span>
|
|
<span>6. Timestamp the event with OpenTimestamps</span>
|
|
</div>
|
|
<div class="pq-checklist-item" id="step-7">
|
|
<span class="pq-checklist-box"></span>
|
|
<span>7. <a href="./verify.html" class="pq-checklist-link">Verify the event signatures</a></span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ================================================================
|
|
STEP CONTENT (shown/hidden below checklist)
|
|
================================================================ -->
|
|
|
|
<!-- STEP 0: NOT AUTHENTICATED -->
|
|
<div id="pqNotAuthed" class="pq-card">
|
|
<div class="pq-card-title">Post-Quantum Nostr</div>
|
|
<div class="pq-info-text" style="font-size: 16px; color: var(--primary-color);">
|
|
Create an experimental pre-quantum PQ key commitment
|
|
</div>
|
|
<div class="pq-info-text">
|
|
Sign in with your Nostr signer to begin. Your npub won't change, your followers stay,
|
|
and your social graph is preserved. We'll generate a seed phrase and link
|
|
it to your current identity.
|
|
</div>
|
|
<div class="pq-info-text" style="font-size: 13px; color: var(--accent-color);">
|
|
<strong>Research prototype.</strong> Do not enter a valuable existing mnemonic. This tool
|
|
does not make your Nostr identity post-quantum secure on its own; it creates a commitment
|
|
that future PQ protocols can build on.
|
|
</div>
|
|
<div class="pq-info-text" id="noSignerWarning" style="color: var(--accent-color); display: none;">
|
|
No Nostr signer detected. Install a NIP-07 browser extension (like nos2x or Alby) and reload.
|
|
</div>
|
|
<div id="pqSignInError"></div>
|
|
<button class="pq-button" id="pqSignInBtn">Sign In to Continue</button>
|
|
</div>
|
|
|
|
<!-- STEP 1: AUTHENTICATED - INTRO -->
|
|
<div id="pqAuthed" class="pq-card pq-hidden">
|
|
<div class="pq-card-title">Post-Quantum Migration</div>
|
|
<div class="pq-info-text">
|
|
<strong>Connected as:</strong>
|
|
<div class="pq-npub-display" id="pqNpub"></div>
|
|
</div>
|
|
<div class="pq-info-text">
|
|
<strong>Current Bitcoin block height:</strong>
|
|
<div class="pq-npub-display" id="pqBlockHeight">Loading...</div>
|
|
</div>
|
|
<div class="pq-info-text">
|
|
This tool will:
|
|
<ol>
|
|
<li>Generate a new <strong>seed phrase</strong> (your recovery backup)</li>
|
|
<li>Derive <strong>post-quantum keys</strong> from that seed via BIP32 paths (ML-DSA-44, ML-DSA-65, SLH-DSA-128s, Falcon-512, ML-KEM-768)</li>
|
|
<li>Sign a <strong>NIP-QR migration event</strong> linking your identity to the PQ keys</li>
|
|
<li>Publish the event to relays</li>
|
|
</ol>
|
|
</div>
|
|
<button class="pq-button" id="pqStartBtn">Start Migration</button>
|
|
</div>
|
|
|
|
<!-- STEP 2: SEED PHRASE GENERATION -->
|
|
<div id="pqSeedStep" class="pq-card pq-hidden">
|
|
<div class="pq-card-title">Your Seed Phrase</div>
|
|
<div class="pq-info-text">
|
|
Your seed phrase is your <strong>recovery root</strong>. All post-quantum keys
|
|
are derived from it. It is processed entirely in your browser and never sent to any server.
|
|
The code is open source and auditable.
|
|
</div>
|
|
<div class="pq-info-text" style="font-size: 13px; color: var(--accent-color);">
|
|
<strong>Back this up offline.</strong> Anyone with this phrase can derive your PQ private keys.
|
|
24 words (256 bits) is recommended; 12 words (128 bits) is offered for testing only and is
|
|
weaker under a quantum brute-force model.
|
|
</div>
|
|
|
|
<!-- Mode toggle + copy row (always visible) -->
|
|
<div class="pq-button-row" style="margin-bottom: 15px;">
|
|
<button class="pq-button pq-button-secondary pq-seed-toggle pq-seed-toggle-active" id="pqSeedToggleGenerate">Generate New Seed</button>
|
|
<button class="pq-button pq-button-secondary pq-seed-toggle" id="pqSeedToggleOwn">I Have a Seed Phrase</button>
|
|
<button class="pq-button pq-button-secondary" id="pqCopySeedBtn">Copy</button>
|
|
</div>
|
|
|
|
<!-- Generate mode -->
|
|
<div id="pqSeedGeneratePanel">
|
|
<!-- Word-count selector -->
|
|
<div class="pq-info-text" style="margin-bottom: 10px; font-size: 13px;">
|
|
<label style="margin-right: 12px;"><input type="radio" name="pqWordCount" value="256" id="pqWordCount24" checked> 24 words (recommended)</label>
|
|
<label><input type="radio" name="pqWordCount" value="128" id="pqWordCount12"> 12 words (testing only)</label>
|
|
</div>
|
|
<div class="pq-seed-display" id="pqSeedDisplay"></div>
|
|
<div class="pq-info-text" style="margin-top: 12px; font-size: 13px; color: var(--accent-color);">
|
|
<strong>Verify out-of-band after migration:</strong> This seed phrase is your backup — but
|
|
this browser cannot cryptographically prove the published PQ keys came from it. After completing
|
|
the migration, take your seed phrase to a <em>different</em> device or browser, derive the keys
|
|
independently, and confirm the public keys match the published event. This is the only way to
|
|
catch a compromised browser that might have substituted attacker keys.
|
|
</div>
|
|
<div class="pq-info-text" style="margin-top: 15px; font-size: 13px;">
|
|
<strong>Optional:</strong> Add extra entropy by moving your mouse and typing in the box below.
|
|
This mixes your input with the browser's random number generator, making the seed unpredictable
|
|
even if the browser's RNG is compromised. You can skip this — a seed has already been generated for you above.
|
|
</div>
|
|
<div id="pqEntropyBox" style="background: var(--secondary-color); color: var(--primary-color); border: var(--border); border-radius: var(--border-radius); padding: 15px; font-size: 13px; min-height: 60px; margin: 10px 0; cursor: text; text-align: left;" tabindex="0">
|
|
<span id="pqEntropyHint">Optional: click here and type random characters, move your mouse to add entropy...</span>
|
|
<div style="margin-top: 8px; height: 6px; background: var(--muted-color); border-radius: 3px; overflow: hidden;">
|
|
<div id="pqEntropyBar" style="height: 100%; width: 0%; background: var(--accent-color); transition: width 0.2s;"></div>
|
|
</div>
|
|
</div>
|
|
<div class="pq-button-row">
|
|
<button class="pq-button pq-button-secondary pq-hidden" id="pqRegenerateWithEntropyBtn" disabled>Generate with Extra Entropy</button>
|
|
</div>
|
|
<label class="pq-checkbox-row">
|
|
<input type="checkbox" id="pqSeedConfirmed" />
|
|
I have written down my seed phrase
|
|
</label>
|
|
<button class="pq-button" id="pqSeedContinueBtn" disabled>Continue</button>
|
|
</div>
|
|
|
|
<!-- Bring your own mode -->
|
|
<div id="pqSeedOwnPanel" style="display: none;">
|
|
<div class="pq-info-text">
|
|
Paste your 12 or 24-word BIP39 seed phrase below. It will be validated and used to
|
|
derive your post-quantum keys. Make sure you generated it offline or with a trusted tool.
|
|
</div>
|
|
<textarea class="pq-textarea" id="pqSeedInput" style="min-height: 80px; width: 100%; background: var(--secondary-color); border: var(--border); border-radius: var(--border-radius); padding: 12px; font-size: 14px; color: var(--primary-color); font-family: monospace; margin: 10px 0;" placeholder="abandon ability able about above absent absorb abstract absurd abuse access accident..."></textarea>
|
|
<div id="pqSeedValidation" style="font-size: 13px; margin: 5px 0;"></div>
|
|
<button class="pq-button" id="pqSeedOwnContinueBtn" disabled>Continue</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- STEP 3: DERIVING PQ KEYS -->
|
|
<div id="pqDeriveStep" class="pq-card pq-hidden">
|
|
<div class="pq-card-title">Deriving Post-Quantum Keys</div>
|
|
<div class="pq-info-text">
|
|
Deriving post-quantum keys from your seed phrase via BIP32 paths.
|
|
All keys are generated deterministically — the same seed will always produce the same keys.
|
|
</div>
|
|
<div class="pq-progress-bar">
|
|
<div class="pq-progress-fill" id="pqDeriveProgress"></div>
|
|
</div>
|
|
<div class="pq-checklist" id="pqKeyList" style="margin-bottom: 15px;">
|
|
<div class="pq-checklist-item" id="pqKeyMlDsa44">
|
|
<span class="pq-checklist-box"></span>
|
|
<span>ML-DSA-44 (Dilithium) — FIPS 204 · Cat 2 · 1312-byte pubkey · m/44'/1237'/0'/0/1
|
|
<div class="pq-key-pubkey" id="pqKeyMlDsa44Pub" style="margin-top: 2px;"></div>
|
|
</span>
|
|
</div>
|
|
<div class="pq-checklist-item" id="pqKeyMlDsa65">
|
|
<span class="pq-checklist-box"></span>
|
|
<span>ML-DSA-65 (Dilithium) — FIPS 204 · Cat 3 · 1952-byte pubkey · m/44'/1237'/0'/0/2
|
|
<div class="pq-key-pubkey" id="pqKeyMlDsa65Pub" style="margin-top: 2px;"></div>
|
|
</span>
|
|
</div>
|
|
<div class="pq-checklist-item" id="pqKeySlhDsa">
|
|
<span class="pq-checklist-box"></span>
|
|
<span>SLH-DSA-128s (SPHINCS+) — FIPS 205 · Cat 1 · 32-byte pubkey · m/44'/1237'/0'/0/3+4
|
|
<div class="pq-key-pubkey" id="pqKeySlhDsaPub" style="margin-top: 2px;"></div>
|
|
</span>
|
|
</div>
|
|
<div class="pq-checklist-item" id="pqKeyFalcon">
|
|
<span class="pq-checklist-box"></span>
|
|
<span>Falcon-512 — FIPS 206 (draft) · Cat 1 · 897-byte pubkey · m/44'/1237'/0'/0/5+6
|
|
<div class="pq-key-pubkey" id="pqKeyFalconPub" style="margin-top: 2px;"></div>
|
|
<div style="font-size: 11px; color: var(--accent-color); margin-top: 4px;">⚠ Draft standard — may need re-issuing if FIPS 206 changes</div>
|
|
</span>
|
|
</div>
|
|
<div class="pq-checklist-item" id="pqKeyMlKem">
|
|
<span class="pq-checklist-box"></span>
|
|
<span>ML-KEM-768 (Kyber) — FIPS 203 · Cat 3 · 1184-byte pubkey · m/44'/1237'/0'/0/7+8
|
|
<div class="pq-key-pubkey" id="pqKeyMlKemPub" style="margin-top: 2px;"></div>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div id="pqDeriveStatus"></div>
|
|
<button class="pq-button pq-hidden" id="pqDeriveContinueBtn">Continue to Signing</button>
|
|
</div>
|
|
|
|
<!-- STEP 4: SIGNING -->
|
|
<div id="pqSignStep" class="pq-card pq-hidden">
|
|
<div class="pq-card-title">Sign Migration Event</div>
|
|
<div class="pq-info-text">
|
|
We'll now create two events: a kind 1 announcement (public, visible in Nostr feeds) and
|
|
a kind 9999 proof carrier (carries the OpenTimestamps proof). Each PQ key signs the attestation
|
|
statement, and your signer signs both events with your secp256k1 key. The SHA-256 of the
|
|
full kind 1 event (including its secp256k1 signature) is submitted to OpenTimestamps.
|
|
</div>
|
|
<div id="pqSignStatus"></div>
|
|
<button class="pq-button" id="pqSignBtn">Sign Event</button>
|
|
<div id="pqEventPreviewWrap" class="pq-hidden" style="margin-top: 15px;">
|
|
<div class="pq-info-text" style="margin-bottom: 5px;">Event preview:</div>
|
|
<div class="pq-event-preview" id="pqEventPreview"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- STEP 5: REVIEW & PUBLISH -->
|
|
<div id="pqPublishStep" class="pq-card pq-hidden">
|
|
<div class="pq-card-title">Review & Publish Events</div>
|
|
<div class="pq-info-text">
|
|
Two events have been signed: a kind 1 announcement (public, visible in Nostr feeds) and
|
|
a kind 9999 proof carrier (carries the OpenTimestamps proof). Review them below, then publish
|
|
both to relays when ready.
|
|
</div>
|
|
|
|
<div class="pq-info-text" style="margin-bottom: 5px;"><strong>Kind 1 announcement event:</strong></div>
|
|
<div id="pqKind1IdDisplay" style="margin: 5px 0; font-size: 11px; color: var(--primary-color); word-break: break-all;"></div>
|
|
<div class="pq-event-preview" id="pqKind1Preview" style="max-height: 200px;"></div>
|
|
|
|
<div class="pq-info-text" style="margin-top: 15px; margin-bottom: 5px;"><strong>Kind 9999 proof carrier event:</strong></div>
|
|
<div id="pqEventIdDisplay" style="margin: 5px 0; font-size: 11px; color: var(--primary-color); word-break: break-all;"></div>
|
|
<div class="pq-event-preview" id="pqSuccessEventPreview"></div>
|
|
<div class="pq-info-text" id="pqEventSizeWarning" style="display: none; font-size: 13px; margin-top: 8px;"></div>
|
|
<div class="pq-button-row" style="margin-top: 10px;">
|
|
<button class="pq-button pq-button-secondary" id="pqCopyProofCarrierBtn">Copy Proof Carrier Event</button>
|
|
</div>
|
|
|
|
<div style="margin-top: 15px;">
|
|
<div class="pq-info-text" style="margin-bottom: 5px;"><strong>Relays to publish to:</strong></div>
|
|
<div id="pqRelayList" style="display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px;"></div>
|
|
<div class="pq-button-row">
|
|
<input type="text" class="pq-relay-input" id="pqRelayAddInput" style="flex: 1; margin: 0;" placeholder="wss://relay.example.com" />
|
|
<button class="pq-button pq-button-secondary" id="pqRelayAddBtn" style="flex: 0 0 auto; width: auto; padding: 12px 20px;">Add Relay</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="pqPublishStatus"></div>
|
|
|
|
<div class="pq-button-row" style="margin-top: 15px;">
|
|
<button class="pq-button" id="pqPublishBtn">Publish Both Events to Relays</button>
|
|
<button class="pq-button pq-button-secondary" id="pqDownloadArchiveBtn" title="Download a self-contained proof archive (kind 1 event + proof carrier + OTS proof) so you can verify it offline even if relays delete it.">Download Proof Archive</button>
|
|
</div>
|
|
<div class="pq-button-row">
|
|
<button class="pq-button pq-button-secondary pq-hidden" id="pqOtsContinueBtn">Continue to OpenTimestamps</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- STEP 6: OPENTIMESTAMPS -->
|
|
<div id="pqOtsStep" class="pq-card pq-hidden">
|
|
<div class="pq-card-title">OpenTimestamps Attestation - Leave This Page Open</div>
|
|
<div class="pq-info-text">
|
|
Your event hash has already been submitted to OpenTimestamps in the previous step, and a
|
|
<strong>pending</strong> timestamp proof is embedded in the kind 9999 event you just published.
|
|
OpenTimestamps anchors that hash into the Bitcoin blockchain, proving your event existed at this
|
|
point in time — preventing a future quantum attacker from backdating a fraudulent event.
|
|
</div>
|
|
<div class="pq-info-text">
|
|
Bitcoin confirmation typically takes <strong>10-30 minutes</strong>. You can
|
|
<strong style="text-decoration: underline;">leave this page open</strong>
|
|
to watch the progress below — the polling log updates every minute. Once the Bitcoin attestation is
|
|
confirmed, this page will automatically sign and publish a <strong>new</strong> kind 9999 event carrying
|
|
the upgraded (confirmed) proof, referencing the original via an <code>upgrade_of</code> tag. The original
|
|
event remains on relays permanently. You're free to close the page; the workflow resumes on your next visit.
|
|
</div>
|
|
<div id="pqOtsStatus"></div>
|
|
|
|
<!-- Polling log window -->
|
|
<div class="pq-info-text" style="margin-top: 10px; margin-bottom: 5px;">Polling log:</div>
|
|
<div id="pqOtsLog" style="background: var(--secondary-color); color: var(--primary-color); border: var(--border); border-radius: var(--border-radius); padding: 10px; font-size: 11px; font-family: monospace; height: 120px; overflow-y: auto; text-align: left; margin-bottom: 15px;"></div>
|
|
|
|
<div class="pq-checklist" style="margin-bottom: 15px;">
|
|
<div class="pq-checklist-item pq-done" id="pqOtsSubmit">
|
|
<span class="pq-checklist-box"></span>
|
|
<span>Submit hash to OpenTimestamps — pending .ots proof created</span>
|
|
</div>
|
|
<div class="pq-checklist-item pq-done" id="pqOtsPendingPublish">
|
|
<span class="pq-checklist-box"></span>
|
|
<span>Pending proof embedded in kind 9999 event</span>
|
|
</div>
|
|
<div class="pq-checklist-item pq-active" id="pqOtsConfirm">
|
|
<span class="pq-checklist-box"></span>
|
|
<span>Wait for Bitcoin confirmation
|
|
<div class="pq-key-meta" id="pqOtsConfirmDetail" style="margin-top: 2px;">Polling every 60 seconds...</div>
|
|
</span>
|
|
</div>
|
|
<div class="pq-checklist-item" id="pqOtsConfirmedPublish">
|
|
<span class="pq-checklist-box"></span>
|
|
<span>Publish new kind 9999 with confirmed proof — automatic on confirmation</span>
|
|
</div>
|
|
</div>
|
|
<div id="pqOtsProofWrap" class="pq-hidden" style="margin-top: 15px;">
|
|
<div class="pq-info-text" style="margin-bottom: 5px;">OTS proof file (base64):</div>
|
|
<div class="pq-event-preview" id="pqOtsProof" style="max-height: 150px;"></div>
|
|
<div class="pq-button-row" style="margin-top: 10px;">
|
|
<button class="pq-button pq-button-secondary" id="pqOtsDownloadBtn">Download .ots file</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div id="divFooter">
|
|
<div id="divFooterLeft" class="divFooterBox"></div>
|
|
<div id="divFooterCenter" class="divFooterBox"></div>
|
|
<div id="divFooterRight" class="divFooterBox"></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: fetch version.json and update title + header (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/index-app.mjs"></script>
|
|
</body>
|
|
|
|
</html>
|