summaryrefslogtreecommitdiffstats
path: root/news/phase4-wasm-browser-verification/index.html
blob: 571e0945346fa9271f49b855702e867032a6d2ef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Phase 4: Verify Without Installing Anything — Tesseras</title>
    <meta name="description" content="Tesseras now compiles to WebAssembly — anyone can verify a tessera&#x27;s integrity and authenticity directly in the browser, with no software to install.">
    <!-- Open Graph -->
    <meta property="og:type" content="article">
    <meta property="og:title" content="Phase 4: Verify Without Installing Anything">
    <meta property="og:description" content="Tesseras now compiles to WebAssembly — anyone can verify a tessera&#x27;s integrity and authenticity directly in the browser, with no software to install.">
    <meta property="og:image" content="https://tesseras.net/images/social.jpg">
    <meta property="og:image:width" content="1200">
    <meta property="og:image:height" content="630">
    <meta property="og:site_name" content="Tesseras">
    <!-- Twitter Card -->
    <meta name="twitter:card" content="summary_large_image">
    <meta name="twitter:title" content="Phase 4: Verify Without Installing Anything">
    <meta name="twitter:description" content="Tesseras now compiles to WebAssembly — anyone can verify a tessera&#x27;s integrity and authenticity directly in the browser, with no software to install.">
    <meta name="twitter:image" content="https://tesseras.net/images/social.jpg">
    <link rel="stylesheet" href="https://tesseras.net/style.css?h=21f0f32121928ee5c690">
    
        
            <link rel="alternate" type="application/atom+xml" title="Tesseras" href="https://tesseras.net/atom.xml">
        
    
    <link rel="icon" type="image/png" sizes="32x32" href="https://tesseras.net/images/favicon.png?h=be4e123a23393b1a027d">
    
</head>
<body>
    <header>
        <h1>
            <a href="https:&#x2F;&#x2F;tesseras.net/">
                <img src="https://tesseras.net/images/logo-64.png?h=c1b8d0c4c5f93b49d40b" alt="Tesseras" width="40" height="40" class="logo">
                Tesseras
            </a>
        </h1>
        <nav>
            
                <a href="https://tesseras.net/about/">About</a>
                <a href="https://tesseras.net/news/">News</a>
                <a href="https://tesseras.net/releases/">Releases</a>
                <a href="https://tesseras.net/faq/">FAQ</a>
                <a href="https://tesseras.net/subscriptions/">Subscriptions</a>
                <a href="https://tesseras.net/contact/">Contact</a>
            
        </nav>
        <nav class="lang-switch">
            
                <strong>English</strong> | <a href="/pt-br&#x2F;news&#x2F;phase4-wasm-browser-verification&#x2F;">Português</a>
            
        </nav>
    </header>

    <main>
        
<article>
    <h2>Phase 4: Verify Without Installing Anything</h2>
    <p class="news-date">2026-02-15</p>
    <p>Trust shouldn't require installing software. If someone sends you a tessera — a
bundle of preserved memories — you should be able to verify it's genuine and
unmodified without downloading an app, creating an account, or trusting a
server. That's what <code>tesseras-wasm</code> delivers: drag a tessera archive into a web
page, and cryptographic verification happens entirely in your browser.</p>
<h2 id="what-was-built">What was built</h2>
<p><strong>tesseras-wasm</strong> — A Rust crate that compiles to WebAssembly via wasm-pack,
exposing four stateless functions to JavaScript. The crate depends on
<code>tesseras-core</code> for manifest parsing and calls cryptographic primitives directly
(blake3, ed25519-dalek) rather than depending on <code>tesseras-crypto</code>, which pulls
in C-based post-quantum libraries that don't compile to
<code>wasm32-unknown-unknown</code>.</p>
<p><code>parse_manifest</code> takes raw MANIFEST bytes (UTF-8 plain text, not MessagePack),
delegates to <code>tesseras_core::manifest::Manifest::parse()</code>, and returns a JSON
string with the creator's Ed25519 public key, signature file paths, and a list
of files with their expected BLAKE3 hashes, sizes, and MIME types. Internal
structs (<code>ManifestJson</code>, <code>CreatorPubkey</code>, <code>SignatureFiles</code>, <code>FileEntry</code>) are
serialized with serde_json. The ML-DSA public key and signature file fields are
present in the JSON contract but set to <code>null</code> — ready for when post-quantum
signing is implemented on the native side.</p>
<p><code>hash_blake3</code> computes a BLAKE3 hash of arbitrary bytes and returns a
64-character hex string. It's called once per file in the tessera to verify
integrity against the MANIFEST.</p>
<p><code>verify_ed25519</code> takes a message, a 64-byte signature, and a 32-byte public key,
constructs an <code>ed25519_dalek::VerifyingKey</code>, and returns whether the signature
is valid. Length validation returns descriptive errors ("Ed25519 public key must
be 32 bytes") rather than panicking.</p>
<p><code>verify_ml_dsa</code> is a stub that returns an error explaining ML-DSA verification
is not yet available. This is deliberate: the <code>ml-dsa</code> crate on crates.io is
v0.1.0-rc.7 (pre-release), and <code>tesseras-crypto</code> uses <code>pqcrypto-dilithium</code>
(C-based CRYSTALS-Dilithium) which is byte-incompatible with FIPS 204 ML-DSA.
Both sides need to use the same pure Rust implementation before
cross-verification works. Ed25519 verification is sufficient — every tessera is
Ed25519-signed.</p>
<p>All four functions use a two-layer pattern for testability: inner functions
return <code>Result&lt;T, String&gt;</code> and are tested natively, while thin <code>#[wasm_bindgen]</code>
wrappers convert errors to <code>JsError</code>. This avoids <code>JsError::new()</code> panicking on
non-WASM targets during testing.</p>
<p>The compiled WASM binary is 109 KB raw and 44 KB gzipped — well under the 200 KB
budget. wasm-opt applies <code>-Oz</code> optimization after wasm-pack builds with
<code>opt-level = "z"</code>, LTO, and single codegen unit.</p>
<p><strong>@tesseras/verify</strong> — A TypeScript npm package (<code>crates/tesseras-wasm/js/</code>)
that orchestrates browser-side verification. The public API is a single
function:</p>
<pre><code data-lang="typescript">async function verifyTessera(
  archive: Uint8Array,
  onProgress?: (current: number, total: number, file: string) =&gt; void
): Promise&lt;VerificationResult&gt;
</code></pre>
<p>The <code>VerificationResult</code> type provides everything a UI needs: overall validity,
tessera hash, creator public keys, signature status (valid/invalid/missing for
both Ed25519 and ML-DSA), per-file integrity results with expected and actual
hashes, a list of unexpected files not in the MANIFEST, and an errors array.</p>
<p>Archive unpacking (<code>unpack.ts</code>) handles three formats: gzip-compressed tar
(detected by <code>\x1f\x8b</code> magic bytes, decompressed with fflate then parsed as
tar), ZIP (<code>PK\x03\x04</code> magic, unpacked with fflate's <code>unzipSync</code>), and raw tar
(<code>ustar</code> at offset 257). A <code>normalizePath</code> function strips the leading
<code>tessera-&lt;hash&gt;/</code> prefix so internal paths match MANIFEST entries.</p>
<p>Verification runs in a Web Worker (<code>worker.ts</code>) to keep the UI thread
responsive. The worker initializes the WASM module, unpacks the archive, parses
the MANIFEST, verifies the Ed25519 signature against the creator's public key,
then hashes each file with BLAKE3 and compares against expected values. Progress
messages stream back to the main thread after each file. If any signature is
invalid, verification stops early without hashing files — failing fast on the
most critical check.</p>
<p>The archive is transferred to the worker with zero-copy
(<code>worker.postMessage({ type: "verify", archive }, [archive.buffer])</code>) to avoid
duplicating potentially large tessera files in memory.</p>
<p><strong>Build pipeline</strong> — Three new justfile targets: <code>wasm-build</code> runs wasm-pack
with <code>--target web --release</code> and optimizes with wasm-opt; <code>wasm-size</code> reports
raw and gzipped binary size; <code>test-wasm</code> runs the native test suite.</p>
<p><strong>Tests</strong> — 9 native unit tests cover BLAKE3 hashing (empty input, known value),
Ed25519 verification (valid signature, invalid signature, wrong key, bad key
length), and MANIFEST parsing (valid manifest, invalid UTF-8, garbage input). 3
WASM integration tests run in headless Chrome via
<code>wasm-pack test --headless --chrome</code>, verifying that <code>hash_blake3</code>,
<code>verify_ed25519</code>, and <code>parse_manifest</code> work correctly when compiled to
<code>wasm32-unknown-unknown</code>.</p>
<h2 id="architecture-decisions">Architecture decisions</h2>
<ul>
<li><strong>No tesseras-crypto dependency</strong>: the WASM crate calls blake3 and
ed25519-dalek directly. <code>tesseras-crypto</code> depends on <code>pqcrypto-kyber</code> (C-based
ML-KEM via pqcrypto-traits) which requires a C compiler toolchain and doesn't
target wasm32. By depending only on pure Rust crates, the WASM build has zero
C dependencies and compiles cleanly to WebAssembly.</li>
<li><strong>ML-DSA deferred, not faked</strong>: rather than silently skipping post-quantum
verification, the stub returns an explicit error. This ensures that if a
tessera contains an ML-DSA signature, the verification result will report
<code>ml_dsa: "missing"</code> rather than pretending it was checked. The JS orchestrator
handles this gracefully — a tessera is valid if Ed25519 passes and ML-DSA is
missing (not yet implemented on either side).</li>
<li><strong>Inner function pattern</strong>: <code>JsError</code> cannot be constructed on non-WASM
targets (it panics). Splitting each function into
<code>foo_inner() -&gt; Result&lt;T, String&gt;</code> and <code>foo() -&gt; Result&lt;T, JsError&gt;</code> lets the
native test suite exercise all logic without touching JavaScript types. The
WASM integration tests in headless Chrome test the full <code>#[wasm_bindgen]</code>
surface.</li>
<li><strong>Web Worker isolation</strong>: cryptographic operations (especially BLAKE3 over
large media files) can take hundreds of milliseconds. Running in a Worker
prevents UI jank. The streaming progress protocol
(<code>{ type: "progress", current, total, file }</code>) lets the UI show a progress bar
during verification of tesseras with many files.</li>
<li><strong>Zero-copy transfer</strong>: <code>archive.buffer</code> is transferred to the Worker, not
copied. For a 50 MB tessera archive, this avoids doubling memory usage during
verification.</li>
<li><strong>Plain text MANIFEST, not MessagePack</strong>: the WASM crate parses the same
plain-text MANIFEST format as the CLI. This is by design — the MANIFEST is the
tessera's Rosetta Stone, readable by anyone with a text editor. The
<code>rmp-serde</code> dependency in the Cargo.toml is not used and will be removed.</li>
</ul>
<h2 id="what-comes-next">What comes next</h2>
<ul>
<li><strong>Phase 4: Resilience and Scale</strong> — OS packaging (Alpine, Arch, Debian,
FreeBSD, OpenBSD), CI on SourceHut and GitHub Actions, security audits,
browser-based tessera explorer at tesseras.net using @tesseras/verify</li>
<li><strong>Phase 5: Exploration and Culture</strong> — Public tessera browser by
era/location/theme/language, institutional curation, genealogy integration,
physical media export (M-DISC, microfilm, acid-free paper with QR)</li>
</ul>
<p>Verification no longer requires trust in software. A tessera archive dropped
into a browser is verified with the same cryptographic rigor as the CLI — same
BLAKE3 hashes, same Ed25519 signatures, same MANIFEST parser. The difference is
that now anyone can do it.</p>

</article>

    </main>

    <footer>
        <p>&copy; 2026 Tesseras Project. <a href="/atom.xml">News Feed</a> · <a href="https://git.sr.ht/~ijanc/tesseras">Source</a></p>
    </footer>
</body>
</html>