Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94f03f2999 | ||
|
|
200eb41f39 |
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "nostr_quantum_preparation",
|
||||
"version": "0.0.33",
|
||||
"version": "0.0.35",
|
||||
"description": "A migration strategy for bringing post-quantum security to Nostr without breaking the social graph, without requiring consensus on a single post-quantum algorithm, and without forcing existing users to abandon their identities.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
+17
-18
@@ -104,16 +104,6 @@
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
/* Inline braille spinner shown next to the "Wait for Bitcoin
|
||||
confirmation" label. Frames advanced by JS (startInlineSpinner). */
|
||||
.pq-inline-spinner {
|
||||
display: inline-block;
|
||||
color: var(--accent-color);
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
margin-right: 6px;
|
||||
vertical-align: -2px;
|
||||
}
|
||||
|
||||
.pq-checklist-link {
|
||||
color: var(--accent-color);
|
||||
@@ -366,9 +356,14 @@
|
||||
<li>A new seed phrase. This should be kept secret and private.</li>
|
||||
<li>A proof archive. This information is published to relays, but keeping a copy yourself is useful in case the relays don't keep the event.</li>
|
||||
</ol>
|
||||
In case quantum computers break nostr, you can use your seed phrase to create a new nostr
|
||||
|
||||
</div>
|
||||
|
||||
<div class="pq-info-text">
|
||||
In case quantum computers break nostr, you can use your new seed phrase to create a new nostr
|
||||
identity, and prove that your old nostr identity created this new one.
|
||||
</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>
|
||||
@@ -611,8 +606,8 @@
|
||||
<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><span class="pq-inline-spinner" id="pqOtsConfirmSpinner"></span>Wait for Bitcoin confirmation
|
||||
<span class="pq-checklist-box" id="pqOtsConfirmBox"></span>
|
||||
<span>Wait for Bitcoin confirmation
|
||||
<div class="pq-key-meta" id="pqOtsConfirmDetail" style="margin-top: 2px;">Polling every 60 seconds...</div>
|
||||
</span>
|
||||
</div>
|
||||
@@ -621,11 +616,15 @@
|
||||
<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 id="pqOtsArchiveWrap" class="pq-hidden" style="margin-top: 15px;">
|
||||
<div class="pq-info-text" style="margin-bottom: 8px;">
|
||||
Bitcoin confirmation verified and the upgraded proof carrier has been published to relays.
|
||||
Download the upgraded proof archive below — it contains the kind 1 event, the upgraded kind 9999
|
||||
proof carrier, and the <strong>confirmed</strong> OTS proof (with Bitcoin attestation). Keep a copy
|
||||
in case relays delete the event.
|
||||
</div>
|
||||
<div class="pq-button-row">
|
||||
<button class="pq-button pq-button-secondary" id="pqOtsArchiveBtn">Download Upgraded Proof Archive</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+51
-35
@@ -140,24 +140,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Inline braille spinner next to the "Wait for Bitcoin confirmation"
|
||||
label. Same frames as the checklist-box spinner. */
|
||||
let inlineSpinnerInterval = null;
|
||||
function startInlineSpinner(elementId) {
|
||||
const el = document.getElementById(elementId);
|
||||
if (!el) return;
|
||||
stopInlineSpinner(elementId);
|
||||
/* Braille spinner on the "Wait for Bitcoin confirmation" checklist box
|
||||
(pqOtsConfirm). Same frames as the step-6 checklist-box spinner. */
|
||||
let otsConfirmSpinnerInterval = null;
|
||||
function startOtsConfirmSpinner() {
|
||||
const box = document.getElementById('pqOtsConfirmBox');
|
||||
if (!box) return;
|
||||
stopOtsConfirmSpinner();
|
||||
box.classList.add('pq-spinner');
|
||||
let frame = 0;
|
||||
el.textContent = SPINNER_FRAMES[0];
|
||||
inlineSpinnerInterval = setInterval(() => {
|
||||
box.textContent = SPINNER_FRAMES[0];
|
||||
otsConfirmSpinnerInterval = setInterval(() => {
|
||||
frame = (frame + 1) % SPINNER_FRAMES.length;
|
||||
el.textContent = SPINNER_FRAMES[frame];
|
||||
box.textContent = SPINNER_FRAMES[frame];
|
||||
}, 80);
|
||||
}
|
||||
function stopInlineSpinner() {
|
||||
if (inlineSpinnerInterval) { clearInterval(inlineSpinnerInterval); inlineSpinnerInterval = null; }
|
||||
const el = document.getElementById('pqOtsConfirmSpinner');
|
||||
if (el) el.textContent = '';
|
||||
function stopOtsConfirmSpinner() {
|
||||
if (otsConfirmSpinnerInterval) { clearInterval(otsConfirmSpinnerInterval); otsConfirmSpinnerInterval = null; }
|
||||
const box = document.getElementById('pqOtsConfirmBox');
|
||||
if (box) { box.classList.remove('pq-spinner'); box.textContent = ''; }
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
@@ -1066,7 +1067,7 @@
|
||||
// Stop OTS polling, but preserve its persisted workflow for the next login.
|
||||
if (otsPollInterval) { clearInterval(otsPollInterval); otsPollInterval = null; }
|
||||
clearStepSpinner(6);
|
||||
stopInlineSpinner();
|
||||
stopOtsConfirmSpinner();
|
||||
pendingOtsBytes = null;
|
||||
const persistedOts = localStorage.getItem('pq-pending-ots');
|
||||
// Logout from nostr-login-lite
|
||||
@@ -1163,7 +1164,8 @@
|
||||
if (otsLogEl) otsLogEl.innerHTML = '';
|
||||
setKeyIcon('pqOtsConfirm', '');
|
||||
setKeyIcon('pqOtsConfirmedPublish', '');
|
||||
stopInlineSpinner();
|
||||
stopOtsConfirmSpinner();
|
||||
hideUpgradedArchiveButton();
|
||||
|
||||
// Check for a saved workflow from a previous session (same event ID)
|
||||
let existing = loadPendingOts();
|
||||
@@ -1182,10 +1184,10 @@
|
||||
setStepDone(6);
|
||||
setStepActive(7);
|
||||
otsStatus.innerHTML = '<div class="pq-status pq-status-success">Confirmed timestamp proof was already published.</div>';
|
||||
showOtsProof(pendingOtsBytes);
|
||||
showUpgradedArchiveButton();
|
||||
return;
|
||||
}
|
||||
showOtsProof(pendingOtsBytes);
|
||||
hideUpgradedArchiveButton();
|
||||
setKeyIcon('pqOtsPendingPublish', 'Done');
|
||||
otsStatus.innerHTML = '<div class="pq-status pq-status-info">Pending proof is embedded in the kind 9999 event. Waiting for Bitcoin confirmation...</div>';
|
||||
startOtsPolling();
|
||||
@@ -1200,7 +1202,6 @@
|
||||
pendingOtsBytes = base64ToBytes(otsTag[1]);
|
||||
if (isDetachedOtsFile(pendingOtsBytes)) {
|
||||
otsLog(`Found pending OTS proof embedded in event (${pendingOtsBytes.length} bytes).`);
|
||||
showOtsProof(pendingOtsBytes);
|
||||
savePendingOts(pqEvent.id, pendingOtsBytes, {
|
||||
ownerPubkey: currentPubkey,
|
||||
targetKind: pqEvent.kind,
|
||||
@@ -1236,8 +1237,6 @@
|
||||
const otsBytes = await timestampEvent(fullHash);
|
||||
pendingOtsBytes = otsBytes;
|
||||
otsLog(`Created pending detached .ots file (${otsBytes.length} bytes).`);
|
||||
showOtsProof(otsBytes);
|
||||
|
||||
// Republish the event with the OTS proof embedded
|
||||
otsStatus.innerHTML = '<div class="pq-status pq-status-info">Publishing new kind 9999 event with embedded OTS proof...</div>';
|
||||
await publishUpgradedEvent(otsBytes);
|
||||
@@ -1260,11 +1259,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
function showOtsProof(otsBytes) {
|
||||
const proofWrap = document.getElementById('pqOtsProofWrap');
|
||||
const proofEl = document.getElementById('pqOtsProof');
|
||||
proofEl.textContent = bytesToBase64(otsBytes);
|
||||
proofWrap.classList.remove('pq-hidden');
|
||||
// The raw OTS proof base64 display and standalone .ots download have been
|
||||
// removed from step 6. Instead, after confirmation + publish, the user is
|
||||
// offered an upgraded proof archive (kind 1 + upgraded kind 9999 + confirmed
|
||||
// OTS proof) via showUpgradedArchiveButton().
|
||||
function showUpgradedArchiveButton() {
|
||||
const wrap = document.getElementById('pqOtsArchiveWrap');
|
||||
if (wrap) wrap.classList.remove('pq-hidden');
|
||||
}
|
||||
function hideUpgradedArchiveButton() {
|
||||
const wrap = document.getElementById('pqOtsArchiveWrap');
|
||||
if (wrap) wrap.classList.add('pq-hidden');
|
||||
}
|
||||
|
||||
function startOtsPolling() {
|
||||
@@ -1275,7 +1280,7 @@
|
||||
// to the "Wait for Bitcoin confirmation" label so the user can see
|
||||
// the page is still actively working while waiting for Bitcoin.
|
||||
setStepSpinner(6);
|
||||
startInlineSpinner('pqOtsConfirmSpinner');
|
||||
startOtsConfirmSpinner();
|
||||
|
||||
async function poll() {
|
||||
pollCount++;
|
||||
@@ -1290,8 +1295,6 @@
|
||||
proofCarrierEvent: pqEvent,
|
||||
kind1Event
|
||||
});
|
||||
showOtsProof(pendingOtsBytes);
|
||||
|
||||
// Run full cryptographic verification: parse the proof, bind the
|
||||
// target digest to the event's sha256 tag, walk the Merkle path,
|
||||
// and check the block header against a Bitcoin API.
|
||||
@@ -1308,7 +1311,7 @@
|
||||
: verifyResult.trustMode || 'unknown';
|
||||
otsLog(`Poll ${pollCount}: Bitcoin attestation VERIFIED (block ${att ? att.height : '?'}, mined ${date} UTC, trust: ${trustLabel}). Proof: ${pendingOtsBytes.length} bytes.`);
|
||||
setKeyIcon('pqOtsConfirm', 'Done');
|
||||
stopInlineSpinner();
|
||||
stopOtsConfirmSpinner();
|
||||
confirmDetail.textContent = `Verified on Bitcoin (block ${att ? att.height : '?'}, ${trustLabel})`;
|
||||
if (otsPollInterval) { clearInterval(otsPollInterval); otsPollInterval = null; }
|
||||
|
||||
@@ -1385,6 +1388,7 @@
|
||||
setStatus(otsStatus, 'success', `Upgraded proof carrier published to ${successCount} relays. Timestamping complete.`);
|
||||
setStepDone(6);
|
||||
setStepActive(7);
|
||||
showUpgradedArchiveButton();
|
||||
return validatedEvent;
|
||||
} catch (error) {
|
||||
otsLog(`ERROR publishing upgraded event: ${error.message}`);
|
||||
@@ -1393,16 +1397,28 @@
|
||||
}
|
||||
}
|
||||
|
||||
// OTS event listeners
|
||||
document.getElementById('pqOtsDownloadBtn').addEventListener('click', () => {
|
||||
if (pendingOtsBytes) {
|
||||
const blob = new Blob([pendingOtsBytes], { type: 'application/octet-stream' });
|
||||
// OTS event listener: download the upgraded proof archive (kind 1 event +
|
||||
// upgraded kind 9999 proof carrier + confirmed OTS proof) after Bitcoin
|
||||
// confirmation and publish.
|
||||
document.getElementById('pqOtsArchiveBtn').addEventListener('click', () => {
|
||||
if (!kind1Event || !pqEvent || !pendingOtsBytes) {
|
||||
console.warn('[post-quantum] Cannot build upgraded archive: missing kind1Event, proofCarrier, or OTS proof');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const archiveJson = buildProofArchive(kind1Event, pqEvent, pendingOtsBytes);
|
||||
const blob = new Blob([archiveJson], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${pqEvent.id.substring(0, 16)}.ots`;
|
||||
a.download = `nostr-pq-proof-${pqEvent.id.substring(0, 12)}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
console.log('[post-quantum] Upgraded proof archive downloaded');
|
||||
} catch (e) {
|
||||
console.error('[post-quantum] Failed to build upgraded archive:', e);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+145
-8
@@ -22,19 +22,26 @@
|
||||
================================================================ */
|
||||
const tabRelay = document.getElementById('pqTabRelay');
|
||||
const tabPaste = document.getElementById('pqTabPaste');
|
||||
const tabSignIn = document.getElementById('pqTabSignIn');
|
||||
const panelRelay = document.getElementById('pqPanelRelay');
|
||||
const panelPaste = document.getElementById('pqPanelPaste');
|
||||
const panelSignIn = document.getElementById('pqPanelSignIn');
|
||||
|
||||
function switchTab(which) {
|
||||
const isRelay = which === 'relay';
|
||||
const isPaste = which === 'paste';
|
||||
const isSignIn = which === 'signin';
|
||||
tabRelay.classList.toggle('pq-tab-active', isRelay);
|
||||
tabPaste.classList.toggle('pq-tab-active', !isRelay);
|
||||
tabPaste.classList.toggle('pq-tab-active', isPaste);
|
||||
tabSignIn.classList.toggle('pq-tab-active', isSignIn);
|
||||
panelRelay.classList.toggle('pq-tab-panel-active', isRelay);
|
||||
panelPaste.classList.toggle('pq-tab-panel-active', !isRelay);
|
||||
panelPaste.classList.toggle('pq-tab-panel-active', isPaste);
|
||||
panelSignIn.classList.toggle('pq-tab-panel-active', isSignIn);
|
||||
}
|
||||
|
||||
tabRelay.addEventListener('click', () => switchTab('relay'));
|
||||
tabPaste.addEventListener('click', () => switchTab('paste'));
|
||||
tabSignIn.addEventListener('click', () => switchTab('signin'));
|
||||
|
||||
/* ================================================================
|
||||
SHARED DOM + STATE
|
||||
@@ -607,6 +614,141 @@
|
||||
}
|
||||
});
|
||||
|
||||
/* ================================================================
|
||||
AUTHENTICATION (nostr-login-lite / NIP-07)
|
||||
================================================================ */
|
||||
async function initNostrLoginLite() {
|
||||
if (!window.NOSTR_LOGIN_LITE) return false;
|
||||
if (!window.NOSTR_LOGIN_LITE._initialized) {
|
||||
await window.NOSTR_LOGIN_LITE.init({
|
||||
methods: { extension: true, local: true, nip46: true, seedphrase: true, nsigner: true }
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fill the pubkey field and auto-query relays for the signed-in user's
|
||||
// kind 9999 event. Switches to the Query Relay tab so the user sees the
|
||||
// results. Returns the pubkey hex on success, null otherwise.
|
||||
async function queryAsSignedInUser() {
|
||||
if (!window.nostr || !window.nostr.getPublicKey) {
|
||||
throw new Error('No Nostr signer available. Install a NIP-07 browser extension (nos2x, Alby) or use nostr-login-lite.');
|
||||
}
|
||||
const pubkeyHex = await window.nostr.getPublicKey();
|
||||
if (!pubkeyHex) throw new Error('No pubkey returned by signer.');
|
||||
|
||||
console.log('[verify] Signed-in pubkey:', pubkeyHex);
|
||||
const pubkeyInput = document.getElementById('pqPubkeyInput');
|
||||
const npub = hexToNpub(pubkeyHex);
|
||||
pubkeyInput.value = npub || pubkeyHex;
|
||||
|
||||
// Show the query tab so the user sees the results
|
||||
switchTab('relay');
|
||||
queryBtn.click();
|
||||
return pubkeyHex;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
TAB 3: SIGN IN
|
||||
================================================================ */
|
||||
const signInBtn = document.getElementById('pqSignInBtn');
|
||||
const signInError = document.getElementById('pqSignInError');
|
||||
const signInStatus = document.getElementById('pqSignInStatus');
|
||||
|
||||
signInBtn.addEventListener('click', async () => {
|
||||
signInBtn.disabled = true;
|
||||
signInError.innerHTML = '';
|
||||
setStatus(signInStatus, 'info', 'Signing in...');
|
||||
try {
|
||||
if (window.NOSTR_LOGIN_LITE) {
|
||||
await initNostrLoginLite();
|
||||
// Try a restored session first
|
||||
if (window.nostr) {
|
||||
try {
|
||||
const pk = await window.nostr.getPublicKey();
|
||||
if (pk) {
|
||||
await queryAsSignedInUser();
|
||||
setStatus(signInStatus, 'success', 'Signed in. Querying your event...');
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('[verify] Not yet authenticated, launching login modal...');
|
||||
}
|
||||
}
|
||||
// Launch the login modal and wait for auth events
|
||||
const pubkeyHex = await new Promise((resolve, reject) => {
|
||||
let settled = false;
|
||||
const timeout = setTimeout(() => {
|
||||
if (!settled) { settled = true; cleanup(); reject(new Error('Login timed out')); }
|
||||
}, 120000);
|
||||
|
||||
async function tryGetPubkey() {
|
||||
if (settled) return;
|
||||
if (window.nostr) {
|
||||
try {
|
||||
const pk = await window.nostr.getPublicKey();
|
||||
if (pk && !settled) { settled = true; cleanup(); resolve(pk); }
|
||||
} catch (e) { console.log('[verify] getPublicKey not ready:', e.message); }
|
||||
}
|
||||
}
|
||||
|
||||
function authHandler(e) {
|
||||
if (e.type === 'nlMethodSelected' && e.detail && e.detail.pubkey) {
|
||||
if (!settled) { settled = true; cleanup(); resolve(e.detail.pubkey); }
|
||||
return;
|
||||
}
|
||||
tryGetPubkey();
|
||||
setTimeout(tryGetPubkey, 200);
|
||||
setTimeout(tryGetPubkey, 500);
|
||||
setTimeout(tryGetPubkey, 1000);
|
||||
setTimeout(tryGetPubkey, 2000);
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
window.removeEventListener('nlAuth', authHandler);
|
||||
window.removeEventListener('nlAuthRestored', authHandler);
|
||||
window.removeEventListener('nlMethodSelected', authHandler);
|
||||
window.removeEventListener('nlAuthComplete', authHandler);
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
|
||||
window.addEventListener('nlAuth', authHandler);
|
||||
window.addEventListener('nlAuthRestored', authHandler);
|
||||
window.addEventListener('nlMethodSelected', authHandler);
|
||||
window.addEventListener('nlAuthComplete', authHandler);
|
||||
|
||||
try {
|
||||
const result = window.NOSTR_LOGIN_LITE.launch();
|
||||
if (result && typeof result.then === 'function') {
|
||||
result.then(() => {
|
||||
tryGetPubkey(); setTimeout(tryGetPubkey, 200); setTimeout(tryGetPubkey, 500);
|
||||
}).catch(e => { if (!settled) { settled = true; cleanup(); reject(e); } });
|
||||
}
|
||||
} catch (e) {
|
||||
if (!settled) { settled = true; cleanup(); reject(e); }
|
||||
}
|
||||
});
|
||||
// Fill the pubkey field with the launched-session pubkey and query
|
||||
const pubkeyInput = document.getElementById('pqPubkeyInput');
|
||||
const npub = hexToNpub(pubkeyHex);
|
||||
pubkeyInput.value = npub || pubkeyHex;
|
||||
switchTab('relay');
|
||||
queryBtn.click();
|
||||
setStatus(signInStatus, 'success', 'Signed in. Querying your event...');
|
||||
} else {
|
||||
// No nostr-login-lite — fall back to a plain NIP-07 extension
|
||||
await queryAsSignedInUser();
|
||||
setStatus(signInStatus, 'success', 'Signed in. Querying your event...');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[verify] Sign-in failed:', error);
|
||||
setStatus(signInError, 'error', error.message);
|
||||
setStatus(signInStatus, 'error', 'Sign-in failed.');
|
||||
} finally {
|
||||
signInBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
/* ================================================================
|
||||
AUTO-DETECT SIGNED-IN USER
|
||||
On page load, check for a NIP-07 signer / nostr-login-lite. If the
|
||||
@@ -614,14 +756,9 @@
|
||||
relays for their kind 9999 event.
|
||||
================================================================ */
|
||||
async function initAutoDetect() {
|
||||
// Initialize nostr-login-lite if available (shares session with main page)
|
||||
if (window.NOSTR_LOGIN_LITE) {
|
||||
try {
|
||||
if (!window.NOSTR_LOGIN_LITE._initialized) {
|
||||
await window.NOSTR_LOGIN_LITE.init({
|
||||
methods: { extension: true, local: true, nip46: true, seedphrase: true, nsigner: true }
|
||||
});
|
||||
}
|
||||
await initNostrLoginLite();
|
||||
} catch (e) {
|
||||
console.log('[verify] nostr-login-lite init failed:', e.message);
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"VERSION": "v0.0.33",
|
||||
"VERSION_NUMBER": "0.0.33",
|
||||
"BUILD_DATE": "2026-07-26T15:58:51.892Z"
|
||||
"VERSION": "v0.0.35",
|
||||
"VERSION_NUMBER": "0.0.35",
|
||||
"BUILD_DATE": "2026-07-27T17:03:35.968Z"
|
||||
}
|
||||
|
||||
+25
-19
@@ -254,35 +254,19 @@
|
||||
|
||||
<div class="pq-card">
|
||||
<div class="pq-card-title">Verify Post-Quantum Key Link</div>
|
||||
<div class="pq-info-text" style="color: var(--accent-color); font-size: 13px;">
|
||||
<strong>Note:</strong> Passing these checks confirms the keys are linked and the signatures
|
||||
are valid — it does not mean the identity is post-quantum secure. The Bitcoin timestamp check
|
||||
relies on a public block explorer rather than running a full Bitcoin node, so a compromised
|
||||
explorer could fool it.
|
||||
</div>
|
||||
<div class="pq-info-text">
|
||||
You can check a post-quantum key-link event two ways: look it up on a relay by pubkey, or
|
||||
paste the event JSON directly. The event you're checking (kind 9999) wraps a public
|
||||
announcement (kind 1) and carries a Bitcoin timestamp proof. Verification checks each piece
|
||||
separately and reports the results below: both Nostr signatures, the link between the two
|
||||
events, each post-quantum signature (ML-DSA-44, ML-DSA-65, SLH-DSA-128s, Falcon-512 —
|
||||
ML-KEM-768 is for encryption, so it has no signature to check), that all required algorithms
|
||||
are present, that the event really belongs to the claimed identity, and the Bitcoin timestamp.
|
||||
</div>
|
||||
<div class="pq-info-text">
|
||||
<a href="./" class="pq-link">Back to preparation page</a>
|
||||
</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 it.
|
||||
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..." />
|
||||
@@ -319,6 +303,17 @@
|
||||
<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>
|
||||
|
||||
<!-- Shared results area -->
|
||||
<div id="pqResultsWrap" style="display: none;">
|
||||
<div class="pq-info-text" style="margin-top: 15px; margin-bottom: 5px;">
|
||||
@@ -346,6 +341,17 @@
|
||||
</div>
|
||||
<div id="pqOtsUpgradeStatus"></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>
|
||||
|
||||
Reference in New Issue
Block a user