Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e8229e13e | ||
|
|
230bd70e07 | ||
|
|
c36cb4a0fe | ||
|
|
d6b167a50b | ||
|
|
27f4cd6857 |
+3
-3
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"VERSION": "v0.7.57",
|
||||
"VERSION_NUMBER": "0.7.57",
|
||||
"BUILD_DATE": "2026-06-29T00:17:04.254Z"
|
||||
"VERSION": "v0.7.62",
|
||||
"VERSION_NUMBER": "0.7.62",
|
||||
"BUILD_DATE": "2026-06-29T00:30:26.801Z"
|
||||
}
|
||||
|
||||
+399
-49
@@ -91,7 +91,7 @@
|
||||
}
|
||||
|
||||
#stegoWrap .subtitle {
|
||||
color: var(--muted-color);
|
||||
color: var(--primary-color);
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
@@ -112,7 +112,7 @@
|
||||
}
|
||||
|
||||
.stegoHint {
|
||||
color: var(--muted-color);
|
||||
color: var(--primary-color);
|
||||
font-size: 13px;
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
@@ -126,7 +126,7 @@
|
||||
|
||||
.stegoFormRow label {
|
||||
font-size: 13px;
|
||||
color: var(--muted-color);
|
||||
color: var(--primary-color);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@
|
||||
}
|
||||
|
||||
.stegoStatus {
|
||||
color: var(--muted-color);
|
||||
color: var(--primary-color);
|
||||
font-size: 13px;
|
||||
margin: 8px 0;
|
||||
min-height: 1.2em;
|
||||
@@ -166,7 +166,7 @@
|
||||
|
||||
.stegoInfo {
|
||||
font-size: 13px;
|
||||
color: var(--muted-color);
|
||||
color: var(--primary-color);
|
||||
margin: 8px 0 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
@@ -200,7 +200,7 @@
|
||||
.stegoProgressFiles {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--muted-color);
|
||||
color: var(--primary-color);
|
||||
font-family: var(--font-mono, "SF Mono", "Fira Code", "Consolas", monospace);
|
||||
max-height: 120px;
|
||||
overflow-y: auto;
|
||||
@@ -302,9 +302,88 @@
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* ---------- FAQ items ---------- */
|
||||
.stegoFaqItem {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
margin-bottom: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.stegoFaqItem:last-child { margin-bottom: 0; }
|
||||
|
||||
.stegoFaqToggle {
|
||||
padding: 12px 14px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
background: var(--background-color);
|
||||
width: 100%;
|
||||
}
|
||||
.stegoFaqToggle:hover {
|
||||
background: var(--secondary-color);
|
||||
}
|
||||
.stegoFaqItem .stegoCollapsibleContent {
|
||||
padding: 0 14px 14px 14px;
|
||||
margin-top: 0;
|
||||
}
|
||||
.stegoCollapsibleContent ul {
|
||||
padding-left: 20px;
|
||||
margin: 8px 0;
|
||||
}
|
||||
.stegoCollapsibleContent li {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
/* ---------- Example walkthrough ---------- */
|
||||
.stegoExampleH3 {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: var(--accent-color);
|
||||
margin: 16px 0 8px 0;
|
||||
}
|
||||
.stegoExampleH4 {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--primary-color);
|
||||
margin: 14px 0 6px 0;
|
||||
}
|
||||
.stegoExampleOutput {
|
||||
font-family: var(--font-mono, "SF Mono", "Fira Code", "Consolas", monospace);
|
||||
background: var(--background-color);
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--accent-color);
|
||||
font-size: 13px;
|
||||
margin: 8px 0;
|
||||
}
|
||||
.stegoTableWrap {
|
||||
overflow-x: auto;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.stegoTable {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 12px;
|
||||
font-family: var(--font-mono, "SF Mono", "Fira Code", "Consolas", monospace);
|
||||
}
|
||||
.stegoTable th,
|
||||
.stegoTable td {
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 4px 8px;
|
||||
text-align: left;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.stegoTable th {
|
||||
background: var(--background-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
.stegoTable tr:nth-child(even) td {
|
||||
background: var(--background-color);
|
||||
}
|
||||
|
||||
.stegoFooter {
|
||||
text-align: center;
|
||||
color: var(--muted-color);
|
||||
color: var(--primary-color);
|
||||
font-size: 12px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
@@ -403,37 +482,302 @@
|
||||
<p id="stegoMatchIndicator" class="stegoInfo"></p>
|
||||
</section>
|
||||
|
||||
<!-- How It Works -->
|
||||
<!-- ================================================================
|
||||
FAQ SECTION
|
||||
================================================================
|
||||
Each question is its own collapsible card using the same
|
||||
stegoCollapsibleToggle / stegoCollapsibleContent pattern.
|
||||
================================================================ -->
|
||||
<section class="stegoCard">
|
||||
<h2>
|
||||
<button id="stegoHowToggle" class="stegoCollapsibleToggle" aria-expanded="false">
|
||||
How It Works <span class="chevron">▸</span>
|
||||
<h2 style="text-align: center; margin-bottom: 16px;">FAQ</h2>
|
||||
|
||||
<!-- Q1: What is the point of this? -->
|
||||
<div class="stegoFaqItem">
|
||||
<button class="stegoCollapsibleToggle stegoFaqToggle" aria-expanded="false">
|
||||
What is the point of this? <span class="chevron">▸</span>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="stegoHowContent" class="stegoCollapsibleContent stegoHidden">
|
||||
<p>
|
||||
This demo uses a <strong>half-splitting entropy coding</strong> scheme
|
||||
built on top of GPT-2's next-token probability distribution.
|
||||
</p>
|
||||
<ol>
|
||||
<li>The secret message is converted to a bit string (UTF-8 → bits).</li>
|
||||
<li>For each secret bit, GPT-2 produces a probability distribution over
|
||||
the entire vocabulary for the next token.</li>
|
||||
<li>Tokens are sorted by probability (descending) and split into two
|
||||
halves at the 50% cumulative probability mark.</li>
|
||||
<li>Bit <code>0</code> → the next token is sampled from the
|
||||
<em>first</em> (higher-probability) half; bit <code>1</code> →
|
||||
from the <em>second</em> half.</li>
|
||||
<li>A shared-key PRNG (mulberry32) selects the exact token within the
|
||||
chosen half, so the decoder can reproduce the same random draws.</li>
|
||||
<li>The decoder re-runs GPT-2 on the same context, observes which half
|
||||
each cover token fell into, and recovers the bits → original message.</li>
|
||||
</ol>
|
||||
<p>
|
||||
Because both sides share the same model, context, and PRNG seed, the
|
||||
decoder can perfectly reconstruct the hidden bits. The resulting cover
|
||||
text reads like normal GPT-2 output, hiding the secret in plain sight.
|
||||
</p>
|
||||
<div class="stegoCollapsibleContent stegoHidden">
|
||||
<p>
|
||||
This demo lets you <strong>hide a secret message inside ordinary-looking
|
||||
AI-generated text</strong>. The output reads like a normal sentence a
|
||||
language model might produce, but it secretly encodes your message bit
|
||||
by bit.
|
||||
</p>
|
||||
<p>
|
||||
The practical point is <em>covert communication</em>: two people who
|
||||
share a key can exchange messages that, to anyone watching, look like
|
||||
innocuous GPT-2 text. There's no obvious ciphertext, no encrypted file,
|
||||
and no metadata screaming "this is encrypted." The secret is hidden in
|
||||
plain sight.
|
||||
</p>
|
||||
<p>
|
||||
It's also a neat demonstration of how much information is packed into
|
||||
every token a language model emits — each token can carry a full secret
|
||||
bit while still looking natural.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Q2: How does it work? -->
|
||||
<div class="stegoFaqItem">
|
||||
<button class="stegoCollapsibleToggle stegoFaqToggle" aria-expanded="false">
|
||||
How does it work? <span class="chevron">▸</span>
|
||||
</button>
|
||||
<div class="stegoCollapsibleContent stegoHidden">
|
||||
<p>
|
||||
This demo uses a <strong>half-splitting entropy coding</strong> scheme
|
||||
built on top of GPT-2's next-token probability distribution.
|
||||
</p>
|
||||
<ol>
|
||||
<li>The secret message is converted to a bit string (UTF-8 → bits).</li>
|
||||
<li>For each secret bit, GPT-2 produces a probability distribution over
|
||||
the entire vocabulary for the next token.</li>
|
||||
<li>Tokens are sorted by probability (descending) and split into two
|
||||
halves at the 50% cumulative probability mark.</li>
|
||||
<li>Bit <code>0</code> → the next token is sampled from the
|
||||
<em>first</em> (higher-probability) half; bit <code>1</code> →
|
||||
from the <em>second</em> half.</li>
|
||||
<li>A shared-key PRNG (mulberry32) selects the exact token within the
|
||||
chosen half, so the decoder can reproduce the same random draws.</li>
|
||||
<li>The decoder re-runs GPT-2 on the same context, observes which half
|
||||
each cover token fell into, and recovers the bits → original message.</li>
|
||||
</ol>
|
||||
<p>
|
||||
Because both sides share the same model, context, and PRNG seed, the
|
||||
decoder can perfectly reconstruct the hidden bits. The resulting cover
|
||||
text reads like normal GPT-2 output, hiding the secret in plain sight.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Q3: Can you give me a simple example? -->
|
||||
<div class="stegoFaqItem">
|
||||
<button class="stegoCollapsibleToggle stegoFaqToggle" aria-expanded="false">
|
||||
Can you give me a simple example of how it works? <span class="chevron">▸</span>
|
||||
</button>
|
||||
<div class="stegoCollapsibleContent stegoHidden">
|
||||
<p>
|
||||
To make this tangible, let's use a <strong>toy model with a small
|
||||
vocabulary of 11 words</strong> so you can see every number. The real
|
||||
scheme works identically, just with vocabularies of ~50,000 tokens and
|
||||
floating-point probabilities.
|
||||
</p>
|
||||
|
||||
<h3 class="stegoExampleH3">Setup</h3>
|
||||
<p>
|
||||
<strong>The model:</strong> A tiny "LLM" with a fixed vocabulary of 11
|
||||
words: <code>{apple, date, banana, cherry, pie, juice, tart, sauce, for, with, and}</code>.
|
||||
At each step, the model assigns a probability to all 11 words based on
|
||||
the context. The probabilities change as the context grows, and they
|
||||
always sum to 1.0.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Shared context/prompt:</strong> <code>"I like to eat"</code> —
|
||||
both Alice and Bob have this. It is not secret, just shared.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Shared secret key:</strong> Used to seed a PRNG, producing the
|
||||
random stream: <code>0.15, 0.62, 0.40, ...</code>
|
||||
</p>
|
||||
<p>
|
||||
<strong>Secret message:</strong> The bits <code>0 1 1</code> (3 bits).
|
||||
</p>
|
||||
|
||||
<h3 class="stegoExampleH3">Encoding (Alice's Side)</h3>
|
||||
|
||||
<h4 class="stegoExampleH4">Step 1: Alice runs the model</h4>
|
||||
<p>
|
||||
Alice feeds <code>"I like to eat"</code> into the model. After "I like
|
||||
to eat", fruits are the most natural continuation, so they get the
|
||||
highest probabilities:
|
||||
</p>
|
||||
<div class="stegoTableWrap">
|
||||
<table class="stegoTable">
|
||||
<thead><tr><th>Word</th><th>Probability</th><th>Interval</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>apple</td><td>0.20</td><td>[0.00, 0.20)</td></tr>
|
||||
<tr><td>date</td><td>0.15</td><td>[0.20, 0.35)</td></tr>
|
||||
<tr><td>banana</td><td>0.10</td><td>[0.35, 0.45)</td></tr>
|
||||
<tr><td>cherry</td><td>0.05</td><td>[0.45, 0.50)</td></tr>
|
||||
<tr><td>pie</td><td>0.20</td><td>[0.50, 0.70)</td></tr>
|
||||
<tr><td>juice</td><td>0.12</td><td>[0.70, 0.82)</td></tr>
|
||||
<tr><td>tart</td><td>0.08</td><td>[0.82, 0.90)</td></tr>
|
||||
<tr><td>sauce</td><td>0.05</td><td>[0.90, 0.95)</td></tr>
|
||||
<tr><td>for</td><td>0.03</td><td>[0.95, 0.98)</td></tr>
|
||||
<tr><td>with</td><td>0.015</td><td>[0.98, 0.995)</td></tr>
|
||||
<tr><td>and</td><td>0.005</td><td>[0.995, 1.00)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p>The probabilities sum to 1.0, and the intervals partition [0, 1).</p>
|
||||
|
||||
<h4 class="stegoExampleH4">Step 2: Alice encodes her first secret bit</h4>
|
||||
<p>
|
||||
Alice's first secret bit is <code>0</code> → "look in the <strong>first
|
||||
half</strong> [0.00, 0.50)." The words in that half are apple, date,
|
||||
banana, and cherry. Alice uses her next random number, <code>0.15</code>,
|
||||
to pick within [0.00, 0.50). The value <code>0.15</code> falls in
|
||||
[0.00, 0.20), which is <strong>apple</strong>.
|
||||
</p>
|
||||
<p class="stegoExampleOutput">Alice outputs: "apple" → text so far: "I like to eat apple"</p>
|
||||
|
||||
<h4 class="stegoExampleH4">Step 3: Alice runs the model again</h4>
|
||||
<p>
|
||||
Context is now <code>"I like to eat apple"</code>. After "apple", food
|
||||
preparations like pie and tart become more likely:
|
||||
</p>
|
||||
<div class="stegoTableWrap">
|
||||
<table class="stegoTable">
|
||||
<thead><tr><th>Word</th><th>Probability</th><th>Interval</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>apple</td><td>0.15</td><td>[0.00, 0.15)</td></tr>
|
||||
<tr><td>date</td><td>0.10</td><td>[0.15, 0.25)</td></tr>
|
||||
<tr><td>banana</td><td>0.08</td><td>[0.25, 0.33)</td></tr>
|
||||
<tr><td>cherry</td><td>0.07</td><td>[0.33, 0.40)</td></tr>
|
||||
<tr><td>for</td><td>0.06</td><td>[0.40, 0.46)</td></tr>
|
||||
<tr><td>with</td><td>0.04</td><td>[0.46, 0.50)</td></tr>
|
||||
<tr><td>pie</td><td>0.20</td><td>[0.50, 0.70)</td></tr>
|
||||
<tr><td>tart</td><td>0.16</td><td>[0.70, 0.86)</td></tr>
|
||||
<tr><td>juice</td><td>0.08</td><td>[0.86, 0.94)</td></tr>
|
||||
<tr><td>sauce</td><td>0.04</td><td>[0.94, 0.98)</td></tr>
|
||||
<tr><td>and</td><td>0.02</td><td>[0.98, 1.00)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p>
|
||||
Alice's next bit is <code>1</code> → "look in the <strong>second half</strong>
|
||||
[0.50, 1.00)." Random number <code>0.62</code> → scaled:
|
||||
<code>0.50 + 0.62 × 0.50 = 0.81</code>, which falls in [0.70, 0.86) →
|
||||
<strong>tart</strong>.
|
||||
</p>
|
||||
<p class="stegoExampleOutput">Alice outputs: "tart" → text so far: "I like to eat apple tart"</p>
|
||||
|
||||
<h4 class="stegoExampleH4">Step 4: Alice runs the model again</h4>
|
||||
<p>
|
||||
Context: <code>"I like to eat apple tart"</code>. After "apple tart",
|
||||
connectors like "with" and "and" become most likely:
|
||||
</p>
|
||||
<div class="stegoTableWrap">
|
||||
<table class="stegoTable">
|
||||
<thead><tr><th>Word</th><th>Probability</th><th>Interval</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>apple</td><td>0.10</td><td>[0.00, 0.10)</td></tr>
|
||||
<tr><td>date</td><td>0.08</td><td>[0.10, 0.18)</td></tr>
|
||||
<tr><td>banana</td><td>0.07</td><td>[0.18, 0.25)</td></tr>
|
||||
<tr><td>cherry</td><td>0.06</td><td>[0.25, 0.31)</td></tr>
|
||||
<tr><td>pie</td><td>0.08</td><td>[0.31, 0.39)</td></tr>
|
||||
<tr><td>juice</td><td>0.06</td><td>[0.39, 0.45)</td></tr>
|
||||
<tr><td>for</td><td>0.05</td><td>[0.45, 0.50)</td></tr>
|
||||
<tr><td>tart</td><td>0.06</td><td>[0.50, 0.56)</td></tr>
|
||||
<tr><td>sauce</td><td>0.04</td><td>[0.56, 0.60)</td></tr>
|
||||
<tr><td>with</td><td>0.18</td><td>[0.60, 0.78)</td></tr>
|
||||
<tr><td>and</td><td>0.22</td><td>[0.78, 1.00)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p>
|
||||
Alice's next bit is <code>1</code> → second half [0.50, 1.00). Random
|
||||
number <code>0.40</code> → scaled: <code>0.50 + 0.40 × 0.50 = 0.70</code>,
|
||||
falls in [0.60, 0.78) → <strong>with</strong>.
|
||||
</p>
|
||||
<p class="stegoExampleOutput">Alice outputs: "with" → text so far: "I like to eat apple tart with"</p>
|
||||
|
||||
<h4 class="stegoExampleH4">Alice is done</h4>
|
||||
<p>
|
||||
Alice sends Bob the text: <strong>"I like to eat apple tart with"</strong>
|
||||
(plus whatever padding she wants to make it look like a complete
|
||||
sentence). To any observer, this looks like someone generated a
|
||||
sentence about food. Nothing suspicious.
|
||||
</p>
|
||||
|
||||
<h3 class="stegoExampleH3">Decoding (Bob's Side)</h3>
|
||||
<p>
|
||||
Bob has the received text, the same model, the same shared context
|
||||
<code>"I like to eat"</code>, and the same shared key (so the same
|
||||
random stream: <code>0.15, 0.62, 0.40, ...</code>).
|
||||
</p>
|
||||
|
||||
<h4 class="stegoExampleH4">Step D1: Bob runs the model</h4>
|
||||
<p>
|
||||
Bob feeds <code>"I like to eat"</code> into the model and gets the
|
||||
<strong>same</strong> distribution Alice got. He sees Alice chose
|
||||
<strong>apple</strong>, interval [0.00, 0.20). "Which half?" First half
|
||||
[0.00, 0.50) → <strong>bit <code>0</code></strong> ✓
|
||||
</p>
|
||||
|
||||
<h4 class="stegoExampleH4">Step D2: Bob runs the model again</h4>
|
||||
<p>
|
||||
Context: <code>"I like to eat apple"</code>. Same distribution as Alice's
|
||||
Step 3. Bob sees Alice chose <strong>tart</strong>, interval [0.70, 0.86).
|
||||
"Which half?" Second half [0.50, 1.00) → <strong>bit <code>1</code></strong> ✓
|
||||
</p>
|
||||
|
||||
<h4 class="stegoExampleH4">Step D3: Bob runs the model again</h4>
|
||||
<p>
|
||||
Context: <code>"I like to eat apple tart"</code>. Same distribution as
|
||||
Alice's Step 4. Bob sees Alice chose <strong>with</strong>, interval
|
||||
[0.60, 0.78). "Which half?" Second half [0.50, 1.00) →
|
||||
<strong>bit <code>1</code></strong> ✓
|
||||
</p>
|
||||
|
||||
<h3 class="stegoExampleH3">Result</h3>
|
||||
<p>
|
||||
Bob has recovered: <code>0 1 1</code> — exactly the message Alice sent.
|
||||
</p>
|
||||
|
||||
<h3 class="stegoExampleH3">Key Points</h3>
|
||||
<ol>
|
||||
<li><strong>The LLM never saw the secret message.</strong> The secret bits controlled which word was picked from the distribution. The LLM just produced distributions.</li>
|
||||
<li><strong>The output looks natural.</strong> "I like to eat apple tart with" is a perfectly normal sentence. A censor seeing this has no reason to be suspicious.</li>
|
||||
<li><strong>Both sides run the same model with the same context.</strong> This is why they get the same distributions. If Bob had a different model or different context, decoding would fail.</li>
|
||||
<li><strong>The shared key provides the random stream.</strong> Without it, a censor could run the same model and try to decode. With the key, the censor can't reproduce the random choices.</li>
|
||||
<li><strong>Each token encodes roughly 1 bit</strong> in this toy example (2-way split). With arithmetic coding over a 50,000-word vocabulary, you can encode ~2–4 bits per token.</li>
|
||||
<li><strong>The "half" splitting is the simplified version.</strong> The real scheme (from the <a href="https://eprint.iacr.org/2021/686" target="_blank" rel="noopener">Meteor paper</a>) uses full arithmetic coding, which is more efficient. But the principle is the same: secret bits steer selection within the model's probability distribution.</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Q4: What is your inspiration for this? -->
|
||||
<div class="stegoFaqItem">
|
||||
<button class="stegoCollapsibleToggle stegoFaqToggle" aria-expanded="false">
|
||||
What is your inspiration for this? <span class="chevron">▸</span>
|
||||
</button>
|
||||
<div class="stegoCollapsibleContent stegoHidden">
|
||||
<p>
|
||||
The direct inspiration for this project is a
|
||||
<a href="https://primal.net/e/nevent1qqstxmgsd0egtq57gj8mghckthsv79dhegh7xh97hwtwve6l56g4s9sqtjd7k" target="_blank" rel="noopener">post by waxwing on Primal</a>
|
||||
that walks through the core idea of using a language model's
|
||||
next-token probability distribution as a steganographic carrier
|
||||
channel. The post explains how secret bits can steer token selection
|
||||
while preserving the model's output distribution, making the
|
||||
resulting cover text statistically indistinguishable from normal
|
||||
model output.
|
||||
</p>
|
||||
<p>
|
||||
That post references the academic paper that formalized this
|
||||
approach:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<strong><a href="https://eprint.iacr.org/2021/686" target="_blank" rel="noopener">Meteor: A Simple and Practical Steganographic Protocol for LLMs</a></strong>
|
||||
— this paper introduces the entropy-coding mechanism that makes
|
||||
the output distribution-preserving. Instead of a naive 2-way
|
||||
split, the full scheme uses arithmetic coding over the model's
|
||||
probability distribution, achieving roughly 2–4 bits per token
|
||||
while remaining information-theoretically undetectable to a censor
|
||||
who only observes the channel.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
The half-splitting scheme implemented in this demo is a simplified
|
||||
version of Meteor's approach: it guarantees exactly one bit per
|
||||
token, is symmetric (encoder and decoder run the same logic), and
|
||||
produces text that's indistinguishable from normal model output to
|
||||
a casual reader. The full arithmetic-coding version from the paper
|
||||
is more efficient but the core principle is identical — secret bits
|
||||
steer selection within the model's probability distribution.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -714,12 +1058,11 @@
|
||||
return explicitAuth;
|
||||
}
|
||||
|
||||
// Convention: explicit target profiles are public-readable by default.
|
||||
if (hasTargetPubkeyInUrl()) {
|
||||
return 'optional';
|
||||
}
|
||||
|
||||
return 'required';
|
||||
// This page is a standalone demo that does not require Nostr auth.
|
||||
// Default to 'none' so visitors are never prompted to log in; the
|
||||
// sidenav logout button doubles as a "Sign in" entry point if a user
|
||||
// wants to access relay/blossom/AI sections.
|
||||
return 'none';
|
||||
}
|
||||
|
||||
function isAuthRequiredError(error) {
|
||||
@@ -1238,8 +1581,8 @@
|
||||
const decodeProgressBar = document.getElementById("stegoDecodeProgressBar");
|
||||
const decodeRecoveredChars = document.getElementById("stegoDecodeRecoveredChars");
|
||||
|
||||
const howToggle = document.getElementById("stegoHowToggle");
|
||||
const howContent = document.getElementById("stegoHowContent");
|
||||
// Legacy single-toggle references removed — FAQ section now uses
|
||||
// multiple .stegoFaqToggle buttons handled generically below.
|
||||
|
||||
// State
|
||||
let model = null;
|
||||
@@ -1249,12 +1592,19 @@
|
||||
let lastSecret = null;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Collapsible "How It Works"
|
||||
// Collapsible FAQ toggles
|
||||
// ---------------------------------------------------------------------------
|
||||
howToggle.addEventListener("click", () => {
|
||||
const expanded = howToggle.getAttribute("aria-expanded") === "true";
|
||||
howToggle.setAttribute("aria-expanded", String(!expanded));
|
||||
howContent.classList.toggle("stegoHidden", expanded);
|
||||
// Each .stegoFaqToggle button toggles the visibility of the next
|
||||
// .stegoCollapsibleContent sibling inside its .stegoFaqItem container.
|
||||
document.querySelectorAll(".stegoFaqToggle").forEach((toggle) => {
|
||||
toggle.addEventListener("click", () => {
|
||||
const expanded = toggle.getAttribute("aria-expanded") === "true";
|
||||
toggle.setAttribute("aria-expanded", String(!expanded));
|
||||
const content = toggle.nextElementSibling;
|
||||
if (content) {
|
||||
content.classList.toggle("stegoHidden", expanded);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user