// ============================================================================ // Entropy Edge Resolver — Reference Implementation // ============================================================================ // This JS mirrors the Rust `best_edge()` in rana's src/entropy.rs exactly. // Any Nostr client can use this to determine which prefix/suffix edge of an // npub to display prominently. The npub is self-describing. // ============================================================================ const BECH32_PREFIX = 'npub1'; const BECH32_MAX_ENTROPY = 5.0; // log2(32) — the bech32 alphabet has 32 symbols const ENTROPY_FLOOR = 3.0; // log2(8) — only edges with ≤8 distinct symbols qualify /// Shannon entropy in bits/char. function shannonEntropy(str) { if (!str || str.length === 0) return 0.0; const counts = {}; for (const ch of str) counts[ch] = (counts[ch] || 0) + 1; const n = str.length; let h = 0; for (const ch in counts) { const p = counts[ch] / n; h -= p * Math.log2(p); } return h; } /// Difficulty metric: L × (5 − H) — "bits of pattern". function edgeDifficulty(str) { if (!str || str.length === 0) return 0.0; const h = shannonEntropy(str); if (h > ENTROPY_FLOOR) return 0.0; // edge is too random — no pattern credit return str.length * (BECH32_MAX_ENTROPY - h); } /// Resolve the best edge: scan prefix and suffix lengths 1..29, /// return the one with max difficulty. function bestEdge(npub) { const data = npub.startsWith(BECH32_PREFIX) ? npub.slice(BECH32_PREFIX.length) : npub; const dataLen = data.length; if (dataLen === 0) return { side: 'prefix', length: 0, entropy: 0, difficulty: 0 }; const maxEdge = Math.min(29, dataLen); let best = { side: 'prefix', length: 0, entropy: 0, difficulty: 0 }; for (let L = 1; L <= maxEdge; L++) { const edge = data.slice(0, L); const h = shannonEntropy(edge); if (h > ENTROPY_FLOOR) continue; // skip edges above the quality floor const diff = L * (BECH32_MAX_ENTROPY - h); if (diff > best.difficulty) best = { side: 'prefix', length: L, entropy: h, difficulty: diff }; } for (let L = 1; L <= maxEdge; L++) { const edge = data.slice(dataLen - L); const h = shannonEntropy(edge); if (h > ENTROPY_FLOOR) continue; // skip edges above the quality floor const diff = L * (BECH32_MAX_ENTROPY - h); if (diff > best.difficulty) best = { side: 'suffix', length: L, entropy: h, difficulty: diff }; } return best; } /// Compute all prefix and suffix difficulties for the bar chart. function allEdgeDifficulties(npub) { const data = npub.startsWith(BECH32_PREFIX) ? npub.slice(BECH32_PREFIX.length) : npub; const dataLen = data.length; const maxEdge = Math.min(29, dataLen); const prefix = []; const suffix = []; for (let L = 1; L <= maxEdge; L++) { prefix.push({ length: L, entropy: shannonEntropy(data.slice(0, L)), difficulty: edgeDifficulty(data.slice(0, L)), }); suffix.push({ length: L, entropy: shannonEntropy(data.slice(dataLen - L)), difficulty: edgeDifficulty(data.slice(dataLen - L)), }); } return { prefix, suffix }; } // ============================================================================ // Rendering // ============================================================================ /// Render an npub with the winning edge highlighted. function renderNpubHTML(npub, edge) { const data = npub.startsWith(BECH32_PREFIX) ? npub.slice(BECH32_PREFIX.length) : npub; const dataLen = data.length; const edgeLen = Math.min(edge.length, dataLen); if (edge.side === 'prefix') { const e = data.slice(0, edgeLen); const rest = data.slice(edgeLen); return `${BECH32_PREFIX}${e}${rest}`; } else { const split = dataLen - edgeLen; const rest = data.slice(0, split); const e = data.slice(split); return `${BECH32_PREFIX}${rest}${e}`; } } /// Render the interactive resolver result. function renderResolver(npub) { const edge = bestEdge(npub); const allDiff = allEdgeDifficulties(npub); // Npub display document.getElementById('resolver-npub').innerHTML = renderNpubHTML(npub, edge); // Stats const sideStr = edge.side; document.getElementById('resolver-stats').innerHTML = ` Best edge: ${sideStr}, ${edge.length} chars — entropy ${edge.entropy.toFixed(3)} bits/char — difficulty ${edge.difficulty.toFixed(1)} bits `; // Bar chart renderBarChart(allDiff, edge); } /// Render the difficulty bar chart. function renderBarChart(allDiff, bestEdge) { const maxDiff = Math.max( ...allDiff.prefix.map((d) => d.difficulty), ...allDiff.suffix.map((d) => d.difficulty), 1 ); let html = '
L
suffix ←
→ prefix
'; for (let i = 0; i < allDiff.prefix.length; i++) { const p = allDiff.prefix[i]; const s = allDiff.suffix[i]; const pWidth = (p.difficulty / maxDiff) * 50; const sWidth = (s.difficulty / maxDiff) * 50; const pBest = bestEdge.side === 'prefix' && bestEdge.length === p.length; const sBest = bestEdge.side === 'suffix' && bestEdge.length === s.length; html += `
${p.length}
${s.difficulty.toFixed(1)}
${p.difficulty.toFixed(1)}
`; } document.getElementById('resolver-chart').innerHTML = html; } /// Gallery npubs for the comparison section. const GALLERY_NPUBS = [ { npub: 'npub1yx8qm2k7w3vp5dr9cfntu4shlu6z3aefkj8snwq5hlmvd7te4tcsjq7ut2', label: 'Random npub', }, { npub: 'npub1x7m2kp9qfl5d3wrt8hnqzy0vce4plm3k9j5w7f2g6h8d4s1n', label: 'Random npub', }, { npub: 'npub1aaaaaaaaaaaaaaaaaaaax7m2kp9qfl5d3wrt8hnqzy0vce4plm', label: 'Mined: repetitive prefix', }, { npub: 'npub1z898xl5g9xypn58zfpnffffx8g58m3ywk8wf4mv2qkvmcw04at3sn44jkn', label: 'Mined: difficulty 48', }, { npub: 'npub1x7m2kp9qfl5d3wrt8hnqzy0vce4plmqqqqqqqqqqqqqqqqqqqq', label: 'Mined: repetitive suffix', }, { npub: 'npub12heyhhtd3dd99h6e9ex938dye92grp8u7vsq2pevp9nrvcz79c3s7n4l8t', label: 'Mined: difficulty 48', }, ]; /// Render the gallery section. function renderGallery() { const container = document.getElementById('gallery'); let html = ''; for (const item of GALLERY_NPUBS) { const edge = bestEdge(item.npub); const isMined = edge.difficulty > 25; html += ` `; } container.innerHTML = html; } // ============================================================================ // Initialization // ============================================================================ document.addEventListener('DOMContentLoaded', () => { // Interactive resolver const input = document.getElementById('npub-input'); const defaultNpub = 'npub1z898xl5g9xypn58zfpnffffx8g58m3ywk8wf4mv2qkvmcw04at3sn44jkn'; input.value = defaultNpub; renderResolver(defaultNpub); input.addEventListener('input', (e) => { const val = e.target.value.trim(); if (val.length > 10) renderResolver(val); }); document.getElementById('random-btn').addEventListener('click', () => { const chars = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'; let data = ''; for (let i = 0; i < 59; i++) data += chars[Math.floor(Math.random() * chars.length)]; input.value = BECH32_PREFIX + data; renderResolver(input.value); }); // Gallery renderGallery(); });