Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
399a633697 | ||
|
|
a662686a0b | ||
|
|
668fb65efb | ||
|
|
270cddc433 |
@@ -204,12 +204,18 @@ Bitcoin's proof-of-work chain is quantum-resistant not because Bitcoin's signatu
|
||||
|
||||
### Current OTS verification status
|
||||
|
||||
> **Trust model.** The current OTS verification is **API-assisted**, not a full Bitcoin light-client
|
||||
> verification. Treat results accordingly.
|
||||
> **Trust model.** The current OTS verification is **multi-explorer-checked**, not a full Bitcoin
|
||||
> light-client verification. Treat results accordingly.
|
||||
|
||||
The current implementation parses the `.ots` proof, validates the Merkle path against a block header from a public Bitcoin explorer API (mempool.space), and detects confirmation by searching the proof bytes for the block-header attestation magic. Proof *upgrading* (asking calendars for a confirmed proof) is delegated to a server-side helper.
|
||||
The current implementation parses the `.ots` proof, walks the full Merkle path, and validates the resulting Merkle root against a Bitcoin block header fetched from **two independent public explorer APIs** (blockstream.info and mempool.space). The two providers are cross-checked and the verifier fails closed on disagreement, making coordinated false responses harder. The result's `trustMode` field reports the achieved trust level:
|
||||
|
||||
**Not yet done:** full light-client verification (validating block headers, proof-of-work, difficulty, and chain linkage independently, without trusting an explorer API) and multi-explorer cross-checking. Because the block header is trusted from a single API, a compromised explorer could falsify a confirmation — the UI labels this as "API-checked," not "cryptographically verified on Bitcoin." The vendored `javascript-opentimestamps` library in `resources/` provides the primitives for full light-client verification, which is planned for a future release.
|
||||
- `multi-explorer-checked` — both providers agreed on the Merkle root (strongest available mode).
|
||||
- `single-explorer-checked` — only one provider responded (weaker; the UI labels it accordingly).
|
||||
- `structural-only` — no Bitcoin attestation was verified (pending or none).
|
||||
|
||||
Proof *upgrading* (asking calendars for a confirmed proof) is delegated to a server-side helper.
|
||||
|
||||
**Not yet done:** full light-client verification — validating block headers, proof-of-work, difficulty, and chain linkage independently, without trusting any explorer API. Because the block header is trusted from explorer APIs (even when cross-checked), a compromised/colluding set of explorers could still falsify a confirmation. The UI labels results as "multi-explorer-checked (cross-verified)" or "single-explorer-checked (trusted API)," never as "trustless." The vendored `javascript-opentimestamps` library in `resources/` provides the primitives for full light-client verification, which is planned for a future release.
|
||||
|
||||
---
|
||||
|
||||
@@ -288,20 +294,25 @@ The current implementation is a static web app (`www/`) that performs the full p
|
||||
| NIP-01 event ID computation and Schnorr verification | Implemented | `www/js/pq-crypto.mjs` |
|
||||
| OpenTimestamps submission (pending proof) | Implemented | `www/js/pq-crypto.mjs` |
|
||||
| OTS upgrade polling (via server helper) | Implemented | `www/js/pq-crypto.mjs` |
|
||||
| OTS full Merkle-path verification, multi-explorer cross-checked (blockstream + mempool) | Implemented | `www/js/pq-crypto.mjs` |
|
||||
| Canonical proof carrier selection (earliest valid Bitcoin anchor wins) | Implemented | `www/js/pq-crypto.mjs` |
|
||||
| Signer output validation (G56-05: reject mutated/invalid signer responses) | Implemented | `www/js/pq-crypto.mjs` |
|
||||
| Proof archive export/import (offline verification without a relay) | Implemented | `www/js/pq-crypto.mjs`, `www/verify.html` |
|
||||
| Versioned PQ algorithm policy (F-D3: mandatory/KEM/ignored sets) | Implemented | `www/js/nip-qr-policy.mjs` |
|
||||
| Relay publishing (WebSocket) | Implemented | `www/index.html` |
|
||||
| Verification page (query relay or paste event JSON) | Implemented | `www/verify.html` |
|
||||
| Verification page (query relay, paste event JSON, or import proof archive) | Implemented | `www/verify.html` |
|
||||
| NIP-07 signer integration (nostr-login-lite) | Implemented | `www/index.html` |
|
||||
| Test suite + deterministic test vectors (seed → pubkeys) | Implemented | `test/pq-crypto.test.mjs`, `test/vectors/` |
|
||||
|
||||
### What is not yet implemented
|
||||
|
||||
| Component | Description |
|
||||
|---|---|
|
||||
| Full light-client Bitcoin verification | Validate block headers, proof-of-work, difficulty, and chain linkage independently of an explorer API (the vendored `javascript-opentimestamps` library in `resources/` provides primitives). Current path trusts a single explorer API for the header. |
|
||||
| Multi-explorer cross-checking | Compare independent Bitcoin APIs and reject disagreement |
|
||||
| Full light-client Bitcoin verification | Validate block headers, proof-of-work, difficulty, and chain linkage independently of any explorer API (the vendored `javascript-opentimestamps` library in `resources/` provides primitives). The current path cross-checks two explorer APIs but still trusts them for the header rather than verifying PoW/difficulty/chain-linkage itself. |
|
||||
| Raw-nsec preparation (Component 4) | Encrypt old nsec, publish kind 30078, 36-word phrase encoding |
|
||||
| Quantum-safe self-storage (Component 5) | OTP or symmetric-key encryption for kind 30078 data |
|
||||
| PQ event authentication / rotation / revocation | Companion protocols for signing future events with PQ keys, rotating/revoking keys, PQ encryption |
|
||||
| Independent implementation / test vectors | A second implementation reproducing canonical encoding, derivation, and selection |
|
||||
| Independent implementation | A second, independent implementation reproducing canonical encoding, derivation, and selection (deterministic test vectors already exist in `test/vectors/`; a third-party reimplementation is the remaining step). |
|
||||
| Relay event-size interop testing | The kind 1 announcement is ~20–30 KiB; the kind 9999 proof carrier embeds it plus OTS proof data. The UI shows the event size and warns if it exceeds ~60 KiB (some relays reject large events). Testing against target relay policies and a compact binary format remain as future work. |
|
||||
| Reproducible-build verification of the deployed bundle | The deployed `www/pq-crypto.bundle.js` is a build artifact produced by `build-pq-bundle.js` from the audited source in `www/js/pq-crypto.mjs`. Users must currently trust that the deployed bundle matches the source. Reproducible-build verification (so anyone can rebuild the bundle byte-for-byte and confirm the deployed file matches) is on the roadmap. |
|
||||
|
||||
@@ -329,6 +340,7 @@ The current implementation is a static web app (`www/`) that performs the full p
|
||||
- [NIST FIPS 203: ML-KEM (Kyber)](https://csrc.nist.gov/pubs/fips/203/final)
|
||||
- [NIST FIPS 204: ML-DSA (Dilithium)](https://csrc.nist.gov/pubs/fips/204/final)
|
||||
- [NIST FIPS 205: SLH-DSA (SPHINCS+)](https://csrc.nist.gov/pubs/fips/205/final)
|
||||
- [NIST FIPS 206: Falcon (draft)](https://csrc.nist.gov/pubs/fips/206/ipd) — Falcon-512 signature scheme
|
||||
- [OpenTimestamps](https://opentimestamps.org/)
|
||||
- [Open Quantum Safe](https://openquantumsafe.org/)
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "nostr_quantum_preparation",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.5",
|
||||
"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": {
|
||||
|
||||
@@ -823,6 +823,21 @@ a.nostr-embed-preview-text:hover {
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
/* Small, unobtrusive version badge fixed to the bottom-right of the page.
|
||||
Rendered by js/version-display.js. */
|
||||
.pq-version-badge {
|
||||
position: fixed;
|
||||
right: 8px;
|
||||
bottom: 6px;
|
||||
font-size: 10px;
|
||||
line-height: 1;
|
||||
color: var(--muted-color);
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
font-family: var(--font-family, monospace);
|
||||
}
|
||||
|
||||
.svgHeaderButtons {
|
||||
fill: none;
|
||||
stroke: var(--button-color);
|
||||
|
||||
@@ -629,12 +629,6 @@
|
||||
</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>
|
||||
|
||||
@@ -441,9 +441,64 @@
|
||||
}
|
||||
|
||||
resultsWrap.style.display = 'block';
|
||||
// Only the event owner (signed-in pubkey matches event pubkey) may
|
||||
// upgrade/republish. Non-owners see the verification result but the
|
||||
// upgrade action is hidden to avoid confusion and endpoint abuse.
|
||||
applyOwnershipGate(event);
|
||||
return allValid;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
OWNERSHIP GATE: only the event owner can upgrade/republish the OTS
|
||||
proof. Non-owners see the verification result but the upgrade action
|
||||
buttons are hidden and replaced with an explanatory note.
|
||||
================================================================ */
|
||||
// Cache the signed-in pubkey so we don't call getPublicKey repeatedly.
|
||||
let cachedSignedInPubkey = null;
|
||||
let cachedSignedInPubkeyResolved = false;
|
||||
|
||||
async function getSignedInPubkey() {
|
||||
if (cachedSignedInPubkeyResolved) return cachedSignedInPubkey;
|
||||
cachedSignedInPubkeyResolved = true;
|
||||
if (!window.nostr || !window.nostr.getPublicKey) return null;
|
||||
try {
|
||||
cachedSignedInPubkey = await window.nostr.getPublicKey();
|
||||
} catch (e) {
|
||||
cachedSignedInPubkey = null;
|
||||
}
|
||||
return cachedSignedInPubkey;
|
||||
}
|
||||
|
||||
function applyOwnershipGate(event) {
|
||||
// Default: hide the upgrade action row until we confirm ownership.
|
||||
const actionRow = otsUpgradeWrap.querySelector('.pq-button-row');
|
||||
if (!actionRow) return;
|
||||
getSignedInPubkey().then(signedInPubkey => {
|
||||
const isOwner = signedInPubkey && event && event.pubkey &&
|
||||
signedInPubkey.toLowerCase() === event.pubkey.toLowerCase();
|
||||
if (isOwner) {
|
||||
actionRow.style.display = '';
|
||||
const note = document.getElementById('pqOtsOwnerNote');
|
||||
if (note) note.remove();
|
||||
} else {
|
||||
// Non-owner (or not signed in): hide the buttons and show a note.
|
||||
actionRow.style.display = 'none';
|
||||
let note = document.getElementById('pqOtsOwnerNote');
|
||||
if (!note) {
|
||||
note = document.createElement('div');
|
||||
note.id = 'pqOtsOwnerNote';
|
||||
note.className = 'pq-info-text';
|
||||
note.style.fontSize = '13px';
|
||||
note.style.marginTop = '8px';
|
||||
otsUpgradeWrap.appendChild(note);
|
||||
}
|
||||
note.textContent = signedInPubkey
|
||||
? 'Only the event owner can upgrade and republish the OpenTimestamps proof. You are signed in as a different identity.'
|
||||
: 'Only the event owner can upgrade and republish the OpenTimestamps proof. Sign in with the Nostr identity that published this event to enable upgrading.';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
TAB 1: QUERY RELAY
|
||||
================================================================ */
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
/**
|
||||
* Version display: fetch version.json and update title + header.
|
||||
* Version display: fetch version.json and render a small, unobtrusive
|
||||
* version badge fixed to the bottom-right of the page.
|
||||
* Extracted from inline <script> for CSP compliance (G56-08).
|
||||
*
|
||||
* Auto-detects the page title prefix from the existing <title> tag.
|
||||
* The page <title> is still suffixed with the version for tab/bookmark
|
||||
* context, but the in-page header keeps only the human-readable title.
|
||||
*/
|
||||
(function () {
|
||||
// Derive the title prefix from the existing <title> element
|
||||
@@ -13,9 +15,16 @@
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (v) {
|
||||
var versionText = v.VERSION || '';
|
||||
// Suffix the browser tab/bookmark title with the version.
|
||||
document.title = titlePrefix + ' ' + versionText;
|
||||
var header = document.getElementById('divHeaderText');
|
||||
if (header) header.textContent = titlePrefix + ' ' + versionText;
|
||||
|
||||
// Render a small fixed version badge in the bottom-right corner.
|
||||
if (versionText) {
|
||||
var badge = document.createElement('div');
|
||||
badge.className = 'pq-version-badge';
|
||||
badge.textContent = versionText;
|
||||
document.body.appendChild(badge);
|
||||
}
|
||||
})
|
||||
.catch(function () { /* version.json not found — keep default title */ });
|
||||
})();
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"VERSION": "v0.1.1",
|
||||
"VERSION_NUMBER": "0.1.1",
|
||||
"BUILD_DATE": "2026-07-29T13:00:25.910Z"
|
||||
"VERSION": "v0.1.5",
|
||||
"VERSION_NUMBER": "0.1.5",
|
||||
"BUILD_DATE": "2026-07-29T13:52:32.430Z"
|
||||
}
|
||||
|
||||
@@ -376,13 +376,6 @@
|
||||
</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 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>
|
||||
|
||||
Reference in New Issue
Block a user