/**
* Local trust store: trust-on-first-use bindings of display name → identity
* key, plus explicitly verified identities. Lives only in this browser.
*
* - TOFU: the first identity key seen for a display name is remembered; if the
* same name later shows up with a DIFFERENT key, the UI warns loudly.
* - Verified: after comparing fingerprints out-of-band, you mark an identity
* verified; the checkmark follows the KEY (not the name) from then on.
*/
export interface TrustVerdict {
verified: boolean;
/** Set when this display name was previously seen with a different key. */
keyChanged: boolean;
}
interface TrustState {
names: Record<string, string>; // lowercased display name → publicKeyB64 (TOFU)
verified: Record<string, string>; // publicKeyB64 → name it was verified as
}
const KEY = "sueta:trust";
function load(): TrustState {
try {
const s = JSON.parse(localStorage.getItem(KEY) ?? "{}");
return { names: s.names ?? {}, verified: s.verified ?? {} };
} catch {
return { names: {}, verified: {} };
}
}
function persist(s: TrustState): void {
try {
localStorage.setItem(KEY, JSON.stringify(s));
} catch {}
}
/** Record a sighting of (name, key); returns the verdict to render. */
export function observe(name: string, publicKeyB64: string): TrustVerdict {
const s = load();
const n = name.trim().toLowerCase();
const known = s.names[n];
const keyChanged = !!known && known !== publicKeyB64;
if (!known) {
s.names[n] = publicKeyB64; // trust on first use
persist(s);
}
return { verified: publicKeyB64 in s.verified, keyChanged };
}
export function setVerified(publicKeyB64: string, name: string, on: boolean): void {
const s = load();
if (on) {
s.verified[publicKeyB64] = name;
// verifying also (re)binds the name to this key, clearing stale TOFU warnings
s.names[name.trim().toLowerCase()] = publicKeyB64;
} else {
delete s.verified[publicKeyB64];
}
persist(s);
}
export function isVerified(publicKeyB64: string): boolean {
return publicKeyB64 in load().verified;
}