Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 0 additions & 16 deletions site/formulas.js
Original file line number Diff line number Diff line change
Expand Up @@ -384,19 +384,3 @@ async function loadSchnorrRef() {
}
}


// ---------- Shared formatters ----------

function fmtBytes(n) { return n.toLocaleString() + ' B'; }

function fmtCompressions(n) {
if (!isFinite(n)) return '—';
if (n >= 1e15) return n.toExponential(2);
if (n >= 1e12) return (n / 1e12).toFixed(2) + 'T';
if (n >= 1e9) return (n / 1e9).toFixed(2) + 'G';
if (n >= 1e6) return (n / 1e6).toFixed(2) + 'M';
if (n >= 1e3) return (n / 1e3).toFixed(1) + 'K';
return Math.round(n).toLocaleString();
}

function fmtCpb(n) { return n.toFixed(3); }
106 changes: 55 additions & 51 deletions site/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pinned parameters for SHRINCS</title>
<title>SHRINCS pinned parameters</title>
<link rel="icon"
href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🦐</text></svg>">
<link rel="stylesheet" href="style.css">
<style>
:root {
--bg: #f7f7f5;
Expand Down Expand Up @@ -36,7 +37,7 @@

body { padding: 24px; }

header, main, footer { max-width: 820px; margin: 0 auto; }
header, main { max-width: 820px; margin: 0 auto; }
header { margin-bottom: 16px; }

h1 {
Expand Down Expand Up @@ -149,12 +150,6 @@
font-size: 13px;
}

footer {
margin-top: 20px;
color: var(--text-mute);
font-size: 12px;
}

@media (max-width: 720px) {
.cards { grid-template-columns: 1fr; }
}
Expand All @@ -164,57 +159,65 @@
<body>

<header>
<h1>Pinned parameters for SHRINCS</h1>
<h1>SHRINCS pinned parameters</h1>
<p class="tagline">
Pinned parameters for the SHRINCS signature. Parameter explorer is available by <a href="stateless.html">Stateless</a> and <a href="stateful.html">Stateful</a> subpages.
Compare alternatives in the
<a href="stateless.html">Parameter Explorer</a> and the <a href="stateful.html">Stateful Comparison</a>.
<button type="button" class="text-btn" popovertarget="glossary">Glossary</button>
</p>
</header>

<main>
<div class="cards">

<div class="card">
<h2>Stateless</h2>
<h2>Stateless <button type="button" class="info" popovertarget="about-stateless" aria-label="About the stateless parameters">i</button></h2>
<div class="pop" id="about-stateless" popover>
<p>Candidate 1 of the report. The size includes the one-byte SHRINCS indicator.</p>
</div>
<p class="tuple">(h, d, k, a, w) = (45, 5, 10, 13, 16)</p>
<table class="metrics" id="stateless-table"></table>
<div class="card-link">
<a id="stateless-link" href="stateless.html#c=45,5,10,13,16">Inspect in the Parameter Explorer</a>
<a id="stateless-link" href="stateless.html#c=45,5,10,13,16">Open in the Parameter Explorer</a>
</div>
</div>

<div class="card">
<h2>Stateful, BXMSS</h2>
<p class="tuple" id="xmss-tuple">XMSS (w=64, WOTS+C, h = 20)</p>
<h2>Stateful, XMSS <button type="button" class="info" popovertarget="about-xmss" aria-label="About the XMSS parameters">i</button></h2>
<div class="pop" id="about-xmss" popover>
<p>The cached SigGen and the state are BDS upper bounds per signature.</p>
</div>
<p class="tuple" id="xmss-tuple">WOTS+C, w = 64, h = 20</p>
<div class="h-control">
<label>
<span class="name">Tree height h</span>
<input type="number" class="val-input" id="xmss-h-val" min="2" max="20" step="1" value="20">
</label>
<input type="range" id="xmss-h" min="2" max="20" step="1" value="20">
<input type="range" id="xmss-h" min="2" max="20" step="1" value="20" aria-label="Tree height h">
</div>
<table class="metrics" id="xmss-table"></table>
<div class="card-link">
<a id="xmss-link" href="stateful.html#cand=xmssmt-WC-64-20-1,uxmss-WC-64">Inspect in the Parameter Explorer</a>
<a id="xmss-link" href="stateful.html#cand=xmssmt-WC-64-20-1,uxmss-WC-64">Open in the Stateful Comparison</a>
</div>
</div>

<div class="card">
<h2>Stateful, UXMSS</h2>
<p class="tuple">UXMSS (w=64, WOTS+C)</p>
<h2>Stateful, UXMSS <button type="button" class="info" popovertarget="about-uxmss" aria-label="About the UXMSS parameters">i</button></h2>
<div class="pop" id="about-uxmss" popover>
<p>Ranges go from the first to the last signature. Each signature carries one more 16-byte node
than the one before, so size and SigVer grow while ρ and the cached SigGen fall.</p>
</div>
<p class="tuple">WOTS+C, w = 64</p>
<table class="metrics" id="uxmss-table"></table>
<div class="card-link">
<a href="stateful.html#cand=xmssmt-WC-64-20-1,uxmss-WC-64">Inspect in the Parameter Explorer</a>
<a href="stateful.html#cand=xmssmt-WC-64-20-1,uxmss-WC-64">Open in the Stateful Comparison</a>
</div>
</div>

</div>
</main>

<footer>
All costs are SHA-256 compression calls with the PK.seed block midstate-cached. Sizes include the one-byte SHRINCS indicator.
Ranges on UXMSS rows are first-last signature; the cached XMSS signing cost and cache size are BDS upper bounds.
</footer>

<script src="js/ui.js"></script>
<script src="formulas.js"></script>
<script>
'use strict';
Expand Down Expand Up @@ -248,15 +251,15 @@ <h2>Stateful, UXMSS</h2>
const size = shrincsStatelessSize(PINNED);
const cb = m.sv / size, cbW = m.svWorst / size;
document.getElementById('stateless-table').innerHTML = [
row('Signature Size', fmtBytes(size)),
row('Key Generation', fmtCompressions(m.kg) + ' C'),
row('Signature Generation', fmtCompressions(m.sg) + ' C'),
row('Signature Verification (average)', m.sv.toLocaleString() + ' C'),
row('Signature Verification (worst case)', m.svWorst.toLocaleString() + ' C'),
row('Compression-per-byte verification complexity (average)', fmtCpb(cb)),
row('Compression-per-byte verification complexity (worst case)', fmtCpb(cbW)),
row('C/B comparing to BIP-340 verification (max)', fmtCpb(cbW / SCHNORR.cb64) + '×'),
row('Signature Budget', '2^40'),
row('Max signatures', fmtPow2(40)),
row('Size (B)', fmtNum(size)),
row('KeyGen (C)', fmtNum(m.kg)),
row('SigGen (C)', fmtNum(m.sg)),
row('SigVer (C)', fmtNum(m.sv)),
row('SigVer, worst (C)', fmtNum(m.svWorst)),
row('ρ (C/B)', fmtRho(cb)),
row('ρ, worst (C/B)', fmtRho(cbW)),
row('ρ ÷ Schnorr, worst', fmtRatio(cbW / SCHNORR.cb64)),
].join('');
}

Expand All @@ -270,16 +273,17 @@ <h2>Stateful, UXMSS</h2>
const st = xmssMtStateBytes(h, 1, XMSS_W, 'WC');
const sv = xmssMtVerifyC(h, 1, XMSS_W, swn, 'WC');
const cb = sv / size;
document.getElementById('xmss-tuple').textContent = `XMSS (w=${XMSS_W}, WOTS+C, h = ${h})`;
document.getElementById('xmss-tuple').textContent = `WOTS+C, w = ${XMSS_W}, h = ${h}`;
document.getElementById('xmss-table').innerHTML = [
row('Signature Budget', `2^${h} signatures`),
row('Signature Size', fmtBytes(size)),
row('Key Generation', fmtCompressions(kg) + ' C'),
row('Signature Generation (with cache / cold)', `${fmtCompressions(sg)} / ${fmtCompressions(sgCold)} C`),
row('Cache Size', fmtBytes(st)),
row('Signature Verification', sv.toLocaleString() + ' C'),
row('Compression-per-Byte Verification Complexity', fmtCpb(cb)),
row('C/B Comparing to BIP-340 Verification', fmtCpb(cb / SCHNORR.cb64) + '×'),
row('Max signatures', fmtPow2(h)),
row('Size (B)', fmtNum(size)),
row('KeyGen (C)', fmtNum(kg)),
row('SigGen, cached (C)', fmtNum(sg)),
row('SigGen, cold (C)', fmtNum(sgCold)),
row('State (B)', fmtNum(st)),
row('SigVer (C)', fmtNum(sv)),
row('ρ (C/B)', fmtRho(cb)),
row('ρ ÷ Schnorr', fmtRatio(cb / SCHNORR.cb64)),
].join('');
const link = document.getElementById('xmss-link');
link.href = `stateful.html#cand=xmssmt-WC-${XMSS_W}-${h}-1,uxmss-WC-${XMSS_W}`;
Expand All @@ -298,15 +302,15 @@ <h2>Stateful, UXMSS</h2>
const svMax = uxmssVerifyC(hsf, hsf, XMSS_W, swn, 'WC');
const cb1 = sv1 / sz1, cbMax = svMax / szMax;
document.getElementById('uxmss-table').innerHTML = [
row('Signature Budget', `${(hsf + 1).toLocaleString()} signatures`),
row('Signature Size', `${sz1.toLocaleString()} – ${szMax.toLocaleString()} B`),
row('Key Generation', fmtCompressions(kg) + ' C'),
row('Signature Generation (with cache / cold)',
`${sg1.toLocaleString()} – ${sgLast.toLocaleString()} / ${fmtCompressions(sgCold)} C`),
row('Cache Size', fmtBytes((hsf + 1) * N)),
row('Signature Verification', `${sv1.toLocaleString()} – ${svMax.toLocaleString()} C`),
row('Compression-per-Byte Verification Complexity', `${fmtCpb(cb1)} - ${fmtCpb(cbMax)}`),
row('C/B Comparing to BIP-340 Verification', `${fmtCpb(cb1 / SCHNORR.cb64)} - ${fmtCpb(cbMax / SCHNORR.cb64)}×`),
row('Max signatures', fmtNum(hsf + 1)),
row('Size (B)', `${fmtNum(sz1)} → ${fmtNum(szMax)}`),
row('KeyGen (C)', fmtNum(kg)),
row('SigGen, cached (C)', `${fmtNum(sg1)} → ${fmtNum(sgLast)}`),
row('SigGen, cold (C)', fmtNum(sgCold)),
row('State (B)', fmtNum((hsf + 1) * N)),
row('SigVer (C)', `${fmtNum(sv1)} → ${fmtNum(svMax)}`),
row('ρ (C/B)', `${fmtRho(cb1)} → ${fmtRho(cbMax)}`),
row('ρ ÷ Schnorr', `${fmtRatio(cb1 / SCHNORR.cb64)} → ${fmtRatio(cbMax / SCHNORR.cb64)}`),
].join('');
}

Expand Down
102 changes: 102 additions & 0 deletions site/js/ui.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Number formats, popovers and the glossary.
'use strict';

// Number formats

// Compressions and bytes: exact up to six digits, compact above, with the exact value on hover. Returns HTML.
function fmtNum(n) {
if (!isFinite(n)) return '—';
n = Math.round(n);
const exact = n.toLocaleString('en-US');
if (n < 1e6) return exact;
for (const [unit, size] of [['M', 1e6], ['G', 1e9], ['T', 1e12], ['P', 1e15]]) {
const v = (n / size).toPrecision(3);
if (Number(v) < 1000) return `<span title="${exact}">${v}${unit}</span>`;
}
return `<span title="${exact}">${n.toExponential(2)}</span>`;
}

// Ratios to a reference value, to three significant digits.
function fmtRatio(r) {
if (r >= 100) return Math.round(r).toLocaleString('en-US') + '×';
return r.toPrecision(3) + '×';
}

// ρ, in compressions per signature byte.
function fmtRho(r) {
return r.toFixed(4);
}

function fmtPow2(e) {
return `2<sup>${e}</sup>`;
}

// Popovers: <button class="info" popovertarget="ID" aria-label="About …">i</button> opens
// <div class="pop" id="ID" popover>…</div>. The browser opens and closes the popover;
// this code only places it next to its button.

function infoButton(id, label) {
return `<button type="button" class="info" popovertarget="${id}" aria-label="${label}">i</button>`;
}

(function () {
if (!('popover' in HTMLElement.prototype)) return; // older browsers show the content inline
let opener = null;
document.addEventListener('click', e => {
const b = e.target.closest && e.target.closest('[popovertarget]');
if (b) opener = b;
}, true);

function place(pop) {
const btn = opener && opener.isConnected && opener.getAttribute('popovertarget') === pop.id
? opener : document.querySelector(`[popovertarget="${pop.id}"]`);
if (!btn) return;
const r = btn.getBoundingClientRect(), gap = 6, edge = 8;
const left = Math.max(edge, Math.min(r.left, innerWidth - pop.offsetWidth - edge));
let top = r.bottom + gap;
if (top + pop.offsetHeight > innerHeight - edge && r.top - gap - pop.offsetHeight >= edge) {
top = r.top - gap - pop.offsetHeight;
}
pop.style.left = `${left}px`;
pop.style.top = `${Math.max(edge, top)}px`;
}

// toggle does not bubble, so listen in the capture phase.
document.addEventListener('toggle', e => {
if (e.newState === 'open' && e.target.classList.contains('pop')) place(e.target);
}, true);
const placeOpen = () => document.querySelectorAll('.pop:popover-open').forEach(place);
addEventListener('resize', placeOpen);
addEventListener('scroll', placeOpen, true);
})();

// Glossary

document.body.insertAdjacentHTML('beforeend', `
<div class="pop pop-wide" id="glossary" popover>
<h3>Glossary</h3>
<dl>
<dt>C</dt>
<dd>One SHA-256 compression. Costs count compressions; the PK.seed block is compressed once per key and reused.</dd>
<dt>B</dt>
<dd>Byte.</dd>
<dt>KeyGen, SigGen, SigVer</dt>
<dd>Compressions to generate a key pair, to sign a message and to verify a signature. SigVer is the average unless marked worst.</dd>
<dt>ρ</dt>
<dd>SigVer per signature byte, in C/B.</dd>
<dt>ρ ÷ Schnorr</dt>
<dd>ρ divided by the ρ of a BIP340 Schnorr verification (libsecp256k1, 64-byte signature).</dd>
<dt>SLH-DSA-128s</dt>
<dd>The FIPS 205 parameter set (h, d, k, a, w) = (63, 7, 14, 12, 16). Ratios (×) compare against it.</dd>
<dt>h, d, k, a, w</dt>
<dd>Hypertree height, number of layers, FORS trees, FORS tree height, Winternitz parameter. h′ = h/d is the height of one layer.</dd>
<dt>q<sub>s</sub></dt>
<dd>The number of signatures per key that security is computed for: 2<sup>40</sup> for the stateless schemes.</dd>
<dt>WOTS, WOTS+C</dt>
<dd>Winternitz one-time signatures. WOTS (as in SLH-DSA) has checksum chains; WOTS+C replaces them with a counter that the signer grinds until the message digits have a fixed sum, so verification always costs the same.</dd>
<dt>XMSS, XMSS-MT, UXMSS</dt>
<dd>Stateful schemes: one balanced Merkle tree (BXMSS in SHRINCS), d layers of such trees, and the unbalanced SHRINCS tree, whose signatures grow by one node per signature.</dd>
<dt>Cached, cold</dt>
<dd>A cached signer keeps tree state between signatures (a BDS state for XMSS, a leaf cache for UXMSS); a cold signer keeps none and rebuilds its trees for every signature.</dd>
</dl>
</div>`);
Loading
Loading