G56-09: default seed generation to 24 words with 12/24 selector; remove inaccurate quantum-safe claims; fix misleading republish wording for non-replaceable kind 9999 upgrades
This commit is contained in:
@@ -108,7 +108,7 @@ The BIP39 seed is a 64-byte entropy string derived from a mnemonic via PBKDF2-HM
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
SEED[BIP39 Seed - 64 bytes, quantum-safe]
|
||||
SEED[BIP39 Seed - 64 bytes, recovery root]
|
||||
SEED --> BIP32[BIP32 derivation - secp256k1 keypair]
|
||||
SEED --> PQKEM[BIP32 derivation - ML-KEM keypair]
|
||||
SEED --> PQDSA[BIP32 derivation - ML-DSA keypair]
|
||||
@@ -173,10 +173,13 @@ This is not a separate or weaker construction — it IS BIP32. Using BIP32 paths
|
||||
|
||||
| Mnemonic length | Entropy | Post-quantum security (Grover's) | Assessment |
|
||||
|---|---|---|---|
|
||||
| 12 words | 128 bits | ~64 bits | Borderline |
|
||||
| 24 words | 256 bits | ~128 bits | Adequate |
|
||||
| 12 words | 128 bits | ~64 bits | Testing only — weaker under a quantum brute-force model |
|
||||
| 24 words | 256 bits | ~128 bits | Recommended for a PQ recovery root |
|
||||
|
||||
For users concerned about quantum attacks on the seed entropy itself, 24-word mnemonics are recommended. The bigger risk to seed phrases is classical (theft, phishing), not quantum. (Note: the current implementation defaults to 12-word mnemonics; 24-word support is recommended for production use.)
|
||||
The implementation defaults to 24-word (256-bit) mnemonics, with a 12-word option available for
|
||||
testing. Users concerned about quantum attacks on the seed entropy itself should use 24 words. The
|
||||
bigger risk to seed phrases is classical (theft, phishing), not quantum. Note that BIP39's PBKDF2
|
||||
iteration count is low; the seed phrase should be treated as a high-value secret and stored offline.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
|---|---:|---:|---:|---:|
|
||||
| Critical | 2 | 2 | 0 | 0 |
|
||||
| High | 6 | 1 | 0 | 5 |
|
||||
| Medium | 8 | 0 | 0 | 8 |
|
||||
| Medium | 8 | 1 | 0 | 7 |
|
||||
| Low / Info | 4 | 0 | 0 | 4 |
|
||||
| **Total** | **20** | **3** | **0** | **17** |
|
||||
| **Total** | **20** | **4** | **0** | **16** |
|
||||
|
||||
## Findings status
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
| G56-06 | High | New seed continuity asserted, not proven — no successor signature, no identity match | ❌ Not started | — | Protocol design decision needed |
|
||||
| G56-07 | High | Verifier throws on malformed relay/paste input | ⚠️ Partially fixed | v0.0.12 | G56-01 fix wrapped base64/hex decoding in try/catch and made verifyNIPQRContent fail closed. Broader call-site wrapping in verify.html also done. Remaining: fuzz testing, all export functions |
|
||||
| G56-08 | High | Browser origin security insufficient for seed handling | ❌ Not started | — | Needs CSP hardening, header fixes, asset pinning |
|
||||
| G56-09 | Medium | 12-word seed default, warning removed | ❌ Not started | — | Default to 24 words or restore warning |
|
||||
| G56-09 | Medium | 12-word seed default, warning removed | ✅ Fixed | v0.0.17 | Defaulted `generateSeedPhrase()` and `generateSeedPhraseWithEntropy()` to 24 words (256-bit); added 12/24-word selector UI; restored entropy-strength warning; removed "quantum-safe root of trust" / "Make your Nostr identity quantum-resistant" claims from index.html, README.md, nip_proposal.md; 4 new tests |
|
||||
| G56-10 | Medium | Secret material not zeroized, retained in globals/DOM/clipboard | ❌ Not started | — | Minimize secret lifetime, overwrite typed arrays |
|
||||
| G56-11 | Medium | Publication advances despite zero relay acceptance | ❌ Not started | — | Require relay OK for both events |
|
||||
| G56-12 | Medium | Resume workflow insufficiently bound to original event | ⚠️ Partially fixed | v0.0.14 | Resume now fetches all candidates and matches by event ID. Remaining: persist complete immutable artifacts, validate all bindings before signing |
|
||||
@@ -74,9 +74,20 @@
|
||||
- Updated explanation.md, laans_explanation.md, why_what_how.md
|
||||
- Cleaned up button IDs and import statements in index.html
|
||||
|
||||
### v0.0.17 — G56-09 fix
|
||||
- `generateSeedPhrase()` and `generateSeedPhraseWithEntropy()` now default to 24 words (256-bit entropy)
|
||||
- Both accept an `entropyBits` argument (128 or 256) and validate it
|
||||
- Added 12/24-word radio selector to the seed-generation panel in `index.html`; toggling regenerates the seed
|
||||
- Restored entropy-strength warning (12 words = ~64-bit under Grover, testing only; 24 words recommended)
|
||||
- Removed inaccurate claims: "Make your Nostr identity quantum-resistant" → "Create an experimental pre-quantum PQ key commitment"; "quantum-safe root of trust" → "recovery root"; "quantum-safe seed phrase" → "seed phrase"
|
||||
- Added research-prototype warning to the unauthenticated landing card
|
||||
- Updated README.md entropy table and nip_proposal.md mermaid diagram
|
||||
- Added 4 new tests (24-word default, 12-word option, invalid entropyBits rejection for both functions)
|
||||
|
||||
## Test count
|
||||
|
||||
- **Before remediation:** 38 tests
|
||||
- **After G56-01:** 49 tests
|
||||
- **After G56-02+03:** 61 tests
|
||||
- **After G56-09:** 65 tests
|
||||
- **All passing:** ✅
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ The PQ keys are derived deterministically from a BIP39 seed phrase via BIP32, at
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
SEED[BIP39 seed - quantum-safe root of trust] --> SECP[secp256k1 keypair - child 0]
|
||||
SEED[BIP39 seed - recovery root] --> SECP[secp256k1 keypair - child 0]
|
||||
SEED --> PQ[PQ keypairs - children 1..8]
|
||||
SECP --> K1[Kind 1 announcement - attesting identity signs]
|
||||
PQ --> K1
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "post_quantum_nostr",
|
||||
"version": "0.0.16",
|
||||
"version": "0.0.17",
|
||||
"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": {
|
||||
|
||||
+27
-4
@@ -31,18 +31,41 @@ const m = await import('../www/js/pq-crypto.mjs');
|
||||
// ============================================================================
|
||||
|
||||
describe('BIP39 seed phrase', () => {
|
||||
test('generateSeedPhrase produces a valid 12-word mnemonic', () => {
|
||||
test('generateSeedPhrase defaults to a valid 24-word mnemonic', () => {
|
||||
const mnemonic = m.generateSeedPhrase();
|
||||
assert.ok(m.isValidMnemonic(mnemonic), 'generated mnemonic should be valid');
|
||||
const words = mnemonic.split(' ');
|
||||
assert.equal(words.length, 12, 'should be 12 words');
|
||||
assert.equal(words.length, 24, 'default should be 24 words');
|
||||
});
|
||||
|
||||
test('generateSeedPhraseWithEntropy produces a valid mnemonic', () => {
|
||||
test('generateSeedPhrase(128) produces a valid 12-word mnemonic', () => {
|
||||
const mnemonic = m.generateSeedPhrase(128);
|
||||
assert.ok(m.isValidMnemonic(mnemonic), 'generated mnemonic should be valid');
|
||||
assert.equal(mnemonic.split(' ').length, 12, 'should be 12 words');
|
||||
});
|
||||
|
||||
test('generateSeedPhrase rejects invalid entropyBits', () => {
|
||||
assert.throws(() => m.generateSeedPhrase(64), /entropyBits must be 128/);
|
||||
assert.throws(() => m.generateSeedPhrase(192), /entropyBits must be 128/);
|
||||
});
|
||||
|
||||
test('generateSeedPhraseWithEntropy defaults to a 24-word mnemonic', () => {
|
||||
const userEntropy = new TextEncoder().encode('test entropy from user');
|
||||
const mnemonic = m.generateSeedPhraseWithEntropy(userEntropy);
|
||||
assert.ok(m.isValidMnemonic(mnemonic), 'mnemonic with entropy should be valid');
|
||||
assert.equal(mnemonic.split(' ').length, 12);
|
||||
assert.equal(mnemonic.split(' ').length, 24, 'default should be 24 words');
|
||||
});
|
||||
|
||||
test('generateSeedPhraseWithEntropy(128) produces a 12-word mnemonic', () => {
|
||||
const userEntropy = new TextEncoder().encode('test entropy from user');
|
||||
const mnemonic = m.generateSeedPhraseWithEntropy(userEntropy, 128);
|
||||
assert.ok(m.isValidMnemonic(mnemonic), 'mnemonic with entropy should be valid');
|
||||
assert.equal(mnemonic.split(' ').length, 12, 'should be 12 words');
|
||||
});
|
||||
|
||||
test('generateSeedPhraseWithEntropy rejects invalid entropyBits', () => {
|
||||
const userEntropy = new TextEncoder().encode('test entropy');
|
||||
assert.throws(() => m.generateSeedPhraseWithEntropy(userEntropy, 64), /entropyBits must be 128/);
|
||||
});
|
||||
|
||||
test('generateSeedPhraseWithEntropy produces different mnemonics for different CSPRNG draws', () => {
|
||||
|
||||
+50
-12
@@ -292,7 +292,7 @@
|
||||
</div>
|
||||
<div class="pq-checklist-item" id="step-2">
|
||||
<span class="pq-checklist-box"></span>
|
||||
<span>2. Generate a quantum-safe seed phrase</span>
|
||||
<span>2. Generate a seed phrase</span>
|
||||
</div>
|
||||
<div class="pq-checklist-item" id="step-3">
|
||||
<span class="pq-checklist-box"></span>
|
||||
@@ -324,13 +324,18 @@
|
||||
<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);">
|
||||
Make your Nostr identity quantum-resistant
|
||||
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 quantum-safe seed phrase and link
|
||||
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>
|
||||
@@ -352,7 +357,7 @@
|
||||
<div class="pq-info-text">
|
||||
This tool will:
|
||||
<ol>
|
||||
<li>Generate a new <strong>seed phrase</strong> (your quantum-safe backup)</li>
|
||||
<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>
|
||||
@@ -363,12 +368,17 @@
|
||||
|
||||
<!-- STEP 2: SEED PHRASE GENERATION -->
|
||||
<div id="pqSeedStep" class="pq-card pq-hidden">
|
||||
<div class="pq-card-title">Your Quantum-Safe Seed Phrase</div>
|
||||
<div class="pq-card-title">Your Seed Phrase</div>
|
||||
<div class="pq-info-text">
|
||||
Your seed phrase is your <strong>quantum-safe root of trust</strong>. All post-quantum keys
|
||||
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;">
|
||||
@@ -379,6 +389,11 @@
|
||||
|
||||
<!-- 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: 15px; font-size: 13px;">
|
||||
<strong>Optional:</strong> Add extra entropy by moving your mouse and typing in the box below.
|
||||
@@ -529,8 +544,9 @@
|
||||
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 re-sign and republish the kind 9999 event with the upgraded
|
||||
proof. You're free to close the page; the workflow resumes on your next visit.
|
||||
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>
|
||||
|
||||
@@ -940,14 +956,21 @@
|
||||
let userEntropyChunks = [];
|
||||
let entropyCollecting = false;
|
||||
|
||||
function getSelectedEntropyBits() {
|
||||
const radio = document.querySelector('input[name="pqWordCount"]:checked');
|
||||
return radio ? parseInt(radio.value, 10) : 256;
|
||||
}
|
||||
|
||||
function generateAndShowSeed() {
|
||||
const entropyBits = getSelectedEntropyBits();
|
||||
// Use user entropy if available, otherwise pure CSPRNG
|
||||
if (userEntropyChunks.length > 0) {
|
||||
const userEntropy = new TextEncoder().encode(userEntropyChunks.join(''));
|
||||
pqMnemonic = generateSeedPhraseWithEntropy(userEntropy);
|
||||
otsLog(`Seed generated with ${userEntropyChunks.length} entropy chunks from user input.`);
|
||||
pqMnemonic = generateSeedPhraseWithEntropy(userEntropy, entropyBits);
|
||||
otsLog(`Seed generated with ${userEntropyChunks.length} entropy chunks from user input (${entropyBits}-bit).`);
|
||||
} else {
|
||||
pqMnemonic = generateSeedPhrase();
|
||||
pqMnemonic = generateSeedPhrase(entropyBits);
|
||||
otsLog(`Seed generated (${entropyBits}-bit).`);
|
||||
}
|
||||
const words = pqMnemonic.split(' ');
|
||||
pqSeedDisplay.innerHTML = words.map((word, i) =>
|
||||
@@ -1362,6 +1385,21 @@
|
||||
document.getElementById('pqSeedToggleGenerate').addEventListener('click', () => showSeedGeneratePanel());
|
||||
document.getElementById('pqSeedToggleOwn').addEventListener('click', () => showSeedOwnPanel());
|
||||
|
||||
// Word-count selector: regenerate when the user switches between 12/24 words
|
||||
document.querySelectorAll('input[name="pqWordCount"]').forEach((radio) => {
|
||||
radio.addEventListener('change', () => {
|
||||
userEntropyChunks = [];
|
||||
generateAndShowSeed();
|
||||
// Reset entropy UI
|
||||
const entropyBar = document.getElementById('pqEntropyBar');
|
||||
const entropyHint = document.getElementById('pqEntropyHint');
|
||||
const regenWithEntropyBtn = document.getElementById('pqRegenerateWithEntropyBtn');
|
||||
if (entropyBar) entropyBar.style.width = '0%';
|
||||
if (entropyHint) entropyHint.textContent = 'Optional: click here and type random characters, move your mouse to add entropy...';
|
||||
if (regenWithEntropyBtn) { regenWithEntropyBtn.classList.add('pq-hidden'); regenWithEntropyBtn.disabled = true; }
|
||||
});
|
||||
});
|
||||
|
||||
// Bring your own seed: real-time validation
|
||||
const pqSeedInput = document.getElementById('pqSeedInput');
|
||||
const pqSeedValidation = document.getElementById('pqSeedValidation');
|
||||
@@ -1609,7 +1647,7 @@
|
||||
}
|
||||
|
||||
// No proof in the event — OTS submission failed during step 4.
|
||||
// Submit the kind 1 event hash to OpenTimestamps now, then republish the event with the proof.
|
||||
// Submit the kind 1 event hash to OpenTimestamps now, then publish a new kind 9999 event with the proof.
|
||||
const sha256Tag = pqEvent.tags && pqEvent.tags.find(t => t[0] === 'sha256');
|
||||
if (!sha256Tag || !sha256Tag[1]) {
|
||||
otsLog('ERROR: No sha256 tag found in event. Cannot timestamp.');
|
||||
|
||||
+28
-10
@@ -70,11 +70,20 @@ const PQ_SEED_LENGTHS = {
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Generate a new 12-word BIP39 mnemonic.
|
||||
* @returns {string} 12-word seed phrase
|
||||
* Generate a new BIP39 mnemonic.
|
||||
*
|
||||
* Defaults to 24 words (256 bits of entropy) so the recovery root is not the
|
||||
* weakest link in a system whose PQ algorithms target NIST Category 1+.
|
||||
* Pass `128` for `entropyBits` to generate a 12-word phrase (e.g. for testing).
|
||||
*
|
||||
* @param {number} [entropyBits=256] - 128 (12 words) or 256 (24 words)
|
||||
* @returns {string} BIP39 seed phrase (24 words by default)
|
||||
*/
|
||||
export function generateSeedPhrase() {
|
||||
return generateMnemonic(wordlist, 128); // 128 bits = 12 words
|
||||
export function generateSeedPhrase(entropyBits = 256) {
|
||||
if (entropyBits !== 128 && entropyBits !== 256) {
|
||||
throw new Error('entropyBits must be 128 (12 words) or 256 (24 words)');
|
||||
}
|
||||
return generateMnemonic(wordlist, entropyBits);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,20 +94,29 @@ export function generateSeedPhrase() {
|
||||
* compromised, the seed remains unpredictable to an attacker who doesn't have
|
||||
* the user's entropy.
|
||||
*
|
||||
* Defaults to 24 words (256 bits). Pass `128` for `entropyBits` to generate a
|
||||
* 12-word phrase.
|
||||
*
|
||||
* @param {Uint8Array} userEntropy - Additional entropy from the user (any length)
|
||||
* @returns {string} A valid 12-word BIP39 mnemonic
|
||||
* @param {number} [entropyBits=256] - 128 (12 words) or 256 (24 words)
|
||||
* @returns {string} A valid BIP39 mnemonic (24 words by default)
|
||||
*/
|
||||
export function generateSeedPhraseWithEntropy(userEntropy) {
|
||||
// Get 16 bytes (128 bits) of CSPRNG entropy
|
||||
const csprngBytes = new Uint8Array(16);
|
||||
export function generateSeedPhraseWithEntropy(userEntropy, entropyBits = 256) {
|
||||
if (entropyBits !== 128 && entropyBits !== 256) {
|
||||
throw new Error('entropyBits must be 128 (12 words) or 256 (24 words)');
|
||||
}
|
||||
const entropyBytes = entropyBits / 8; // 16 or 32
|
||||
|
||||
// Get CSPRNG entropy of the requested size
|
||||
const csprngBytes = new Uint8Array(entropyBytes);
|
||||
crypto.getRandomValues(csprngBytes);
|
||||
|
||||
// Combine CSPRNG entropy with user entropy via SHA-256
|
||||
const combined = concatBytes(csprngBytes, userEntropy);
|
||||
const hash = sha256(combined);
|
||||
|
||||
// Use the first 16 bytes (128 bits) of the hash as the entropy
|
||||
const finalEntropy = hash.slice(0, 16);
|
||||
// Use the first `entropyBytes` bytes of the hash as the entropy
|
||||
const finalEntropy = hash.slice(0, entropyBytes);
|
||||
|
||||
return entropyToMnemonic(finalEntropy, wordlist);
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"VERSION": "v0.0.16",
|
||||
"VERSION_NUMBER": "0.0.16",
|
||||
"BUILD_DATE": "2026-07-24T12:13:07.640Z"
|
||||
"VERSION": "v0.0.17",
|
||||
"VERSION_NUMBER": "0.0.17",
|
||||
"BUILD_DATE": "2026-07-24T12:44:54.874Z"
|
||||
}
|
||||
|
||||
+12
-5
@@ -9609,15 +9609,22 @@ var PQ_SEED_LENGTHS = {
|
||||
falcon512: 48,
|
||||
mlKem: 64
|
||||
};
|
||||
function generateSeedPhrase() {
|
||||
return generateMnemonic(wordlist, 128);
|
||||
function generateSeedPhrase(entropyBits = 256) {
|
||||
if (entropyBits !== 128 && entropyBits !== 256) {
|
||||
throw new Error("entropyBits must be 128 (12 words) or 256 (24 words)");
|
||||
}
|
||||
return generateMnemonic(wordlist, entropyBits);
|
||||
}
|
||||
function generateSeedPhraseWithEntropy(userEntropy) {
|
||||
const csprngBytes = new Uint8Array(16);
|
||||
function generateSeedPhraseWithEntropy(userEntropy, entropyBits = 256) {
|
||||
if (entropyBits !== 128 && entropyBits !== 256) {
|
||||
throw new Error("entropyBits must be 128 (12 words) or 256 (24 words)");
|
||||
}
|
||||
const entropyBytes = entropyBits / 8;
|
||||
const csprngBytes = new Uint8Array(entropyBytes);
|
||||
crypto.getRandomValues(csprngBytes);
|
||||
const combined = concatBytes4(csprngBytes, userEntropy);
|
||||
const hash = sha256(combined);
|
||||
const finalEntropy = hash.slice(0, 16);
|
||||
const finalEntropy = hash.slice(0, entropyBytes);
|
||||
return entropyToMnemonic(finalEntropy, wordlist);
|
||||
}
|
||||
function mnemonicToSeed(mnemonic, passphrase = "") {
|
||||
|
||||
+4
-4
@@ -330,7 +330,7 @@
|
||||
<div id="pqOtsInfo" class="pq-status pq-status-info"></div>
|
||||
<div class="pq-button-row">
|
||||
<button class="pq-button" id="pqOtsUpgradeBtn">Upgrade OTS Proof</button>
|
||||
<button class="pq-button" id="pqOtsRepublishBtn" style="display:none;">Republish with Confirmed Proof</button>
|
||||
<button class="pq-button" id="pqOtsRepublishBtn" style="display:none;">Publish Upgraded Event</button>
|
||||
</div>
|
||||
<div id="pqOtsUpgradeStatus"></div>
|
||||
</div>
|
||||
@@ -844,7 +844,7 @@
|
||||
if (confirmed) {
|
||||
const att = verifyResult.bitcoinAttestations[0];
|
||||
const date = att ? new Date(att.time * 1000).toISOString().substring(0, 19) : 'unknown';
|
||||
setStatus(otsUpgradeStatus, 'success', `Bitcoin attestation verified! Block ${att ? att.height : '?'} (mined ${date} UTC). Proof upgraded (${currentOtsBytes.length} bytes). You can republish the event with the confirmed proof below.`);
|
||||
setStatus(otsUpgradeStatus, 'success', `Bitcoin attestation verified! Block ${att ? att.height : '?'} (mined ${date} UTC). Proof upgraded (${currentOtsBytes.length} bytes). You can publish a new kind 9999 event carrying the confirmed proof below; it will reference the original via an upgrade_of tag.`);
|
||||
otsUpgradeBtn.style.display = 'none';
|
||||
otsRepublishBtn.style.display = 'inline-block';
|
||||
setOtsBadge('pq-ots-badge-confirmed', 'OTS: Verified (Bitcoin)');
|
||||
@@ -866,7 +866,7 @@
|
||||
|
||||
try {
|
||||
if (!window.nostr || !window.nostr.signEvent) {
|
||||
throw new Error('Nostr signer (window.nostr) not available. Use a NIP-07 signer extension to republish.');
|
||||
throw new Error('Nostr signer (window.nostr) not available. Use a NIP-07 signer extension to publish the upgraded event.');
|
||||
}
|
||||
|
||||
const upgradedTemplate = buildUpgradedEvent(currentEvent, currentOtsBytes);
|
||||
@@ -913,7 +913,7 @@
|
||||
// Re-verify the new event
|
||||
await verifyEvent(signedEvent);
|
||||
} catch (error) {
|
||||
setStatus(otsUpgradeStatus, 'error', `Republish failed: ${error.message}`);
|
||||
setStatus(otsUpgradeStatus, 'error', `Publish failed: ${error.message}`);
|
||||
} finally {
|
||||
otsRepublishBtn.disabled = false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user