Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>AEAD Usage Limits — interactive explorer</title> | |
| <style> | |
| :root { | |
| --bg: #0f141a; | |
| --panel: #182029; | |
| --panel2: #1e2833; | |
| --border: #2c3947; | |
| --text: #e6edf3; | |
| --muted: #93a4b5; | |
| --accent: #4da3ff; | |
| --accent2: #37d0a0; | |
| --warn: #ffb454; | |
| --bad: #ff6b6b; | |
| --good: #37d0a0; | |
| --mono: "SF Mono", ui-monospace, Menlo, Consolas, monospace; | |
| } | |
| * { box-sizing: border-box; } | |
| body { | |
| margin: 0; | |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | |
| background: var(--bg); | |
| color: var(--text); | |
| line-height: 1.5; | |
| } | |
| header { | |
| padding: 20px 24px 12px; | |
| border-bottom: 1px solid var(--border); | |
| } | |
| header h1 { margin: 0 0 4px; font-size: 20px; } | |
| header p { margin: 0; color: var(--muted); font-size: 13px; max-width: 1000px; } | |
| header a { color: var(--accent); } | |
| main { display: grid; grid-template-columns: 320px 1fr; gap: 18px; padding: 18px 24px 40px; align-items: start; } | |
| @media (max-width: 900px) { main { grid-template-columns: 1fr; } } | |
| .panel { background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 16px; } | |
| .panel h2 { margin: 0 0 12px; font-size: 13px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); } | |
| fieldset { border: 1px solid var(--border); border-radius: 8px; margin: 0 0 14px; padding: 10px 12px 12px; } | |
| legend { font-size: 12px; color: var(--muted); padding: 0 4px; } | |
| label.field { display: block; margin-bottom: 12px; font-size: 13px; } | |
| label.field .val { float: right; font-family: var(--mono); color: var(--accent2); font-size: 12px; } | |
| select, input[type=number] { | |
| width: 100%; margin-top: 4px; background: var(--panel2); color: var(--text); | |
| border: 1px solid var(--border); border-radius: 6px; padding: 6px 8px; font-size: 13px; | |
| } | |
| input[type=range] { width: 100%; margin-top: 6px; accent-color: var(--accent); } | |
| .seg { display: flex; gap: 6px; margin-bottom: 12px; } | |
| .seg button { | |
| flex: 1; background: var(--panel2); color: var(--muted); border: 1px solid var(--border); | |
| border-radius: 6px; padding: 7px 4px; font-size: 12.5px; cursor: pointer; | |
| } | |
| .seg button.active { background: var(--accent); color: #06233f; border-color: var(--accent); font-weight: 600; } | |
| .hint { font-size: 11.5px; color: var(--muted); margin-top: -6px; margin-bottom: 12px; } | |
| .presets { display: flex; flex-wrap: wrap; gap: 6px; } | |
| .presets button { | |
| background: var(--panel2); color: var(--text); border: 1px solid var(--border); | |
| border-radius: 20px; padding: 5px 11px; font-size: 12px; cursor: pointer; | |
| } | |
| .presets button:hover { border-color: var(--accent); } | |
| .cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; margin-bottom: 18px; } | |
| .card { background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 14px 16px; } | |
| .card .k { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: .05em; } | |
| .card .big { font-family: var(--mono); font-size: 26px; margin: 6px 0 2px; } | |
| .card .sub { font-family: var(--mono); font-size: 12px; color: var(--muted); } | |
| .card.cl .big { color: var(--accent); } | |
| .card.il .big { color: var(--accent2); } | |
| .card.off .big { color: var(--warn); } | |
| .recommendation { | |
| background: linear-gradient(135deg, rgba(55,208,160,.12), rgba(77,163,255,.08)); | |
| border: 1px solid rgba(55,208,160,.55); | |
| border-radius: 10px; | |
| padding: 15px 17px; | |
| margin-bottom: 18px; | |
| } | |
| .recommendation .k { | |
| color: var(--accent2); | |
| font-size: 12px; | |
| font-weight: 700; | |
| letter-spacing: .06em; | |
| text-transform: uppercase; | |
| } | |
| .recommendation .main { margin: 5px 0 3px; font-size: 16px; line-height: 1.45; } | |
| .notice { border-radius: 8px; padding: 10px 12px; font-size: 12.5px; margin-bottom: 12px; } | |
| .notice.warn { background: rgba(255,180,84,.1); border: 1px solid rgba(255,180,84,.4); color: #ffd9a0; } | |
| .notice.info { background: rgba(77,163,255,.08); border: 1px solid rgba(77,163,255,.35); color: #bcdcff; } | |
| .notice ul { margin: 4px 0 0; padding-left: 18px; } | |
| table.cmp { width: 100%; border-collapse: collapse; font-size: 13px; } | |
| table.cmp th, table.cmp td { padding: 8px 10px; text-align: right; border-bottom: 1px solid var(--border); } | |
| table.cmp th:first-child, table.cmp td:first-child { text-align: left; } | |
| table.cmp th { color: var(--muted); font-weight: 600; font-size: 12px; } | |
| table.cmp td.num { font-family: var(--mono); } | |
| table.cmp tr.sel { background: rgba(77,163,255,.08); } | |
| table.cmp tr:hover { background: rgba(255,255,255,.03); cursor: pointer; } | |
| .chartwrap { background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 14px 16px; margin-bottom: 18px; } | |
| .chartwrap h2 { margin: 0 0 4px; font-size: 14px; } | |
| .chartwrap .desc { font-size: 12px; color: var(--muted); margin-bottom: 10px; } | |
| svg { display: block; width: 100%; height: auto; } | |
| .legend { display: flex; gap: 16px; flex-wrap: wrap; font-size: 12px; margin-top: 8px; } | |
| .legend span { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); } | |
| .legend i { width: 14px; height: 3px; border-radius: 2px; display: inline-block; } | |
| details { background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 12px 16px; } | |
| details summary { cursor: pointer; font-size: 13px; color: var(--accent); font-weight: 600; } | |
| details .body { margin-top: 10px; font-size: 13px; color: var(--text); } | |
| details code, .formula { font-family: var(--mono); font-size: 12px; background: var(--panel2); padding: 2px 5px; border-radius: 4px; } | |
| details a { color: var(--accent); } | |
| .fblock { font-family: var(--mono); font-size: 12.5px; background: var(--panel2); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; margin: 8px 0; white-space: pre; overflow-x: auto; } | |
| .muted { color: var(--muted); } | |
| footer { padding: 0 24px 30px; color: var(--muted); font-size: 12px; } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <h1>AEAD Usage Limits — interactive explorer</h1> | |
| <p> | |
| Explore the confidentiality, integrity and authenticated-encryption usage | |
| limits from | |
| <a href="https://datatracker.ietf.org/doc/draft-irtf-cfrg-aead-limits/">draft-irtf-cfrg-aead-limits</a>. | |
| Pick an algorithm and parameters; the limits, per-algorithm comparison and | |
| the advantage curve update live. All math is done in the log<sub>2</sub> | |
| domain so limits like 2<sup>64.5</sup> stay exact. | |
| </p> | |
| </header> | |
| <main> | |
| <!-- ============ controls ============ --> | |
| <section class="panel" aria-label="controls"> | |
| <h2>Parameters</h2> | |
| <label class="field">Algorithm | |
| <select id="alg"></select> | |
| </label> | |
| <div id="tagControls" style="display:none"> | |
| <div class="field">Authentication tag</div> | |
| <div class="seg" id="tagSeg"> | |
| <button data-v="128" class="active">128-bit</button> | |
| <button data-v="256">256-bit</button> | |
| </div> | |
| <div class="hint" id="tagHint"></div> | |
| </div> | |
| <div class="field">Setting</div> | |
| <div class="seg" id="settingSeg"> | |
| <button data-v="su" class="active">Single-key</button> | |
| <button data-v="mu">Multi-key</button> | |
| </div> | |
| <div class="hint" id="settingHint"></div> | |
| <div class="field" id="nonceWrap">Nonce construction</div> | |
| <div class="seg" id="nonceSeg"> | |
| <button data-v="randomized" id="nonceRandomized" class="active">Non-repeating</button> | |
| <button data-v="random">Random nonces</button> | |
| <button data-v="implicit" id="nonceImplicit">Partially implicit</button> | |
| </div> | |
| <div class="hint" id="nonceHint"></div> | |
| <fieldset> | |
| <legend>Security target & usage</legend> | |
| <label class="field">Target advantage p <span class="val" id="pVal"></span> | |
| <input type="range" id="p" min="-128" max="-10" step="1" value="-50"> | |
| </label> | |
| <div class="hint">Upper bound on attacker success probability (CA, IA or AEA).</div> | |
| <label class="field">Max message size <span class="val" id="lVal"></span> | |
| <input type="range" id="lsize" min="4" max="20" step="1" value="11"> | |
| </label> | |
| <div class="hint">Largest message (plaintext + AAD), in bytes. Converted to L for bounds that depend on message length; the cited AEGIS claims do not.</div> | |
| <label class="field">Offline work o <span class="val" id="oVal"></span> | |
| <input type="range" id="owork" min="0" max="128" step="1" value="70"> | |
| </label> | |
| <div class="hint">Attacker's precomputation, in cipher operations.</div> | |
| </fieldset> | |
| <fieldset id="muParams" style="display:none"> | |
| <legend>Multi-key</legend> | |
| <label class="field"><span id="bcLabel">B — max blocks / key</span> <span class="val" id="bVal"></span> | |
| <input type="range" id="bslide" min="0" max="64" step="1" value="27"> | |
| </label> | |
| <div class="hint" id="bcHint"></div> | |
| <div id="mWrap" style="display:none"> | |
| <label class="field">M — max messages / key <span class="val" id="mVal"></span> | |
| <input type="range" id="mslide" min="0" max="64" step="1" value="20"> | |
| </label> | |
| <div class="hint">Needed to aggregate same-key random-nonce collision risk; independent of B/C.</div> | |
| </div> | |
| </fieldset> | |
| <fieldset> | |
| <legend>Presets</legend> | |
| <div class="presets" id="presets"></div> | |
| </fieldset> | |
| </section> | |
| <!-- ============ results ============ --> | |
| <section> | |
| <div class="recommendation" aria-live="polite"> | |
| <div class="k">Recommendation</div> | |
| <p class="main" id="recommendationText"></p> | |
| </div> | |
| <div id="warnings"></div> | |
| <div class="cards"> | |
| <div class="card cl"> | |
| <div class="k">Confidentiality limit — max q</div> | |
| <div class="big" id="qBig">—</div> | |
| <div class="sub" id="qSub">protected messages</div> | |
| </div> | |
| <div class="card il"> | |
| <div class="k">Integrity limit — max v</div> | |
| <div class="big" id="vBig">—</div> | |
| <div class="sub" id="vSub">forgery attempts</div> | |
| </div> | |
| <div class="card off"> | |
| <div class="k">Offline-work floor (o/2^k)</div> | |
| <div class="big" id="offBig">—</div> | |
| <div class="sub">advantage regardless of usage</div> | |
| </div> | |
| </div> | |
| <div class="chartwrap"> | |
| <h2>Advantage vs. usage</h2> | |
| <div class="desc" id="chartDesc"></div> | |
| <svg id="chart" viewBox="0 0 720 340" role="img" aria-label="advantage curve"></svg> | |
| <div class="legend" id="chartLegend"></div> | |
| </div> | |
| <div class="panel" style="margin-bottom:18px"> | |
| <h2>All algorithm groups at these parameters</h2> | |
| <table class="cmp" id="cmpTable"> | |
| <thead> | |
| <tr><th>AEAD / equivalent-limit group</th><th>max q</th><th>max v</th><th>binding</th></tr> | |
| </thead> | |
| <tbody></tbody> | |
| </table> | |
| </div> | |
| <details> | |
| <summary>Formulas & assumptions for <span id="fAlg"></span> (<span id="fSetting"></span>)</summary> | |
| <div class="body" id="formulaBody"></div> | |
| </details> | |
| </section> | |
| </main> | |
| <footer> | |
| Limits are illustrative results from the cited analyses and security claims, | |
| computed under their simplifying assumptions (including the selected nonce | |
| construction and a single message size covering AAD + plaintext). Random-nonce | |
| results treat any same-key nonce repeat as a security failure. A claim-based | |
| result is not a provable advantage bound. Always validate assumptions for your | |
| deployment. Not security advice. | |
| </footer> | |
| <script src="js/aead.js"></script> | |
| <script> | |
| (function () { | |
| 'use strict'; | |
| var A = window.AEADLimits; | |
| var $ = function (id) { return document.getElementById(id); }; | |
| var DEFAULT_STATE = { | |
| alg: 'AEAD_AES_128_GCM', | |
| setting: 'su', | |
| nonceMode: 'randomized', | |
| tagBits: 128, | |
| lP: -50, | |
| lLBytes: 11, | |
| lO: 70, | |
| lB: 27, | |
| lM: 20 | |
| }; | |
| var state = Object.assign({}, DEFAULT_STATE); | |
| var ALG_NAMES = Object.keys(A.ALGORITHMS); | |
| var AEGIS_NAMES = ['AEAD_AEGIS128L', 'AEAD_AEGIS256']; | |
| /* ---------- build controls ---------- */ | |
| var algSel = $('alg'); | |
| function addAlgorithmGroup(label, names) { | |
| var group = document.createElement('optgroup'); | |
| group.label = label; | |
| names.forEach(function (n) { | |
| var o = document.createElement('option'); | |
| o.value = n; o.textContent = n; | |
| group.appendChild(o); | |
| }); | |
| algSel.appendChild(group); | |
| } | |
| addAlgorithmGroup('AEAD limits draft', ALG_NAMES.filter(function (n) { | |
| return A.ALGORITHMS[n].family !== 'AEGIS'; | |
| })); | |
| addAlgorithmGroup('AEGIS', AEGIS_NAMES); | |
| algSel.value = state.alg; | |
| var COMPARISON_ROWS = ALG_NAMES.map(function (name) { | |
| var label = name; | |
| if (name === 'AEAD_AEGIS128L') label = 'AEGIS-128L/X2/X4'; | |
| if (name === 'AEAD_AEGIS256') label = 'AEGIS-256/X2/X4'; | |
| return { name: name, label: label }; | |
| }); | |
| function preset(label, overrides) { | |
| return { label: label, s: Object.assign({}, DEFAULT_STATE, overrides) }; | |
| } | |
| var PRESETS = [ | |
| preset('Draft example (SU, AES-128-GCM)', {}), | |
| preset('Draft example (MU, AES-128-GCM)', { setting: 'mu' }), | |
| preset('Cautious (AES-256-GCM, 2^-80)', { | |
| alg: 'AEAD_AES_256_GCM', setting: 'mu', lP: -80, lO: 80, lB: 32 | |
| }), | |
| preset('TLS 1.2-style nonces (AES-128-GCM)', { | |
| setting: 'mu', nonceMode: 'implicit', lO: 32 | |
| }), | |
| preset('Random nonces (AEGIS-128L)', { | |
| alg: 'AEAD_AEGIS128L', nonceMode: 'random' | |
| }) | |
| ]; | |
| var presetsEl = $('presets'); | |
| PRESETS.forEach(function (p) { | |
| var b = document.createElement('button'); | |
| b.textContent = p.label; | |
| b.addEventListener('click', function () { | |
| state = Object.assign({}, p.s); | |
| syncControls(); render(); | |
| }); | |
| presetsEl.appendChild(b); | |
| }); | |
| /* ---------- helpers ---------- */ | |
| function isGCM() { return A.ALGORITHMS[state.alg].family === 'GCM'; } | |
| function isCCM() { return A.ALGORITHMS[state.alg].family === 'CCM'; } | |
| function isAEGIS() { return A.ALGORITHMS[state.alg].family === 'AEGIS'; } | |
| function params() { | |
| return { | |
| alg: state.alg, setting: state.setting, nonceMode: state.nonceMode, | |
| tagBits: state.tagBits, lP: state.lP, lLBytes: state.lLBytes, | |
| lO: state.lO, lB: state.lB, lM: state.lM | |
| }; | |
| } | |
| /* ---------- control syncing ---------- */ | |
| function syncControls() { | |
| algSel.value = state.alg; | |
| $('p').value = state.lP; | |
| $('lsize').value = state.lLBytes; | |
| $('owork').value = state.lO; | |
| $('bslide').value = state.lB; | |
| $('mslide').value = state.lM; | |
| var mu = state.setting === 'mu'; | |
| if (state.nonceMode === 'implicit' && !(mu && isGCM())) { | |
| state.nonceMode = 'randomized'; | |
| } | |
| segSet('settingSeg', state.setting); | |
| segSet('nonceSeg', state.nonceMode); | |
| segSet('tagSeg', state.tagBits); | |
| $('muParams').style.display = mu ? '' : 'none'; | |
| $('mWrap').style.display = mu && state.nonceMode === 'random' ? '' : 'none'; | |
| $('nonceImplicit').style.display = mu && isGCM() ? '' : 'none'; | |
| $('nonceRandomized').textContent = mu && !isAEGIS() | |
| ? 'Nonce randomization' | |
| : 'Non-repeating'; | |
| $('tagControls').style.display = isAEGIS() ? '' : 'none'; | |
| $('tagHint').textContent = state.tagBits === 256 | |
| ? 'AEGIS-128L: 216-bit differential-forgery model. AEGIS-256: 256-bit tag-guessing and differential model.' | |
| : 'AEGIS v1.1 claims about v/2^128 success after v online forgery attempts.'; | |
| $('settingHint').textContent = mu | |
| ? 'Limits are totals across all keys (e.g. rekeying, many connections).' | |
| : 'One key shared between two parties.'; | |
| if (state.nonceMode === 'random') { | |
| $('nonceHint').textContent = 'Independent uniform full-width samples; same-key collision risk is included in the limits.'; | |
| } else if (state.nonceMode === 'implicit') { | |
| $('nonceHint').textContent = 'TLS 1.2 style: random implicit part + non-repeating explicit part.'; | |
| } else if (mu && !isAEGIS()) { | |
| $('nonceHint').textContent = 'TLS 1.3 / QUIC style: a non-repeating nonce is XORed with a secret random offset.'; | |
| } else if (mu) { | |
| $('nonceHint').textContent = 'Non-repeating nonce; the AEGIS multi-key model assumes a unique per-key identifier in unused nonce bits.'; | |
| } else { | |
| $('nonceHint').textContent = 'A counter, permutation, or other construction that guarantees no repeats under one key.'; | |
| } | |
| var isC = isCCM(), isA = isAEGIS(); | |
| var isChacha = A.ALGORITHMS[state.alg].family === 'ChaCha20-Poly1305'; | |
| $('bcLabel').textContent = (isA || isChacha) ? 'B/C — per-key blocks (other AEADs)' | |
| : (isC ? 'C — max blocks enc+dec / key' : 'B — max blocks enc / key'); | |
| $('bcHint').textContent = (isA || isChacha) | |
| ? 'Not used by the selected algorithm; retained for the other rows in the comparison table.' | |
| : (isC | |
| ? 'Largest number of 128-bit blocks encrypted or decrypted by any single key.' | |
| : 'Largest number of 128-bit blocks encrypted by any single key.'); | |
| } | |
| function segSet(id, v) { | |
| var el = $(id); | |
| Array.prototype.forEach.call(el.children, function (b) { | |
| b.classList.toggle('active', b.getAttribute('data-v') === String(v)); | |
| }); | |
| } | |
| function segWire(id, key) { | |
| $(id).addEventListener('click', function (e) { | |
| var b = e.target.closest('button'); | |
| if (!b) return; | |
| state[key] = b.getAttribute('data-v'); | |
| syncControls(); render(); | |
| }); | |
| } | |
| segWire('settingSeg', 'setting'); | |
| segWire('nonceSeg', 'nonceMode'); | |
| $('tagSeg').addEventListener('click', function (e) { | |
| var b = e.target.closest('button'); | |
| if (!b) return; | |
| state.tagBits = parseInt(b.getAttribute('data-v'), 10); | |
| syncControls(); render(); | |
| }); | |
| function selectAlgorithm(name) { | |
| state.alg = name; | |
| if (A.ALGORITHMS[name].family !== 'AEGIS') state.tagBits = 128; | |
| syncControls(); render(); | |
| } | |
| algSel.addEventListener('change', function () { selectAlgorithm(algSel.value); }); | |
| function slider(id, key, fmt) { | |
| $(id).addEventListener('input', function () { | |
| state[key] = parseInt(this.value, 10); | |
| render(); | |
| }); | |
| } | |
| slider('p', 'lP'); | |
| slider('lsize', 'lLBytes'); | |
| slider('owork', 'lO'); | |
| slider('bslide', 'lB'); | |
| slider('mslide', 'lM'); | |
| /* ---------- formatting ---------- */ | |
| function bytesLabel(lb) { | |
| var b = Math.pow(2, lb); | |
| if (b < 1024) return b + ' B'; | |
| if (b < 1024 * 1024) return (b / 1024) + ' KiB'; | |
| return (b / (1024 * 1024)) + ' MiB'; | |
| } | |
| function nonceDescription() { | |
| var alg = A.ALGORITHMS[state.alg]; | |
| if (state.nonceMode === 'random') { | |
| return 'independently and uniformly generated ' + alg.r + | |
| '-bit nonces from a CSPRNG'; | |
| } | |
| if (state.nonceMode === 'implicit') { | |
| return 'a non-repeating explicit nonce part for each message and a ' + | |
| 'fresh random implicit part for each key'; | |
| } | |
| if (state.setting === 'mu' && !isAEGIS()) { | |
| return 'non-repeating nonces and an independent random nonce offset ' + | |
| 'for each key'; | |
| } | |
| if (state.setting === 'mu') { | |
| return 'unique nonces and a unique identifier for each key encoded in ' + | |
| 'unused nonce bits'; | |
| } | |
| return 'a unique nonce for each message'; | |
| } | |
| function joinConditions(items) { | |
| if (items.length === 1) return items[0]; | |
| if (items.length === 2) return items[0] + ' and ' + items[1]; | |
| return items.slice(0, -1).join(', ') + ', and ' + items[items.length - 1]; | |
| } | |
| // Conservative authenticated-encryption advantage for using q protected | |
| // messages and processing v forgery attempts together. Some multi-key | |
| // formulas do not include generic offline key search, so add that floor | |
| // unless the formula already contains an equal or larger no-usage term. | |
| function operationalAdvantage(r, lq, lv) { | |
| var bound = r.aea ? r.aea(lq, lv) : -Infinity; | |
| var offline = state.lO - A.ALGORITHMS[state.alg].k; | |
| var formulaFloor = r.aea ? r.aea(-Infinity, -Infinity) : -Infinity; | |
| if (formulaFloor < offline - 1e-9) { | |
| bound = A.log2sum(bound, offline); | |
| } | |
| return bound; | |
| } | |
| // The cards show the individual q and v boundaries. For application | |
| // instructions, lower both by the same factor when necessary so using both | |
| // recommended allowances together remains within the selected AEA target. | |
| function operationalLimits(r) { | |
| var qFinite = isFinite(r.qLimit); | |
| var vFinite = isFinite(r.vLimit); | |
| if (r.qLimit === -Infinity || r.vLimit === -Infinity || | |
| (qFinite && r.qLimit < 0) || (vFinite && r.vLimit < 0) || | |
| isNaN(r.qLimit) || isNaN(r.vLimit)) { | |
| return null; | |
| } | |
| if (operationalAdvantage(r, -Infinity, -Infinity) >= state.lP) { | |
| return null; | |
| } | |
| function advantageAt(reduction) { | |
| return operationalAdvantage( | |
| r, | |
| qFinite ? r.qLimit - reduction : -Infinity, | |
| vFinite ? r.vLimit - reduction : -Infinity | |
| ); | |
| } | |
| var reduction = 0; | |
| if (advantageAt(0) > state.lP) { | |
| var lo = 0, hi = 1; | |
| while (advantageAt(hi) > state.lP && hi < 1024) hi *= 2; | |
| for (var i = 0; i < 100; i++) { | |
| var mid = (lo + hi) / 2; | |
| if (advantageAt(mid) > state.lP) lo = mid; | |
| else hi = mid; | |
| } | |
| reduction = hi; | |
| } | |
| return { | |
| q: qFinite ? r.qLimit - reduction : Infinity, | |
| v: vFinite ? r.vLimit - reduction : Infinity | |
| }; | |
| } | |
| // Return a count that never rounds above the calculated boundary. | |
| function actionableCount(logCount) { | |
| if (logCount <= 52) { | |
| var count = Math.floor(Math.pow(2, logCount)); | |
| return count.toLocaleString('en-US') + ' (approximately ' + | |
| A.fmtPow(logCount, 1) + ')'; | |
| } | |
| var scaled = logCount * 10; | |
| var nearest = Math.round(scaled); | |
| var safeExponent = Math.abs(scaled - nearest) < 1e-9 | |
| ? nearest / 10 | |
| : Math.floor(scaled) / 10; | |
| return A.fmtPow(safeExponent, 1); | |
| } | |
| function recommendationText(r) { | |
| var alg = state.alg; | |
| var target = '2^' + state.lP; | |
| var size = bytesLabel(state.lLBytes); | |
| var fam = A.ALGORITHMS[state.alg].family; | |
| var conditions = [alg]; | |
| if (isAEGIS()) conditions.push('a ' + state.tagBits + '-bit authentication tag'); | |
| conditions.push(nonceDescription()); | |
| if (state.setting === 'mu' && (fam === 'GCM' || fam === 'CCM')) { | |
| conditions.push('no key processing more than ' + A.fmtPow(state.lB, 0) + | |
| ' encrypted ' + (fam === 'CCM' ? 'or decrypted ' : '') + | |
| '128-bit blocks'); | |
| } | |
| if (state.setting === 'mu' && state.nonceMode === 'random') { | |
| conditions.push('no key protecting more than ' + | |
| A.fmtPow(state.lM, 0) + ' messages'); | |
| } | |
| conditions.push('plaintext plus AAD up to ' + size + ' per message'); | |
| var context = 'With ' + joinConditions(conditions); | |
| if (state.nonceMode === 'random' && fam === 'CCM') { | |
| return context + ', the selected nonce mode is not permitted. Select ' + | |
| 'Non-repeating before using this configuration.'; | |
| } | |
| var limits = operationalLimits(r); | |
| if (!limits) { | |
| return context + ' cannot meet a ' + target + ' target. Choose a ' + | |
| 'larger-key or longer-tag cipher, or select a less stringent target.'; | |
| } | |
| var text = context; | |
| if (state.setting === 'su') { | |
| var actions = []; | |
| if (limits.q !== Infinity) { | |
| actions.push('protect at most ' + actionableCount(limits.q) + ' messages'); | |
| } | |
| if (limits.v !== Infinity) { | |
| actions.push('allow at most ' + actionableCount(limits.v) + | |
| ' failed authentication attempts'); | |
| } | |
| if (actions.length) { | |
| text += ', to keep authenticated-encryption advantage at or below ' + | |
| target + ', ' + actions.join(' and ') + ' under each key. ' + | |
| (actions.length > 1 | |
| ? 'Replace the key when either limit is reached.' | |
| : 'Replace the key when that limit is reached.'); | |
| } | |
| if (limits.q === Infinity) { | |
| text += ' For successfully protected messages, follow your protocol\u2019s ' + | |
| 'normal key-usage limit.'; | |
| } | |
| } else { | |
| var totals = []; | |
| if (limits.q !== Infinity) { | |
| totals.push('protect at most ' + actionableCount(limits.q) + | |
| ' messages in total'); | |
| } | |
| if (limits.v !== Infinity) { | |
| totals.push('allow at most ' + actionableCount(limits.v) + | |
| ' failed authentication attempts in total'); | |
| } | |
| if (totals.length) { | |
| text += ', to keep authenticated-encryption advantage at or below ' + | |
| target + ' across all keys, ' + totals.join(' and ') + '.'; | |
| } | |
| if (limits.q === Infinity) { | |
| text += ' For successfully protected messages, follow each protocol\u2019s ' + | |
| 'normal per-key usage limit.'; | |
| } | |
| } | |
| return text; | |
| } | |
| /* ---------- chart ---------- */ | |
| var NS = 'http://www.w3.org/2000/svg'; | |
| function el(tag, attrs) { | |
| var e = document.createElementNS(NS, tag); | |
| for (var k in attrs) e.setAttribute(k, attrs[k]); | |
| return e; | |
| } | |
| function drawChart(r) { | |
| var svg = $('chart'); | |
| while (svg.firstChild) svg.removeChild(svg.firstChild); | |
| var W = 720, H = 340, m = { l: 56, r: 16, t: 18, b: 40 }; | |
| var iw = W - m.l - m.r, ih = H - m.t - m.b; | |
| // x axis: log2 usage (messages). Choose a sensible window. | |
| var qL = r.qLimit, vL = r.vLimit; | |
| var finite = [qL, vL].filter(function (x) { return isFinite(x); }); | |
| var center = finite.length ? Math.max.apply(null, finite) : 40; | |
| var x0 = Math.max(0, center - 30), x1 = center + 14; | |
| // y axis: log2 advantage. Show from a little above 0 down to well below p. | |
| var yTop = 0, yBot = Math.min(state.lP * 2, state.lP - 40, -80); | |
| var yMin = Math.max(yBot, -300); | |
| var YCLIP = 4; // draw values above 0 as just past the top edge (certainty) | |
| function X(lq) { return m.l + (lq - x0) / (x1 - x0) * iw; } | |
| function Y(la) { | |
| var v = Math.max(yMin, Math.min(yTop, la)); | |
| return m.t + (yTop - v) / (yTop - yMin) * ih; | |
| } | |
| // For path drawing: values above 0 (advantage ~ certainty) are pinned just | |
| // past the top edge so the line visibly leaves the plot instead of riding | |
| // the axis. | |
| function Yp(la) { | |
| if (la > yTop) return m.t - YCLIP; | |
| return Y(la); | |
| } | |
| // clip region so curves stop at the plot edge | |
| var defs = el('defs', {}); | |
| var clip = el('clipPath', { id: 'plotclip' }); | |
| clip.appendChild(el('rect', { x: m.l, y: m.t, width: iw, height: ih })); | |
| defs.appendChild(clip); | |
| svg.appendChild(defs); | |
| // gridlines + axis labels | |
| var g = el('g', { 'font-size': '10', fill: '#93a4b5' }); | |
| svg.appendChild(g); | |
| for (var gx = Math.ceil(x0 / 5) * 5; gx <= x1; gx += 5) { | |
| svg.appendChild(el('line', { x1: X(gx), y1: m.t, x2: X(gx), y2: m.t + ih, stroke: '#22303d' })); | |
| var t = el('text', { x: X(gx), y: m.t + ih + 16, 'text-anchor': 'middle' }); | |
| t.textContent = '2^' + gx; g.appendChild(t); | |
| } | |
| for (var gy = Math.ceil(yMin / 20) * 20; gy <= yTop; gy += 20) { | |
| svg.appendChild(el('line', { x1: m.l, y1: Y(gy), x2: m.l + iw, y2: Y(gy), stroke: '#22303d' })); | |
| var ty = el('text', { x: m.l - 6, y: Y(gy) + 3, 'text-anchor': 'end' }); | |
| ty.textContent = '2^' + gy; g.appendChild(ty); | |
| } | |
| // axis titles | |
| var tx = el('text', { x: m.l + iw / 2, y: H - 6, 'text-anchor': 'middle', 'font-size': '11', fill: '#93a4b5' }); | |
| tx.textContent = 'usage (log2 messages)'; svg.appendChild(tx); | |
| // target p line | |
| svg.appendChild(el('line', { x1: m.l, y1: Y(state.lP), x2: m.l + iw, y2: Y(state.lP), stroke: '#ffb454', 'stroke-dasharray': '5 4', 'stroke-width': 1.5 })); | |
| var tp = el('text', { x: m.l + iw - 4, y: Y(state.lP) - 5, 'text-anchor': 'end', 'font-size': '10', fill: '#ffb454' }); | |
| tp.textContent = 'target p = 2^' + state.lP; svg.appendChild(tp); | |
| // curves | |
| var curves = []; | |
| function sample(fn, color, name) { | |
| var d = '', started = false, N = 160; | |
| for (var i = 0; i <= N; i++) { | |
| var lq = x0 + (x1 - x0) * i / N; | |
| var la = fn(lq); | |
| if (!isFinite(la)) { started = false; continue; } | |
| var px = X(lq), py = Yp(la); | |
| d += (started ? 'L' : 'M') + px.toFixed(1) + ' ' + py.toFixed(1) + ' '; | |
| started = true; | |
| } | |
| var path = el('path', { d: d, fill: 'none', stroke: color, 'stroke-width': 2, 'clip-path': 'url(#plotclip)' }); | |
| svg.appendChild(path); | |
| curves.push({ color: color, name: name }); | |
| } | |
| if (r.ca && r.ca(0) !== -Infinity) sample(function (lq) { return r.ca(lq); }, '#4da3ff', 'CA (confidentiality)'); | |
| if (r.ia) { | |
| if (state.nonceMode === 'random' || | |
| (state.setting === 'su' && isGCM())) { | |
| sample(function (lv) { return r.ia(lv, r.qLimit); }, '#37d0a0', 'IA (integrity; q at limit)'); | |
| } else if (state.setting === 'su' && isCCM()) { | |
| sample(function (lv) { return r.ia(lv, Math.min(lv, r.qLimit)); }, '#37d0a0', 'IA (integrity)'); | |
| } else { | |
| sample(function (lv) { return r.ia.length >= 2 ? r.ia(lv, lv) : r.ia(lv); }, '#37d0a0', 'IA (integrity)'); | |
| } | |
| } | |
| // limit markers | |
| function mark(lim, color, label) { | |
| if (!isFinite(lim)) return; | |
| svg.appendChild(el('line', { x1: X(lim), y1: m.t, x2: X(lim), y2: m.t + ih, stroke: color, 'stroke-dasharray': '2 3' })); | |
| var t = el('text', { x: X(lim), y: m.t + 10, 'text-anchor': 'middle', 'font-size': '9', fill: color }); | |
| t.textContent = label; svg.appendChild(t); | |
| } | |
| mark(qL, '#4da3ff', 'q limit'); | |
| mark(vL, '#37d0a0', 'v limit'); | |
| // legend | |
| var leg = $('chartLegend'); | |
| leg.innerHTML = ''; | |
| curves.forEach(function (c) { | |
| var s = document.createElement('span'); | |
| var i = document.createElement('i'); i.style.background = c.color; | |
| s.appendChild(i); s.appendChild(document.createTextNode(c.name)); | |
| leg.appendChild(s); | |
| }); | |
| var sp = document.createElement('span'); | |
| var ip = document.createElement('i'); ip.style.background = '#ffb454'; | |
| sp.appendChild(ip); sp.appendChild(document.createTextNode('target p')); | |
| leg.appendChild(sp); | |
| } | |
| /* ---------- formulas panel ---------- */ | |
| function formulaText(result) { | |
| var alg = A.ALGORITHMS[state.alg]; | |
| var fam = alg.family, mu = state.setting === 'mu'; | |
| var L = fam === 'ChaCha20-Poly1305' | |
| ? 'L\' <= ' + result.poly1305Blocks.toLocaleString('en-US') + | |
| ' Poly1305 data blocks' | |
| : 'L = 2^' + (state.lLBytes - 4) + ' blocks'; | |
| var pSym = state.nonceMode === 'random' && result.collisionBudget !== -Infinity | |
| ? 'p_c' | |
| : 'p'; | |
| var lines = []; | |
| if (fam === 'AEGIS') { | |
| lines.push('Parameters: k=' + alg.k + ', nonce=' + alg.r + | |
| ', tag=' + state.tagBits + ', effective online-forgery strength=' + | |
| Math.min(state.tagBits, alg.differentialForgeryBits) + | |
| ', rate=' + (alg.rateBytes * 8) + ' bits; p = 2^' + state.lP + | |
| '; o = 2^' + state.lO + | |
| (mu && state.nonceMode === 'random' ? '; M = 2^' + state.lM : '')); | |
| } else { | |
| lines.push('Parameters: k=' + alg.k + ', n=' + alg.n + ', t=' + alg.t + ', r=' + alg.r + | |
| '; ' + L + '; p = 2^' + state.lP + '; o = 2^' + state.lO + | |
| (mu && (fam === 'GCM' || fam === 'CCM') | |
| ? '; ' + (isCCM() ? 'C' : 'B') + ' = 2^' + state.lB | |
| : '') + | |
| (mu && state.nonceMode === 'random' ? '; M = 2^' + state.lM : '')); | |
| } | |
| if (pSym === 'p_c') { | |
| lines.push('Conditional-bound target: p_c = p/2 = 2^' + result.conditionalTarget); | |
| } | |
| lines.push(''); | |
| if (fam === 'AEGIS') { | |
| lines.push('Claim-based model (' + | |
| (state.nonceMode === 'random' ? 'conditional on no nonce repeat; ' : 'nonce-respecting; ') + | |
| state.tagBits + '-bit tag):'); | |
| lines.push('Confidentiality: no q-dependent CA bound is stated by the cited sources'); | |
| lines.push(state.nonceMode === 'random' | |
| ? ' random-nonce collision risk supplies the finite q limit' | |
| : ' q has no finite derived limit here'); | |
| var fb = Math.min(state.tagBits, alg.differentialForgeryBits); | |
| lines.push('Integrity model: IA ≈ v / 2^' + fb + | |
| (state.tagBits === 128 ? ' [AEGIS v1.1, Claim 1]' : ' [attack-specific]')); | |
| lines.push(' v <= ' + pSym + ' * 2^' + fb); | |
| lines.push('Combined model: AEA ≈ v/2^' + fb + ' + o/2^k'); | |
| if (alg.differentialForgeryBits < state.tagBits) { | |
| lines.push('Trial condition: the 2^-216 differential path uses chosen-message'); | |
| lines.push(' source material plus an online forgery submission'); | |
| } else { | |
| lines.push('Attack basis: ' + (state.tagBits === 128 | |
| ? 'online tag guessing' | |
| : '256-bit tag guessing; SSI24 finds no cheaper differential path')); | |
| } | |
| lines.push('Tag birthday: q ≈ 2^' + (state.tagBits / 2) + ' makes output collisions likely, but'); | |
| lines.push(' an output collision is not a fresh forgery or a state collision'); | |
| if (mu) { | |
| lines.push('Multi-key: q and v are totals across keys; unique per-key nonce identifiers assumed'); | |
| } | |
| } else if (!mu) { | |
| if (fam === 'GCM') { | |
| lines.push('Confidentiality: CA <= (s+q+1)^2 / 2^129'); | |
| lines.push(' q <= (sqrt(' + pSym + ')*2^64.5 - 1) / (L+1)'); | |
| lines.push('Integrity: IA <= v*(L+1)/2^128 * delta_128(s+q+v+1)'); | |
| lines.push(' delta_n(a) = (1 - (a-1)/2^n)^(-a/2), s <= q*L'); | |
| lines.push(' v is solved numerically at the displayed q; v <= 2^64'); | |
| } else if (fam === 'ChaCha20-Poly1305') { | |
| lines.push('Confidentiality: CA <= 0 (no limit beyond ChaCha20 PRF security)'); | |
| lines.push('Poly1305: epsilon(L\') = 2^25*(L\'+1)'); | |
| lines.push('Integrity: IA <= v*epsilon(L\')/2^128'); | |
| lines.push(' v <= ' + pSym + '*2^128/epsilon(L\')'); | |
| lines.push(' (= ' + pSym + '*2^103/(L\'+1), exactly)'); | |
| } else if (alg.t === 128) { | |
| lines.push('Confidentiality: CA <= (2L*q)^2 / 2^128'); | |
| lines.push(' q <= sqrt(' + pSym + ')*2^63 / L'); | |
| lines.push('Integrity: IA <= v/2^128 + (2L(v+q))^2 / 2^128'); | |
| lines.push(' v+q <= sqrt(' + pSym + ')*2^63 / L (simplified)'); | |
| } else { | |
| lines.push('Confidentiality: CA <= (2L*q)^2 / 2^128'); | |
| lines.push(' q <= sqrt(' + pSym + ')*2^63 / L'); | |
| lines.push('Integrity: IA <= v/2^64 + (2L(v+q))^2 / 2^128'); | |
| lines.push(' split evenly: v <= ' + pSym + '*2^63, (2L(v+q))^2 <= ' + pSym + '*2^127'); | |
| } | |
| lines.push(''); | |
| lines.push('All single-key bounds are additionally floored by offline key search: AEA <= o/2^k.'); | |
| } else { | |
| if (fam === 'GCM' && state.nonceMode === 'implicit') { | |
| lines.push('AEA <= ((q+v)*o + (q+v)^2)/2^(k+26) + (q+v)*L*B/2^127 [TLS 1.2 nonces]'); | |
| if (alg.k === 128) lines.push(' (o <= q+v): q+v <= min(sqrt(' + pSym + ')*2^76, ' + pSym + '*2^126/(L*B))'); | |
| else lines.push(' (k=256, first term negligible): q+v <= ' + pSym + '*2^127/(L*B)'); | |
| lines.push('CA: same form with q. IA <= AEA.'); | |
| } else if (fam === 'GCM') { | |
| lines.push('AEA <= (q+v)*L*B / 2^127 [nonce randomization' + | |
| (state.nonceMode === 'random' ? ', used as conditional model' : '') + ']'); | |
| lines.push('CA <= q*L*B / 2^127; IA <= AEA'); | |
| lines.push(' q+v <= ' + pSym + '*2^127/(L*B); per-value q,v assume equal proportions'); | |
| lines.push(' assumes B >> 100; AES-128-GCM also assumes o <= 2^70'); | |
| } else if (fam === 'ChaCha20-Poly1305') { | |
| lines.push('Exact Theorem 7.2: epsilon(L\') = 2^25*(L\'+1)'); | |
| lines.push('AEA <= v*(epsilon(L\')+3)/2^128 + d*(o+q)/2^256'); | |
| lines.push(' + 2*o*256/2^256 + 2*v*768/2^256'); | |
| lines.push(' + (sigma+q)^2/2^513 + 2^-254 + 2^-254 + 2^(-96*delta)'); | |
| lines.push(' sigma <= q*' + result.chachaMessageBlocks + | |
| ' encrypted ChaCha20 blocks; delta/d are minimized numerically'); | |
| lines.push(' d = ceil((delta+1)*96 / max(1, 96-log2(q))) - 1'); | |
| lines.push('CA uses AEA(q,0); IA uses AEA(0,v); theorem query ranges are enforced'); | |
| } else { | |
| lines.push('AEA <= (q+v)*L*C/2^127 + v/2^t + o/2^(k-6) [nonce randomization' + | |
| (state.nonceMode === 'random' ? ', used as conditional model' : '') + ']'); | |
| lines.push(' (o <= q+v, even split): q+v <= ' + pSym + '*2^127/(L*C), v <= ' + pSym + '*2^(t-1)'); | |
| lines.push(' per-value q,v assume equal proportions'); | |
| } | |
| } | |
| if (state.nonceMode === 'random') { | |
| lines.push(''); | |
| lines.push('Independent random nonces (overall target p):'); | |
| if (result.collisionBudget === -Infinity) { | |
| lines.push(' M = 1, so a same-key nonce collision is impossible'); | |
| lines.push(' the full target p remains available to the conditional bound'); | |
| } else { | |
| lines.push(' conditional security target = collision target = p/2 = 2^' + | |
| result.collisionBudget); | |
| lines.push(mu | |
| ? ' Pr[same-key repeat] <= q*(min(q,M)-1) / 2^' + (alg.r + 1) | |
| : ' Pr[repeat] <= q*(q-1) / 2^' + (alg.r + 1)); | |
| lines.push(' collision allocation gives q <= ' + A.fmtPow(result.collisionLimit)); | |
| lines.push(' CA/IA/AEA <= corresponding conditional bound + Pr[repeat]'); | |
| } | |
| if (result.randomNoncePerKeyCap !== undefined) { | |
| lines.push(' specification guidance/cap: at most 2^' + | |
| result.randomNoncePerKeyCap + ' messages per key'); | |
| } | |
| } | |
| return lines.join('\n'); | |
| } | |
| function sourceText() { | |
| var sources = []; | |
| if (state.nonceMode === 'random') { | |
| var fam = A.ALGORITHMS[state.alg].family; | |
| if (fam === 'GCM') { | |
| sources.push('<a href="https://doi.org/10.6028/NIST.SP.800-38D">NIST SP 800-38D, Sections 8.2.2 and 8.3</a>'); | |
| } else if (fam === 'ChaCha20-Poly1305') { | |
| sources.push('<a href="https://www.rfc-editor.org/rfc/rfc8439.html#section-2.6">RFC 8439, Sections 2.6 and 4</a>'); | |
| } else if (fam === 'CCM') { | |
| sources.push('<a href="https://doi.org/10.6028/NIST.SP.800-38C">NIST SP 800-38C</a>'); | |
| } | |
| } | |
| if (isAEGIS()) { | |
| sources.push('<a href="https://datatracker.ietf.org/doc/draft-irtf-cfrg-aegis-aead/#section-10.1.1">AEGIS specification, Key and Nonce Selection</a>'); | |
| sources.push('<a href="https://competitions.cr.yp.to/round3/aegisv11.pdf">AEGIS v1.1, Section 4</a>'); | |
| sources.push('<a href="https://doi.org/10.1007/978-981-97-7737-2_1">256-bit-tag differential analysis (SSI24)</a>'); | |
| } else if (A.ALGORITHMS[state.alg].family === 'ChaCha20-Poly1305') { | |
| sources.push('<a href="https://eprint.iacr.org/2023/085.pdf">Corrected ChaCha20-Poly1305 analysis, Theorems 3.2 and ' + | |
| (state.setting === 'mu' ? '7.2' : '4.1') + '</a>'); | |
| } | |
| return sources.length | |
| ? '<p class="muted">Sources: ' + sources.join('; ') + '.</p>' | |
| : ''; | |
| } | |
| /* ---------- render ---------- */ | |
| function render() { | |
| // slider value labels | |
| $('pVal').textContent = '2^' + state.lP; | |
| $('lVal').textContent = bytesLabel(state.lLBytes) + ' (2^' + state.lLBytes + ')'; | |
| $('oVal').textContent = '2^' + state.lO; | |
| $('bVal').textContent = '2^' + state.lB; | |
| $('mVal').textContent = '2^' + state.lM; | |
| var r = A.compute(params()); | |
| // plain-English operational guidance | |
| $('recommendationText').textContent = recommendationText(r); | |
| // cards | |
| $('qBig').textContent = A.fmtPow(r.qLimit); | |
| $('vBig').textContent = A.fmtPow(r.vLimit); | |
| $('qSub').textContent = isFinite(r.qLimit) ? '≈ ' + A.fmtNum(r.qLimit) + ' messages' + | |
| (r.qLimitReason ? ' (' + r.qLimitReason + ')' : '') | |
| : (r.claimBased ? 'not bounded' : 'no message limit from CA'); | |
| $('vSub').textContent = isFinite(r.vLimit) ? '≈ ' + A.fmtNum(r.vLimit) + ' forgeries' + | |
| (r.vLimitReason ? ' (' + r.vLimitReason + ')' : '') | |
| : 'no forgery limit'; | |
| var off = state.lO - A.ALGORITHMS[state.alg].k; | |
| $('offBig').textContent = A.fmtPow(off); | |
| // warnings | |
| var w = $('warnings'); | |
| w.innerHTML = ''; | |
| if (r.warnings.length) { | |
| var d = document.createElement('div'); | |
| d.className = 'notice warn'; | |
| d.innerHTML = '<strong>Assumption warnings</strong><ul>' + | |
| r.warnings.map(function (x) { return '<li>' + x + '</li>'; }).join('') + '</ul>'; | |
| w.appendChild(d); | |
| } | |
| if (r.notes.length) { | |
| var dn = document.createElement('div'); | |
| dn.className = 'notice info'; | |
| dn.innerHTML = '<ul>' + r.notes.map(function (x) { return '<li>' + x + '</li>'; }).join('') + '</ul>'; | |
| w.appendChild(dn); | |
| } | |
| // comparison table | |
| var tb = $('cmpTable').querySelector('tbody'); | |
| tb.innerHTML = ''; | |
| COMPARISON_ROWS.forEach(function (row) { | |
| var name = row.name; | |
| var rr = A.compute({ | |
| alg: name, setting: state.setting, nonceMode: state.nonceMode, | |
| tagBits: state.tagBits, lP: state.lP, lLBytes: state.lLBytes, | |
| lO: state.lO, lB: state.lB, lM: state.lM | |
| }); | |
| var tr = document.createElement('tr'); | |
| if (name === state.alg) tr.className = 'sel'; | |
| function cell(txt, cls) { var td = document.createElement('td'); td.textContent = txt; if (cls) td.className = cls; return td; } | |
| var binding = bindingOf(rr); | |
| tr.appendChild(cell(row.label)); | |
| tr.appendChild(cell(A.fmtPow(rr.qLimit), 'num')); | |
| tr.appendChild(cell(A.fmtPow(rr.vLimit), 'num')); | |
| tr.appendChild(cell(binding)); | |
| tr.addEventListener('click', function () { selectAlgorithm(name); }); | |
| tb.appendChild(tr); | |
| }); | |
| // chart + formulas | |
| $('chartDesc').textContent = chartDesc(r); | |
| drawChart(r); | |
| $('fAlg').textContent = state.alg; | |
| $('fSetting').textContent = state.setting === 'mu' ? 'multi-key' : 'single-key'; | |
| $('formulaBody').innerHTML = '<div class="fblock">' + formulaText(r) + '</div>' + sourceText(); | |
| } | |
| function bindingOf(r) { | |
| if (r.qLimitReason && (!isFinite(r.vLimit) || r.qLimit <= r.vLimit)) { | |
| return r.qLimitReason; | |
| } | |
| if (!isFinite(r.qLimit) && isFinite(r.vLimit)) return 'integrity'; | |
| if (isFinite(r.qLimit) && !isFinite(r.vLimit)) return 'confidentiality'; | |
| if (!isFinite(r.qLimit) && !isFinite(r.vLimit)) return '—'; | |
| return r.qLimit <= r.vLimit ? 'confidentiality' : 'integrity'; | |
| } | |
| function chartDesc(r) { | |
| var s = 'Attacker advantage as usage grows. The curve crossing the dashed target line gives the limit. '; | |
| if (state.nonceMode === 'random') { | |
| s = 'Attacker advantage including same-key random-nonce collision risk. ' + | |
| 'The limit markers use separate p/2 budgets for the conditional bound and collision event, so they can precede the overall p crossing. ' + | |
| 'The integrity curve holds q at the displayed confidentiality limit while v grows. '; | |
| } | |
| if (state.nonceMode !== 'random' && state.setting === 'su' && isCCM()) { | |
| s += 'For CCM the IA curve pairs each v with the largest admissible q.'; | |
| } | |
| if (!isFinite(r.qLimit)) { | |
| s += r.claimBased | |
| ? 'The cited AEGIS claims contain no q-dependent CA bound, so no CA curve is drawn.' | |
| : 'Confidentiality imposes no finite limit here, so no CA curve is drawn.'; | |
| } | |
| return s; | |
| } | |
| /* ---------- init ---------- */ | |
| // Allow deep-linking / testing via URL params, e.g. | |
| // index.html?alg=AEAD_AEGIS128L&tag=256&setting=mu&nonce=random&p=-60&l=11&o=70&b=27&m=20 | |
| (function applyUrl() { | |
| var q = new URLSearchParams(location.search); | |
| if (q.get('alg') && A.ALGORITHMS[q.get('alg')]) state.alg = q.get('alg'); | |
| if (q.get('setting') === 'mu' || q.get('setting') === 'su') state.setting = q.get('setting'); | |
| if (q.get('nonce') === 'implicit' || q.get('nonce') === 'randomized' || q.get('nonce') === 'random') state.nonceMode = q.get('nonce'); | |
| if (q.get('tag') === '128' || q.get('tag') === '256') state.tagBits = parseInt(q.get('tag'), 10); | |
| ['p', 'l', 'o', 'b', 'm'].forEach(function (k) { | |
| var v = parseFloat(q.get(k)); | |
| if (!isNaN(v)) { | |
| if (k === 'p') state.lP = v; | |
| if (k === 'l') state.lLBytes = v; | |
| if (k === 'o') state.lO = v; | |
| if (k === 'b') state.lB = v; | |
| if (k === 'm') state.lM = v; | |
| } | |
| }); | |
| if (!isAEGIS()) state.tagBits = 128; | |
| })(); | |
| syncControls(); | |
| render(); | |
| })(); | |
| </script> | |
| </body> | |
| </html> | |