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
var __freenet_user_token = (function () {
'use strict';
// REFUSE-PLAINTEXT-TOKEN (client mirror of the backend invariant, #4381):
// never load OR mint the durable token on a non-secure page. The token is a
// high-value, node-independent bearer secret; the backend won't HONOR it over
// plaintext, but only the client can stop it from being TRANSMITTED in the
// first place. Returning undefined here (BEFORE any localStorage access) means
// an http page never reads a previously-minted token either, so the stored
// value is never put on a ws:// URL. The TLS proxy serves the shell over
// https in the intended hosted deployment; a direct http://localhost dev hit
// simply gets no token (Local), which is the correct/expected behavior.
if (location.protocol !== 'https:') {
return undefined;
}
// Minimal, self-contained Base58 encoder (Bitcoin/bs58 alphabet). There is no
// bs58 in the browser and we deliberately avoid pulling in an external JS
// dependency, so this inlines the standard big-endian base-256 -> base-58 long
// division. `bytes` is a Uint8Array (here always the 32 random token bytes);
// the return value is the base58 string.
//
// The server treats the token as an OPAQUE namespace key — it hashes the raw
// string bytes (blake3(domain || token), see UserSecretContext::from_token) —
// so the ONLY hard requirement is a stable, valid encoding. Base58 is chosen
// over hex because it drops the visually ambiguous characters, which makes an
// "access key" that the user copies, pastes, or transcribes by hand far less
// error-prone, and it matches the bs58 (Bitcoin alphabet) used by the rest of
// Freenet's identifiers. This encoder is byte-for-byte compatible with the
// `bs58` crate's BITCOIN alphabet output.
//
// The BEGIN/END markers below let base58_encode.test.mjs extract this exact
// function and unit-test its output against known bs58 vectors + edge cases
// (this repo has no browser JS test runner, and the shell page's browser-only
// globals make the surrounding IIFE non-executable under Node). Keep the
// function self-contained (no free variables) so the extraction stays valid.
// base58-encoder:BEGIN
function base58Encode(bytes) {
var ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
// Leading zero bytes carry no numeric value but must survive as leading
// '1's (base58's zero digit), so count and skip them before the division.
var zeros = 0;
while (zeros < bytes.length && bytes[zeros] === 0) {
zeros++;
}
// Treat the remaining bytes as a big-endian base-256 integer and convert it
// to base-58, accumulating the base-58 digits little-endian via repeated
// long division. Intermediate `carry` stays small (< 58 * 256 + 255), well
// within a 32-bit int, so `<< 8` and `| 0` are safe.
var digits = [0];
for (var i = zeros; i < bytes.length; i++) {
var carry = bytes[i];
for (var j = 0; j < digits.length; j++) {
carry += digits[j] << 8;
digits[j] = carry % 58;
carry = (carry / 58) | 0;
}
while (carry > 0) {
digits.push(carry % 58);
carry = (carry / 58) | 0;
}
}
var out = '';
for (var z = 0; z < zeros; z++) {
out += ALPHABET[0];
}
// Emit base-58 digits most-significant first. Skip a lone most-significant
// zero digit, which only appears for an all-zero input (already fully
// represented by the leading '1's above); for any non-zero input the top
// digit is non-zero.
var top = digits.length - 1;
while (top > 0 && digits[top] === 0) {
top--;
}
if (!(top === 0 && digits[0] === 0)) {
for (var k = top; k >= 0; k--) {
out += ALPHABET[digits[k]];
}
}
return out;
}
// base58-encoder:END
try {
var KEY = '__freenet_user_token__';
var t = localStorage.getItem(KEY);
if (!t) {
var b = new Uint8Array(32);
crypto.getRandomValues(b);
// New identities mint a base58 access key. Existing users keep whatever
// string is already in localStorage verbatim (older builds stored hex),
// and the "restore" flow in hosted_bar.js stores any pasted key as-is, so
// both formats keep working: the server hashes the raw token string either
// way, so a previously stored hex token maps to the same per-user
// namespace as before and old data stays reachable.
t = base58Encode(b);
localStorage.setItem(KEY, t);
}
return t;
} catch (e) {
return undefined;
}
})();