[tbb-commits] [Git][tpo/applications/tor-browser][tor-browser-115.7.0esr-13.5-1] 3 commits: Temporary commit: manually place generated wasm files

Pier Angelo Vendrame (@pierov) git at gitlab.torproject.org
Fri Jan 26 16:27:37 UTC 2024



Pier Angelo Vendrame pushed to branch tor-browser-115.7.0esr-13.5-1 at The Tor Project / Applications / Tor Browser


Commits:
ebfac0ab by Cecylia Bocovich at 2024-01-26T16:27:01+00:00
Temporary commit: manually place generated wasm files

These files are built reproducibly using tor-browser-build: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/merge_requests/715

We're manually adding them here while working on the interface, but
eventually these should be placed in the right location using
tor-browser-build.

- - - - -
10d5eb0b by Cecylia Bocovich at 2024-01-26T16:27:01+00:00
Lox integration

- - - - -
478ddecb by Cecylia Bocovich at 2024-01-26T16:27:01+00:00
fixup! Bug 40597: Implement TorSettings module

Implement Lox backend.

- - - - -


9 changed files:

- dom/security/nsContentSecurityUtils.cpp
- + toolkit/components/lox/Lox.sys.mjs
- + toolkit/components/lox/content/lox_wasm_bg.wasm
- + toolkit/components/lox/jar.mn
- + toolkit/components/lox/lox_wasm.jsm
- + toolkit/components/lox/moz.build
- toolkit/components/moz.build
- toolkit/components/tor-launcher/TorStartupService.sys.mjs
- toolkit/modules/TorSettings.sys.mjs


Changes:

=====================================
dom/security/nsContentSecurityUtils.cpp
=====================================
@@ -618,6 +618,9 @@ bool nsContentSecurityUtils::IsEvalAllowed(JSContext* cx,
 
       // The Browser Toolbox/Console
       "debugger"_ns,
+
+      // Tor Browser's Lox wasm integration
+      "resource://gre/modules/lox_wasm.jsm"_ns,
   };
 
   // We also permit two specific idioms in eval()-like contexts. We'd like to


=====================================
toolkit/components/lox/Lox.sys.mjs
=====================================
@@ -0,0 +1,801 @@
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+import {
+  clearInterval,
+  setInterval,
+} from "resource://gre/modules/Timer.sys.mjs";
+
+const lazy = {};
+ChromeUtils.defineESModuleGetters(lazy, {
+  DomainFrontRequestBuilder:
+    "resource://gre/modules/DomainFrontedRequests.sys.mjs",
+  TorConnect: "resource://gre/modules/TorConnect.sys.mjs",
+  TorConnectState: "resource://gre/modules/TorConnect.sys.mjs",
+  TorSettings: "resource://gre/modules/TorSettings.sys.mjs",
+  TorSettingsTopics: "resource://gre/modules/TorSettings.sys.mjs",
+  TorBridgeSource: "resource://gre/modules/TorSettings.sys.mjs",
+});
+XPCOMUtils.defineLazyModuleGetters(lazy, {
+  init: "resource://gre/modules/lox_wasm.jsm",
+  open_invite: "resource://gre/modules/lox_wasm.jsm",
+  handle_new_lox_credential: "resource://gre/modules/lox_wasm.jsm",
+  set_panic_hook: "resource://gre/modules/lox_wasm.jsm",
+  invitation_is_trusted: "resource://gre/modules/lox_wasm.jsm",
+  issue_invite: "resource://gre/modules/lox_wasm.jsm",
+  prepare_invite: "resource://gre/modules/lox_wasm.jsm",
+  get_invites_remaining: "resource://gre/modules/lox_wasm.jsm",
+  get_trust_level: "resource://gre/modules/lox_wasm.jsm",
+  level_up: "resource://gre/modules/lox_wasm.jsm",
+  handle_level_up: "resource://gre/modules/lox_wasm.jsm",
+  trust_promotion: "resource://gre/modules/lox_wasm.jsm",
+  handle_trust_promotion: "resource://gre/modules/lox_wasm.jsm",
+  trust_migration: "resource://gre/modules/lox_wasm.jsm",
+  handle_trust_migration: "resource://gre/modules/lox_wasm.jsm",
+  get_next_unlock: "resource://gre/modules/lox_wasm.jsm",
+  check_blockage: "resource://gre/modules/lox_wasm.jsm",
+  handle_check_blockage: "resource://gre/modules/lox_wasm.jsm",
+  blockage_migration: "resource://gre/modules/lox_wasm.jsm",
+  handle_blockage_migration: "resource://gre/modules/lox_wasm.jsm",
+});
+
+export const LoxErrors = Object.freeze({
+  BadInvite: "BadInvite",
+  MissingCredential: "MissingCredential",
+  LoxServerUnreachable: "LoxServerUnreachable",
+  NoInvitations: "NoInvitations",
+  InitError: "InitializationError",
+  NotInitialized: "NotInitialized",
+});
+
+const LoxSettingsPrefs = Object.freeze({
+  /* string: the lox credential */
+  credentials: "lox.settings.credentials",
+  invites: "lox.settings.invites",
+  events: "lox.settings.events",
+  pubkeys: "lox.settings.pubkeys",
+  enctable: "lox.settings.enctable",
+  constants: "lox.settings.constants",
+});
+
+class LoxError extends Error {
+  constructor(type) {
+    super("");
+    this.type = type;
+  }
+}
+
+class LoxImpl {
+  #initialized = false;
+  #window = null;
+  #pubKeyPromise = null;
+  #encTablePromise = null;
+  #constantsPromise = null;
+  #domainFrontedRequests = null;
+  #invites = null;
+  #pubKeys = null;
+  #encTable = null;
+  #constants = null;
+  #credentials = null;
+  #events = [];
+  #backgroundInterval = null;
+
+  observe(subject, topic, data) {
+    switch (topic) {
+      case lazy.TorSettingsTopics.SettingsChanged:
+        const { changes } = subject.wrappedJSObject;
+        if (
+          changes.includes("bridges.enabled") ||
+          changes.includes("bridges.source") ||
+          changes.includes("bridges.lox_id")
+        ) {
+          // if lox_id has changed, clear event and invite queues
+          if (changes.includes("bridges.lox_id")) {
+            this.clearEventData();
+            this.clearInvites();
+          }
+
+          // Only run background tasks if Lox is enabled
+          if (this.#inuse) {
+            if (!this.#backgroundInterval) {
+              this.#backgroundInterval = setInterval(
+                this.#backgroundTasks.bind(this),
+                1000 * 60 * 60 * 12
+              );
+            }
+          } else if (this.#backgroundInterval) {
+            clearInterval(this.#backgroundInterval);
+            this.#backgroundInterval = null;
+          }
+        }
+        break;
+      case lazy.TorSettingsTopics.Ready:
+        // Run background tasks every 12 hours if Lox is enabled
+        if (this.#inuse) {
+          this.#backgroundInterval = setInterval(
+            this.#backgroundTasks.bind(this),
+            1000 * 60 * 60 * 12
+          );
+        }
+        break;
+    }
+  }
+
+  get #inuse() {
+    return (
+      lazy.TorSettings.bridges.enabled === true &&
+      lazy.TorSettings.bridges.source === lazy.TorBridgeSource.Lox &&
+      lazy.TorSettings.bridges.lox_id
+    );
+  }
+
+  /**
+   * Formats and returns bridges from the stored Lox credential
+   *
+   * @param {string} loxid The id string associated with a lox credential
+   *
+   * @returns {string[]} An array of formatted bridge lines. The array is empty
+   * if there are no bridges.
+   */
+  getBridges(loxid) {
+    if (!this.#initialized) {
+      throw new LoxError(LoxErrors.NotInitialized);
+    }
+    if (loxid === null) {
+      return [];
+    }
+    if (!this.#credentials[loxid]) {
+      // This lox id doesn't correspond to a stored credential
+      throw new LoxError(LoxErrors.MissingCredential);
+    }
+    // Note: this is messy now but can be mostly removed after we have
+    // https://gitlab.torproject.org/tpo/anti-censorship/lox/-/issues/46
+    let bridgelines = JSON.parse(this.#credentials[loxid]).bridgelines;
+    let bridges = [];
+    for (const bridge of bridgelines) {
+      let addr = bridge.addr;
+      while (addr[addr.length - 1] === 0) {
+        addr.pop();
+      }
+      addr = new Uint8Array(addr);
+      let decoder = new TextDecoder("utf-8");
+      addr = decoder.decode(addr);
+
+      let info = bridge.info;
+      while (info[info.length - 1] === 0) {
+        info.pop();
+      }
+      info = new Uint8Array(info);
+      info = decoder.decode(info);
+
+      let regexpTransport = /type=([a-zA-Z0-9]*)/;
+      let transport = info.match(regexpTransport);
+      if (transport !== null) {
+        transport = transport[1];
+      } else {
+        transport = "";
+      }
+
+      let regexpFingerprint = /fingerprint=\"([a-zA-Z0-9]*)\"/;
+      let fingerprint = info.match(regexpFingerprint);
+      if (fingerprint !== null) {
+        fingerprint = fingerprint[1];
+      } else {
+        fingerprint = "";
+      }
+
+      let regexpParams = /params=Some\(\{(.*)\}\)/;
+      let params = info.match(regexpParams);
+      if (params !== null) {
+        params = params[1]
+          .replaceAll('"', "")
+          .replaceAll(": ", "=")
+          .replaceAll(",", " ");
+      } else {
+        params = "";
+      }
+
+      bridges.push(
+        `${transport} ${addr}:${bridge.port} ${fingerprint} ${params}`
+      );
+    }
+    return bridges;
+  }
+
+  #store() {
+    Services.prefs.setStringPref(LoxSettingsPrefs.pubkeys, this.#pubKeys);
+    Services.prefs.setStringPref(LoxSettingsPrefs.enctable, this.#encTable);
+    Services.prefs.setStringPref(LoxSettingsPrefs.constants, this.#constants);
+    Services.prefs.setStringPref(
+      LoxSettingsPrefs.credentials,
+      JSON.stringify(this.#credentials)
+    );
+    Services.prefs.setStringPref(
+      LoxSettingsPrefs.invites,
+      JSON.stringify(this.#invites)
+    );
+    Services.prefs.setStringPref(
+      LoxSettingsPrefs.events,
+      JSON.stringify(this.#events)
+    );
+  }
+
+  #load() {
+    if (this.#credentials === null) {
+      let cred = Services.prefs.getStringPref(LoxSettingsPrefs.credentials, "");
+      this.#credentials = cred !== "" ? JSON.parse(cred) : {};
+      let invites = Services.prefs.getStringPref(LoxSettingsPrefs.invites, "");
+      if (invites !== "") {
+        this.#invites = JSON.parse(invites);
+      }
+      let events = Services.prefs.getStringPref(LoxSettingsPrefs.events, "");
+      if (events !== "") {
+        this.#events = JSON.parse(events);
+      }
+    }
+    this.#pubKeys = Services.prefs.getStringPref(
+      LoxSettingsPrefs.pubkeys,
+      null
+    );
+    this.#encTable = Services.prefs.getStringPref(
+      LoxSettingsPrefs.enctable,
+      null
+    );
+    this.#constants = Services.prefs.getStringPref(
+      LoxSettingsPrefs.constants,
+      null
+    );
+  }
+
+  async #getPubKeys() {
+    if (this.#pubKeyPromise === null) {
+      this.#pubKeyPromise = this.#makeRequest("pubkeys", [])
+        .then(pubKeys => {
+          this.#pubKeys = JSON.stringify(pubKeys);
+        })
+        .catch(() => {
+          // We always try to update, but if that doesn't work fall back to stored data
+          if (!this.#pubKeys) {
+            throw new LoxError(LoxErrors.LoxServerUnreachable);
+          }
+        });
+    }
+    await this.#pubKeyPromise;
+  }
+
+  async #getEncTable() {
+    if (this.#encTablePromise === null) {
+      this.#encTablePromise = this.#makeRequest("reachability", [])
+        .then(encTable => {
+          this.#encTable = JSON.stringify(encTable);
+        })
+        .catch(() => {
+          // Try to update first, but if that doesn't work fall back to stored data
+          if (!this.#encTable) {
+            throw new LoxError(LoxErrors.LoxServerUnreachable);
+          }
+        });
+    }
+    await this.#encTablePromise;
+  }
+
+  async #getConstants() {
+    if (this.#constantsPromise === null) {
+      // Try to update first, but if that doesn't work fall back to stored data
+      this.#constantsPromise = this.#makeRequest("constants", [])
+        .then(constants => {
+          this.#constants = JSON.stringify(constants);
+        })
+        .catch(() => {
+          if (!this.#constants) {
+            throw new LoxError(LoxErrors.LoxServerUnreachable);
+          }
+        });
+    }
+    await this.#constantsPromise;
+  }
+
+  /**
+   * Check for blockages and attempt to perform a levelup
+   *
+   * If either blockages or a levelup happened, add an event to the event queue
+   */
+  async #backgroundTasks() {
+    if (!this.#initialized) {
+      throw new LoxError(LoxErrors.NotInitialized);
+    }
+    const loxid = lazy.TorSettings.bridges.lox_id;
+    try {
+      const levelup = await this.#attemptUpgrade(loxid);
+      if (levelup) {
+        const level = lazy.get_trust_level(this.#credentials[loxid]);
+        const newEvent = {
+          type: "levelup",
+          newlevel: level,
+        };
+        this.#events.push(newEvent);
+        this.#store();
+      }
+    } catch (err) {
+      console.log(err);
+    }
+    try {
+      const leveldown = await this.#blockageMigration(loxid);
+      if (leveldown) {
+        let level = lazy.get_trust_level(this.#credentials[loxid]);
+        const newEvent = {
+          type: "blockage",
+          newlevel: level,
+        };
+        this.#events.push(newEvent);
+        this.#store();
+      }
+    } catch (err) {
+      console.log(err);
+    }
+  }
+
+  /**
+   * Generates a new random lox id to be associated with an invitation/credential
+   */
+  #genLoxId() {
+    return crypto.randomUUID();
+  }
+
+  async init() {
+    // If lox_id is set, load it
+    Services.obs.addObserver(this, lazy.TorSettingsTopics.SettingsChanged);
+    Services.obs.addObserver(this, lazy.TorSettingsTopics.Ready);
+
+    // Hack to make the generated wasm happy
+    this.#window = {
+      crypto,
+    };
+    this.#window.window = this.#window;
+    await lazy.init(this.#window);
+    lazy.set_panic_hook();
+    if (typeof lazy.open_invite !== "function") {
+      throw new LoxError(LoxErrors.InitError);
+    }
+    this.#invites = [];
+    this.#events = [];
+    this.#load();
+    this.#initialized = true;
+  }
+
+  async uninit() {
+    Services.obs.removeObserver(this, lazy.TorSettingsTopics.SettingsChanged);
+    Services.obs.removeObserver(this, lazy.TorSettingsTopics.Ready);
+    if (this.#domainFrontedRequests !== null) {
+      try {
+        const domainFronting = await this.#domainFrontedRequests;
+        domainFronting.uninit();
+      } catch {}
+      this.#domainFrontedRequests = null;
+    }
+    this.#initialized = false;
+    this.#window = null;
+    this.#invites = null;
+    this.#pubKeys = null;
+    this.#encTable = null;
+    this.#constants = null;
+    this.#pubKeyPromise = null;
+    this.#encTablePromise = null;
+    this.#constantsPromise = null;
+    this.#credentials = null;
+    this.#events = [];
+    if (this.#backgroundInterval) {
+      clearInterval(this.#backgroundInterval);
+    }
+    this.#backgroundInterval = null;
+  }
+
+  /**
+   * Parses an input string to check if it is a valid Lox invitation
+   *
+   * @param {string} invite A Lox invitation
+   * @returns {Promise<bool>} A promise that resolves to true if the value passed
+   * in was a Lox invitation and false if it is not
+   */
+  async validateInvitation(invite) {
+    if (!this.#initialized) {
+      throw new LoxError(LoxErrors.NotInitialized);
+    }
+    try {
+      await lazy.invitation_is_trusted(invite);
+    } catch (err) {
+      console.log(err);
+      return false;
+    }
+    return true;
+  }
+
+  // Note: This is only here for testing purposes. We're going to be using telegram
+  // to issue open invitations for Lox bridges.
+  async requestOpenInvite() {
+    if (!this.#initialized) {
+      throw new LoxError(LoxErrors.NotInitialized);
+    }
+    let invite = await this.#makeRequest("invite", []);
+    console.log(invite);
+    return invite;
+  }
+
+  /**
+   * Redeems a Lox invitation to obtain a credential and bridges
+   *
+   * @param {string} invite A Lox invitation
+   * @returns {Promise<string>} A promise with the loxid of the associated credential on success
+   */
+  async redeemInvite(invite) {
+    if (!this.#initialized) {
+      throw new LoxError(LoxErrors.NotInitialized);
+    }
+    await this.#getPubKeys();
+    let request = await lazy.open_invite(invite.invite);
+    let id = this.#genLoxId();
+    let response = await this.#makeRequest(
+      "openreq",
+      JSON.parse(request).request
+    );
+    console.log("openreq response: ", response);
+    if (response.hasOwnProperty("error")) {
+      throw new LoxError(LoxErrors.BadInvite);
+    }
+    let cred = lazy.handle_new_lox_credential(
+      request,
+      JSON.stringify(response),
+      this.#pubKeys
+    );
+    this.#credentials[id] = cred;
+    this.#store();
+    return id;
+  }
+
+  /**
+   * Get metadata on all invites historically generated by this credential
+   *
+   * @returns {object[]} A list of all historical invites
+   */
+  getInvites() {
+    if (!this.#initialized) {
+      throw new LoxError(LoxErrors.NotInitialized);
+    }
+    return this.#invites;
+  }
+
+  /**
+   * Generates a new trusted Lox invitation that a user can pass to their contacts
+   *
+   * @returns {Promise<string>} A promise that resolves to a valid Lox invitation. The promise
+   *    will reject if:
+   *      - there is no saved Lox credential
+   *      - the saved credential does not have any invitations available
+   */
+  async generateInvite() {
+    if (!this.#initialized) {
+      throw new LoxError(LoxErrors.NotInitialized);
+    }
+    const loxid = lazy.TorSettings.bridges.lox_id;
+    if (!loxid || !this.#credentials[loxid]) {
+      throw new LoxError(LoxErrors.MissingCredential);
+    }
+    await this.#getPubKeys();
+    await this.#getEncTable();
+    let level = lazy.get_trust_level(this.#credentials[loxid]);
+    if (level < 1) {
+      throw new LoxError(LoxErrors.NoInvitations);
+    }
+    let request = lazy.issue_invite(
+      JSON.stringify(this.#credentials[loxid]),
+      this.#encTable,
+      this.#pubKeys
+    );
+    let response;
+    try {
+      response = await this.#makeRequest(
+        "issueinvite",
+        JSON.parse(request).request
+      );
+    } catch {
+      throw new LoxError(LoxErrors.LoxServerUnreachable);
+    }
+    if (response.hasOwnProperty("error")) {
+      console.log(response.error);
+      throw new LoxError(LoxErrors.NoInvitations);
+    } else {
+      this.#credentials[loxid] = response;
+      const invite = lazy.prepare_invite(response);
+      this.#invites.push(invite);
+      // cap length of stored invites
+      if (this.#invites.len > 50) {
+        this.#invites.shift();
+      }
+      return invite;
+    }
+  }
+
+  /**
+   * Get the number of invites that a user has remaining
+   *
+   * @returns {Promise<int>} A promise with the number of invites that can still be generated
+   *    by a user's credential. This promise will reject if:
+   *        - There is no credential
+   */
+  getRemainingInviteCount() {
+    if (!this.#initialized) {
+      throw new LoxError(LoxErrors.NotInitialized);
+    }
+    const loxid = lazy.TorSettings.bridges.lox_id;
+    if (!loxid || !this.#credentials[loxid]) {
+      throw new LoxError(LoxErrors.MissingCredential);
+    }
+    return parseInt(lazy.get_invites_remaining(this.#credentials[loxid]));
+  }
+
+  async #blockageMigration(loxid) {
+    if (!loxid || !this.#credentials[loxid]) {
+      throw new LoxError(LoxErrors.MissingCredential);
+    }
+    await this.#getPubKeys();
+    let request;
+    try {
+      request = lazy.check_blockage(this.#credentials[loxid], this.#pubKeys);
+    } catch {
+      console.log("Not ready for blockage migration");
+      return false;
+    }
+    let response = await this.#makeRequest("checkblockage", request);
+    if (response.hasOwnProperty("error")) {
+      console.log(response.error);
+      throw new LoxError(LoxErrors.LoxServerUnreachable);
+    }
+    const migrationCred = lazy.handle_check_blockage(
+      this.#credentials[loxid],
+      JSON.stringify(response)
+    );
+    request = lazy.blockage_migration(
+      this.#credentials[loxid],
+      migrationCred,
+      this.#pubKeys
+    );
+    response = await this.#makeRequest("blockagemigration", request);
+    if (response.hasOwnProperty("error")) {
+      console.log(response.error);
+      throw new LoxError(LoxErrors.LoxServerUnreachable);
+    }
+    const cred = lazy.handle_blockage_migration(
+      this.#credentials[loxid],
+      JSON.stringify(response),
+      this.#pubKeys
+    );
+    this.#credentials[loxid] = cred;
+    this.#store();
+    return true;
+  }
+
+  /** Attempts to upgrade the currently saved Lox credential.
+   *  If an upgrade is available, save an event in the event list.
+   *
+   *  @returns {boolean} whether a levelup event occured
+   */
+  async #attemptUpgrade(loxid) {
+    if (!loxid || !this.#credentials[loxid]) {
+      throw new LoxError(LoxErrors.MissingCredential);
+    }
+    await this.#getPubKeys();
+    await this.#getEncTable();
+    await this.#getConstants();
+    let success = false;
+    let level = lazy.get_trust_level(this.#credentials[loxid]);
+    if (level < 1) {
+      // attempt trust promotion instead
+      try {
+        success = await this.#trustMigration();
+      } catch (err) {
+        console.log(err);
+        return false;
+      }
+    } else {
+      let request = lazy.level_up(
+        this.#credentials[loxid],
+        this.#encTable,
+        this.#pubKeys
+      );
+      const response = await this.#makeRequest("levelup", request);
+      if (response.hasOwnProperty("error")) {
+        console.log(response.error);
+        throw new LoxError(LoxErrors.LoxServerUnreachable);
+      }
+      const cred = lazy.handle_level_up(
+        request,
+        JSON.stringify(response),
+        this.#pubKeys
+      );
+      this.#credentials[loxid] = cred;
+      return true;
+    }
+    return success;
+  }
+
+  /**
+   * Attempt to migrate from an untrusted to a trusted Lox credential
+   *
+   * @returns {Promise<bool>} A bool value indicated whether the credential
+   *    was successfully migrated.
+   */
+  async #trustMigration() {
+    const loxid = lazy.TorSettings.bridges.lox_id;
+    if (!loxid || !this.#credentials[loxid]) {
+      throw new LoxError(LoxErrors.MissingCredential);
+    }
+    await this.#getPubKeys();
+    return new Promise((resolve, reject) => {
+      let request = "";
+      try {
+        request = lazy.trust_promotion(this.#credentials[loxid], this.#pubKeys);
+      } catch (err) {
+        console.log("Not ready to upgrade");
+        resolve(false);
+      }
+      this.#makeRequest("trustpromo", JSON.parse(request).request)
+        .then(response => {
+          if (response.hasOwnProperty("error")) {
+            resolve(false);
+          }
+          console.log("Got promotion cred");
+          console.log(response);
+          console.log(request);
+          let promoCred = lazy.handle_trust_promotion(
+            request,
+            JSON.stringify(response)
+          );
+          console.log("Formatted promotion cred");
+          request = lazy.trust_migration(
+            this.#credentials[loxid],
+            promoCred,
+            this.#pubKeys
+          );
+          console.log("Formatted migration request");
+          this.#makeRequest("trustmig", JSON.parse(request).request)
+            .then(response => {
+              if (response.hasOwnProperty("error")) {
+                resolve(false);
+              }
+              console.log("Got new credential");
+              let cred = lazy.handle_trust_migration(request, response);
+              this.#credentials[loxid] = cred;
+              this.#store();
+              resolve(true);
+            })
+            .catch(err => {
+              console.log(err);
+              console.log("Failed trust migration");
+              resolve(false);
+            });
+        })
+        .catch(err => {
+          console.log(err);
+          console.log("Failed trust promotion");
+          resolve(false);
+        });
+    });
+  }
+
+  /**
+   * @typedef {object} EventData
+   *
+   * @property {string} [type] - the type of event. This should be one of:
+   *   ("levelup", "blockage")
+   * @property {integer} [newlevel] - the new level, after the event. Levels count
+   * from 0, but "blockage" events can never take the user to 0, so this will always
+   * be 1 or greater.
+   */
+
+  /**
+   * Get a list of accumulated events
+   *
+   * @returns {Promise<EventData[]>} A promise with a list of the accumulated,
+   *   unacknowledged events associated with a user's credential. This promise will reject if
+   *        - There is no credential
+   */
+  getEventData() {
+    if (!this.#initialized) {
+      throw new LoxError(LoxErrors.NotInitialized);
+    }
+    const loxid = lazy.TorSettings.bridges.lox_id;
+    if (!loxid || !this.#credentials[loxid]) {
+      throw new LoxError(LoxErrors.MissingCredential);
+    }
+    return this.#events;
+  }
+
+  /**
+   * Clears accumulated event data
+   */
+  clearEventData() {
+    if (!this.#initialized) {
+      throw new LoxError(LoxErrors.NotInitialized);
+    }
+    this.#events = [];
+    this.#store();
+  }
+
+  /**
+   * Clears accumulated invitations
+   */
+  clearInvites() {
+    if (!this.#initialized) {
+      throw new LoxError(LoxErrors.NotInitialized);
+    }
+    this.#invites = [];
+    this.#store();
+  }
+
+  /**
+   * @typedef {object} UnlockData
+   *
+   * @property {string} date - The date-time for the next level up, formatted as YYYY-MM-DDTHH:mm:ssZ.
+   * @property {integer} nextLevel - The next level. Levels count from 0, so this will be 1 or greater.
+   *
+   */
+
+  /**
+   * Get dates at which access to new features will unlock
+   */
+  async getNextUnlock() {
+    if (!this.#initialized) {
+      throw new LoxError(LoxErrors.NotInitialized);
+    }
+    const loxid = lazy.TorSettings.bridges.lox_id;
+    if (!loxid || !this.#credentials[loxid]) {
+      throw new LoxError(LoxErrors.MissingCredential);
+    }
+    await this.#getConstants();
+    let nextUnlocks = JSON.parse(
+      lazy.get_next_unlock(this.#constants, this.#credentials[loxid])
+    );
+    const level = lazy.get_trust_level(this.#credentials[loxid]);
+    const unlocks = {
+      date: nextUnlocks.trust_level_unlock_date,
+      level: level + 1,
+    };
+    return unlocks;
+  }
+
+  async #makeRequest(procedure, args) {
+    // TODO: Customize to for Lox
+    const serviceUrl = "https://rdsys-frontend-01.torproject.org/lox";
+    const url = `${serviceUrl}/${procedure}`;
+
+    if (lazy.TorConnect.state === lazy.TorConnectState.Bootstrapped) {
+      const request = await fetch(url, {
+        method: "POST",
+        headers: {
+          "Content-Type": "application/vnd.api+json",
+        },
+        body: JSON.stringify(args),
+      });
+      return request.json();
+    }
+
+    if (this.#domainFrontedRequests === null) {
+      this.#domainFrontedRequests = new Promise((resolve, reject) => {
+        // TODO: Customize to the values for Lox
+        const reflector = Services.prefs.getStringPref(
+          "extensions.torlauncher.bridgedb_reflector"
+        );
+        const front = Services.prefs.getStringPref(
+          "extensions.torlauncher.bridgedb_front"
+        );
+        const builder = new lazy.DomainFrontRequestBuilder();
+        builder
+          .init(reflector, front)
+          .then(() => resolve(builder))
+          .catch(reject);
+      });
+    }
+    const builder = await this.#domainFrontedRequests;
+    return builder.buildPostRequest(url, args);
+  }
+}
+
+export const Lox = new LoxImpl();


=====================================
toolkit/components/lox/content/lox_wasm_bg.wasm
=====================================
Binary files /dev/null and b/toolkit/components/lox/content/lox_wasm_bg.wasm differ


=====================================
toolkit/components/lox/jar.mn
=====================================
@@ -0,0 +1,2 @@
+toolkit.jar:
+    content/global/lox/lox_wasm_bg.wasm                      (content/lox_wasm_bg.wasm)


=====================================
toolkit/components/lox/lox_wasm.jsm
=====================================
@@ -0,0 +1,1217 @@
+var EXPORTED_SYMBOLS = ["set_panic_hook", "open_invite", "handle_new_lox_credential", "trust_promotion", "handle_trust_promotion", "trust_migration", "handle_trust_migration", "level_up", "handle_level_up", "issue_invite", "handle_issue_invite", "prepare_invite", "redeem_invite", "handle_redeem_invite", "check_blockage", "handle_check_blockage", "blockage_migration", "handle_blockage_migration", "get_last_upgrade_time", "get_trust_level", "get_invites_remaining", "get_issued_invite_expiry", "get_received_invite_expiry", "get_bridgelines_from_bucket", "invitation_is_trusted", "get_next_unlock", "init", "initSync"];
+
+let wasm;
+let module;
+
+const heap = new Array(128).fill(undefined);
+
+heap.push(undefined, null, true, false);
+
+function getObject(idx) { return heap[idx]; }
+
+let heap_next = heap.length;
+
+function dropObject(idx) {
+    if (idx < 132) return;
+    heap[idx] = heap_next;
+    heap_next = idx;
+}
+
+function takeObject(idx) {
+    const ret = getObject(idx);
+    dropObject(idx);
+    return ret;
+}
+
+const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
+
+if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
+
+let cachedUint8Memory0 = null;
+
+function getUint8Memory0() {
+    if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) {
+        cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
+    }
+    return cachedUint8Memory0;
+}
+
+function getStringFromWasm0(ptr, len) {
+    ptr = ptr >>> 0;
+    return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
+}
+
+function addHeapObject(obj) {
+    if (heap_next === heap.length) heap.push(heap.length + 1);
+    const idx = heap_next;
+    heap_next = heap[idx];
+
+    heap[idx] = obj;
+    return idx;
+}
+/**
+*/
+function set_panic_hook() {
+    wasm.set_panic_hook();
+}
+
+let WASM_VECTOR_LEN = 0;
+
+function passArray8ToWasm0(arg, malloc) {
+    const ptr = malloc(arg.length * 1, 1) >>> 0;
+    getUint8Memory0().set(arg, ptr / 1);
+    WASM_VECTOR_LEN = arg.length;
+    return ptr;
+}
+
+let cachedInt32Memory0 = null;
+
+function getInt32Memory0() {
+    if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) {
+        cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);
+    }
+    return cachedInt32Memory0;
+}
+/**
+* @param {Uint8Array} invite
+* @returns {string}
+*/
+function open_invite(invite) {
+    let deferred3_0;
+    let deferred3_1;
+    try {
+        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+        const ptr0 = passArray8ToWasm0(invite, wasm.__wbindgen_malloc);
+        const len0 = WASM_VECTOR_LEN;
+        wasm.open_invite(retptr, ptr0, len0);
+        var r0 = getInt32Memory0()[retptr / 4 + 0];
+        var r1 = getInt32Memory0()[retptr / 4 + 1];
+        var r2 = getInt32Memory0()[retptr / 4 + 2];
+        var r3 = getInt32Memory0()[retptr / 4 + 3];
+        var ptr2 = r0;
+        var len2 = r1;
+        if (r3) {
+            ptr2 = 0; len2 = 0;
+            throw takeObject(r2);
+        }
+        deferred3_0 = ptr2;
+        deferred3_1 = len2;
+        return getStringFromWasm0(ptr2, len2);
+    } finally {
+        wasm.__wbindgen_add_to_stack_pointer(16);
+        wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
+    }
+}
+
+const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } );
+
+const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
+    ? function (arg, view) {
+    return cachedTextEncoder.encodeInto(arg, view);
+}
+    : function (arg, view) {
+    const buf = cachedTextEncoder.encode(arg);
+    view.set(buf);
+    return {
+        read: arg.length,
+        written: buf.length
+    };
+});
+
+function passStringToWasm0(arg, malloc, realloc) {
+
+    if (realloc === undefined) {
+        const buf = cachedTextEncoder.encode(arg);
+        const ptr = malloc(buf.length, 1) >>> 0;
+        getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
+        WASM_VECTOR_LEN = buf.length;
+        return ptr;
+    }
+
+    let len = arg.length;
+    let ptr = malloc(len, 1) >>> 0;
+
+    const mem = getUint8Memory0();
+
+    let offset = 0;
+
+    for (; offset < len; offset++) {
+        const code = arg.charCodeAt(offset);
+        if (code > 0x7F) break;
+        mem[ptr + offset] = code;
+    }
+
+    if (offset !== len) {
+        if (offset !== 0) {
+            arg = arg.slice(offset);
+        }
+        ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
+        const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
+        const ret = encodeString(arg, view);
+
+        offset += ret.written;
+    }
+
+    WASM_VECTOR_LEN = offset;
+    return ptr;
+}
+/**
+* @param {string} open_lox_result
+* @param {string} open_lox_response
+* @param {string} lox_pub
+* @returns {string}
+*/
+function handle_new_lox_credential(open_lox_result, open_lox_response, lox_pub) {
+    let deferred5_0;
+    let deferred5_1;
+    try {
+        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+        const ptr0 = passStringToWasm0(open_lox_result, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        const ptr1 = passStringToWasm0(open_lox_response, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len1 = WASM_VECTOR_LEN;
+        const ptr2 = passStringToWasm0(lox_pub, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len2 = WASM_VECTOR_LEN;
+        wasm.handle_new_lox_credential(retptr, ptr0, len0, ptr1, len1, ptr2, len2);
+        var r0 = getInt32Memory0()[retptr / 4 + 0];
+        var r1 = getInt32Memory0()[retptr / 4 + 1];
+        var r2 = getInt32Memory0()[retptr / 4 + 2];
+        var r3 = getInt32Memory0()[retptr / 4 + 3];
+        var ptr4 = r0;
+        var len4 = r1;
+        if (r3) {
+            ptr4 = 0; len4 = 0;
+            throw takeObject(r2);
+        }
+        deferred5_0 = ptr4;
+        deferred5_1 = len4;
+        return getStringFromWasm0(ptr4, len4);
+    } finally {
+        wasm.__wbindgen_add_to_stack_pointer(16);
+        wasm.__wbindgen_free(deferred5_0, deferred5_1, 1);
+    }
+}
+
+/**
+* @param {string} open_lox_cred
+* @param {string} lox_pub
+* @returns {string}
+*/
+function trust_promotion(open_lox_cred, lox_pub) {
+    let deferred4_0;
+    let deferred4_1;
+    try {
+        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+        const ptr0 = passStringToWasm0(open_lox_cred, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        const ptr1 = passStringToWasm0(lox_pub, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len1 = WASM_VECTOR_LEN;
+        wasm.trust_promotion(retptr, ptr0, len0, ptr1, len1);
+        var r0 = getInt32Memory0()[retptr / 4 + 0];
+        var r1 = getInt32Memory0()[retptr / 4 + 1];
+        var r2 = getInt32Memory0()[retptr / 4 + 2];
+        var r3 = getInt32Memory0()[retptr / 4 + 3];
+        var ptr3 = r0;
+        var len3 = r1;
+        if (r3) {
+            ptr3 = 0; len3 = 0;
+            throw takeObject(r2);
+        }
+        deferred4_0 = ptr3;
+        deferred4_1 = len3;
+        return getStringFromWasm0(ptr3, len3);
+    } finally {
+        wasm.__wbindgen_add_to_stack_pointer(16);
+        wasm.__wbindgen_free(deferred4_0, deferred4_1, 1);
+    }
+}
+
+/**
+* @param {string} trust_promo_request
+* @param {string} trust_promo_response
+* @returns {string}
+*/
+function handle_trust_promotion(trust_promo_request, trust_promo_response) {
+    let deferred4_0;
+    let deferred4_1;
+    try {
+        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+        const ptr0 = passStringToWasm0(trust_promo_request, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        const ptr1 = passStringToWasm0(trust_promo_response, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len1 = WASM_VECTOR_LEN;
+        wasm.handle_trust_promotion(retptr, ptr0, len0, ptr1, len1);
+        var r0 = getInt32Memory0()[retptr / 4 + 0];
+        var r1 = getInt32Memory0()[retptr / 4 + 1];
+        var r2 = getInt32Memory0()[retptr / 4 + 2];
+        var r3 = getInt32Memory0()[retptr / 4 + 3];
+        var ptr3 = r0;
+        var len3 = r1;
+        if (r3) {
+            ptr3 = 0; len3 = 0;
+            throw takeObject(r2);
+        }
+        deferred4_0 = ptr3;
+        deferred4_1 = len3;
+        return getStringFromWasm0(ptr3, len3);
+    } finally {
+        wasm.__wbindgen_add_to_stack_pointer(16);
+        wasm.__wbindgen_free(deferred4_0, deferred4_1, 1);
+    }
+}
+
+/**
+* @param {string} open_lox_cred
+* @param {string} trust_promo_cred
+* @param {string} lox_pub
+* @returns {string}
+*/
+function trust_migration(open_lox_cred, trust_promo_cred, lox_pub) {
+    let deferred5_0;
+    let deferred5_1;
+    try {
+        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+        const ptr0 = passStringToWasm0(open_lox_cred, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        const ptr1 = passStringToWasm0(trust_promo_cred, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len1 = WASM_VECTOR_LEN;
+        const ptr2 = passStringToWasm0(lox_pub, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len2 = WASM_VECTOR_LEN;
+        wasm.trust_migration(retptr, ptr0, len0, ptr1, len1, ptr2, len2);
+        var r0 = getInt32Memory0()[retptr / 4 + 0];
+        var r1 = getInt32Memory0()[retptr / 4 + 1];
+        var r2 = getInt32Memory0()[retptr / 4 + 2];
+        var r3 = getInt32Memory0()[retptr / 4 + 3];
+        var ptr4 = r0;
+        var len4 = r1;
+        if (r3) {
+            ptr4 = 0; len4 = 0;
+            throw takeObject(r2);
+        }
+        deferred5_0 = ptr4;
+        deferred5_1 = len4;
+        return getStringFromWasm0(ptr4, len4);
+    } finally {
+        wasm.__wbindgen_add_to_stack_pointer(16);
+        wasm.__wbindgen_free(deferred5_0, deferred5_1, 1);
+    }
+}
+
+/**
+* @param {string} trust_migration_request
+* @param {string} trust_migration_response
+* @param {string} lox_pub
+* @returns {string}
+*/
+function handle_trust_migration(trust_migration_request, trust_migration_response, lox_pub) {
+    let deferred5_0;
+    let deferred5_1;
+    try {
+        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+        const ptr0 = passStringToWasm0(trust_migration_request, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        const ptr1 = passStringToWasm0(trust_migration_response, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len1 = WASM_VECTOR_LEN;
+        const ptr2 = passStringToWasm0(lox_pub, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len2 = WASM_VECTOR_LEN;
+        wasm.handle_trust_migration(retptr, ptr0, len0, ptr1, len1, ptr2, len2);
+        var r0 = getInt32Memory0()[retptr / 4 + 0];
+        var r1 = getInt32Memory0()[retptr / 4 + 1];
+        var r2 = getInt32Memory0()[retptr / 4 + 2];
+        var r3 = getInt32Memory0()[retptr / 4 + 3];
+        var ptr4 = r0;
+        var len4 = r1;
+        if (r3) {
+            ptr4 = 0; len4 = 0;
+            throw takeObject(r2);
+        }
+        deferred5_0 = ptr4;
+        deferred5_1 = len4;
+        return getStringFromWasm0(ptr4, len4);
+    } finally {
+        wasm.__wbindgen_add_to_stack_pointer(16);
+        wasm.__wbindgen_free(deferred5_0, deferred5_1, 1);
+    }
+}
+
+/**
+* @param {string} level_one_cred
+* @param {string} encrypted_table
+* @param {string} lox_pub
+* @returns {string}
+*/
+function level_up(level_one_cred, encrypted_table, lox_pub) {
+    let deferred5_0;
+    let deferred5_1;
+    try {
+        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+        const ptr0 = passStringToWasm0(level_one_cred, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        const ptr1 = passStringToWasm0(encrypted_table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len1 = WASM_VECTOR_LEN;
+        const ptr2 = passStringToWasm0(lox_pub, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len2 = WASM_VECTOR_LEN;
+        wasm.level_up(retptr, ptr0, len0, ptr1, len1, ptr2, len2);
+        var r0 = getInt32Memory0()[retptr / 4 + 0];
+        var r1 = getInt32Memory0()[retptr / 4 + 1];
+        var r2 = getInt32Memory0()[retptr / 4 + 2];
+        var r3 = getInt32Memory0()[retptr / 4 + 3];
+        var ptr4 = r0;
+        var len4 = r1;
+        if (r3) {
+            ptr4 = 0; len4 = 0;
+            throw takeObject(r2);
+        }
+        deferred5_0 = ptr4;
+        deferred5_1 = len4;
+        return getStringFromWasm0(ptr4, len4);
+    } finally {
+        wasm.__wbindgen_add_to_stack_pointer(16);
+        wasm.__wbindgen_free(deferred5_0, deferred5_1, 1);
+    }
+}
+
+/**
+* @param {string} levelup_request
+* @param {string} levelup_response
+* @param {string} lox_pub
+* @returns {string}
+*/
+function handle_level_up(levelup_request, levelup_response, lox_pub) {
+    let deferred5_0;
+    let deferred5_1;
+    try {
+        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+        const ptr0 = passStringToWasm0(levelup_request, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        const ptr1 = passStringToWasm0(levelup_response, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len1 = WASM_VECTOR_LEN;
+        const ptr2 = passStringToWasm0(lox_pub, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len2 = WASM_VECTOR_LEN;
+        wasm.handle_level_up(retptr, ptr0, len0, ptr1, len1, ptr2, len2);
+        var r0 = getInt32Memory0()[retptr / 4 + 0];
+        var r1 = getInt32Memory0()[retptr / 4 + 1];
+        var r2 = getInt32Memory0()[retptr / 4 + 2];
+        var r3 = getInt32Memory0()[retptr / 4 + 3];
+        var ptr4 = r0;
+        var len4 = r1;
+        if (r3) {
+            ptr4 = 0; len4 = 0;
+            throw takeObject(r2);
+        }
+        deferred5_0 = ptr4;
+        deferred5_1 = len4;
+        return getStringFromWasm0(ptr4, len4);
+    } finally {
+        wasm.__wbindgen_add_to_stack_pointer(16);
+        wasm.__wbindgen_free(deferred5_0, deferred5_1, 1);
+    }
+}
+
+/**
+* @param {string} trusted_cred
+* @param {string} encrypted_table
+* @param {string} lox_pub
+* @returns {string}
+*/
+function issue_invite(trusted_cred, encrypted_table, lox_pub) {
+    let deferred5_0;
+    let deferred5_1;
+    try {
+        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+        const ptr0 = passStringToWasm0(trusted_cred, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        const ptr1 = passStringToWasm0(encrypted_table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len1 = WASM_VECTOR_LEN;
+        const ptr2 = passStringToWasm0(lox_pub, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len2 = WASM_VECTOR_LEN;
+        wasm.issue_invite(retptr, ptr0, len0, ptr1, len1, ptr2, len2);
+        var r0 = getInt32Memory0()[retptr / 4 + 0];
+        var r1 = getInt32Memory0()[retptr / 4 + 1];
+        var r2 = getInt32Memory0()[retptr / 4 + 2];
+        var r3 = getInt32Memory0()[retptr / 4 + 3];
+        var ptr4 = r0;
+        var len4 = r1;
+        if (r3) {
+            ptr4 = 0; len4 = 0;
+            throw takeObject(r2);
+        }
+        deferred5_0 = ptr4;
+        deferred5_1 = len4;
+        return getStringFromWasm0(ptr4, len4);
+    } finally {
+        wasm.__wbindgen_add_to_stack_pointer(16);
+        wasm.__wbindgen_free(deferred5_0, deferred5_1, 1);
+    }
+}
+
+/**
+* @param {string} issue_invite_request
+* @param {string} issue_invite_response
+* @param {string} lox_pub
+* @returns {string}
+*/
+function handle_issue_invite(issue_invite_request, issue_invite_response, lox_pub) {
+    let deferred5_0;
+    let deferred5_1;
+    try {
+        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+        const ptr0 = passStringToWasm0(issue_invite_request, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        const ptr1 = passStringToWasm0(issue_invite_response, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len1 = WASM_VECTOR_LEN;
+        const ptr2 = passStringToWasm0(lox_pub, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len2 = WASM_VECTOR_LEN;
+        wasm.handle_issue_invite(retptr, ptr0, len0, ptr1, len1, ptr2, len2);
+        var r0 = getInt32Memory0()[retptr / 4 + 0];
+        var r1 = getInt32Memory0()[retptr / 4 + 1];
+        var r2 = getInt32Memory0()[retptr / 4 + 2];
+        var r3 = getInt32Memory0()[retptr / 4 + 3];
+        var ptr4 = r0;
+        var len4 = r1;
+        if (r3) {
+            ptr4 = 0; len4 = 0;
+            throw takeObject(r2);
+        }
+        deferred5_0 = ptr4;
+        deferred5_1 = len4;
+        return getStringFromWasm0(ptr4, len4);
+    } finally {
+        wasm.__wbindgen_add_to_stack_pointer(16);
+        wasm.__wbindgen_free(deferred5_0, deferred5_1, 1);
+    }
+}
+
+/**
+* @param {string} invitation_cred
+* @returns {string}
+*/
+function prepare_invite(invitation_cred) {
+    let deferred3_0;
+    let deferred3_1;
+    try {
+        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+        const ptr0 = passStringToWasm0(invitation_cred, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        wasm.prepare_invite(retptr, ptr0, len0);
+        var r0 = getInt32Memory0()[retptr / 4 + 0];
+        var r1 = getInt32Memory0()[retptr / 4 + 1];
+        var r2 = getInt32Memory0()[retptr / 4 + 2];
+        var r3 = getInt32Memory0()[retptr / 4 + 3];
+        var ptr2 = r0;
+        var len2 = r1;
+        if (r3) {
+            ptr2 = 0; len2 = 0;
+            throw takeObject(r2);
+        }
+        deferred3_0 = ptr2;
+        deferred3_1 = len2;
+        return getStringFromWasm0(ptr2, len2);
+    } finally {
+        wasm.__wbindgen_add_to_stack_pointer(16);
+        wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
+    }
+}
+
+/**
+* @param {string} invitation
+* @param {string} lox_pub
+* @returns {string}
+*/
+function redeem_invite(invitation, lox_pub) {
+    let deferred4_0;
+    let deferred4_1;
+    try {
+        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+        const ptr0 = passStringToWasm0(invitation, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        const ptr1 = passStringToWasm0(lox_pub, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len1 = WASM_VECTOR_LEN;
+        wasm.redeem_invite(retptr, ptr0, len0, ptr1, len1);
+        var r0 = getInt32Memory0()[retptr / 4 + 0];
+        var r1 = getInt32Memory0()[retptr / 4 + 1];
+        var r2 = getInt32Memory0()[retptr / 4 + 2];
+        var r3 = getInt32Memory0()[retptr / 4 + 3];
+        var ptr3 = r0;
+        var len3 = r1;
+        if (r3) {
+            ptr3 = 0; len3 = 0;
+            throw takeObject(r2);
+        }
+        deferred4_0 = ptr3;
+        deferred4_1 = len3;
+        return getStringFromWasm0(ptr3, len3);
+    } finally {
+        wasm.__wbindgen_add_to_stack_pointer(16);
+        wasm.__wbindgen_free(deferred4_0, deferred4_1, 1);
+    }
+}
+
+/**
+* @param {string} redeem_invite_request
+* @param {string} redeem_invite_response
+* @param {string} lox_pub
+* @returns {string}
+*/
+function handle_redeem_invite(redeem_invite_request, redeem_invite_response, lox_pub) {
+    let deferred5_0;
+    let deferred5_1;
+    try {
+        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+        const ptr0 = passStringToWasm0(redeem_invite_request, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        const ptr1 = passStringToWasm0(redeem_invite_response, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len1 = WASM_VECTOR_LEN;
+        const ptr2 = passStringToWasm0(lox_pub, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len2 = WASM_VECTOR_LEN;
+        wasm.handle_redeem_invite(retptr, ptr0, len0, ptr1, len1, ptr2, len2);
+        var r0 = getInt32Memory0()[retptr / 4 + 0];
+        var r1 = getInt32Memory0()[retptr / 4 + 1];
+        var r2 = getInt32Memory0()[retptr / 4 + 2];
+        var r3 = getInt32Memory0()[retptr / 4 + 3];
+        var ptr4 = r0;
+        var len4 = r1;
+        if (r3) {
+            ptr4 = 0; len4 = 0;
+            throw takeObject(r2);
+        }
+        deferred5_0 = ptr4;
+        deferred5_1 = len4;
+        return getStringFromWasm0(ptr4, len4);
+    } finally {
+        wasm.__wbindgen_add_to_stack_pointer(16);
+        wasm.__wbindgen_free(deferred5_0, deferred5_1, 1);
+    }
+}
+
+/**
+* @param {string} lox_cred
+* @param {string} lox_pub
+* @returns {string}
+*/
+function check_blockage(lox_cred, lox_pub) {
+    let deferred4_0;
+    let deferred4_1;
+    try {
+        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+        const ptr0 = passStringToWasm0(lox_cred, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        const ptr1 = passStringToWasm0(lox_pub, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len1 = WASM_VECTOR_LEN;
+        wasm.check_blockage(retptr, ptr0, len0, ptr1, len1);
+        var r0 = getInt32Memory0()[retptr / 4 + 0];
+        var r1 = getInt32Memory0()[retptr / 4 + 1];
+        var r2 = getInt32Memory0()[retptr / 4 + 2];
+        var r3 = getInt32Memory0()[retptr / 4 + 3];
+        var ptr3 = r0;
+        var len3 = r1;
+        if (r3) {
+            ptr3 = 0; len3 = 0;
+            throw takeObject(r2);
+        }
+        deferred4_0 = ptr3;
+        deferred4_1 = len3;
+        return getStringFromWasm0(ptr3, len3);
+    } finally {
+        wasm.__wbindgen_add_to_stack_pointer(16);
+        wasm.__wbindgen_free(deferred4_0, deferred4_1, 1);
+    }
+}
+
+/**
+* @param {string} check_blockage_request
+* @param {string} check_blockage_response
+* @returns {string}
+*/
+function handle_check_blockage(check_blockage_request, check_blockage_response) {
+    let deferred4_0;
+    let deferred4_1;
+    try {
+        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+        const ptr0 = passStringToWasm0(check_blockage_request, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        const ptr1 = passStringToWasm0(check_blockage_response, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len1 = WASM_VECTOR_LEN;
+        wasm.handle_check_blockage(retptr, ptr0, len0, ptr1, len1);
+        var r0 = getInt32Memory0()[retptr / 4 + 0];
+        var r1 = getInt32Memory0()[retptr / 4 + 1];
+        var r2 = getInt32Memory0()[retptr / 4 + 2];
+        var r3 = getInt32Memory0()[retptr / 4 + 3];
+        var ptr3 = r0;
+        var len3 = r1;
+        if (r3) {
+            ptr3 = 0; len3 = 0;
+            throw takeObject(r2);
+        }
+        deferred4_0 = ptr3;
+        deferred4_1 = len3;
+        return getStringFromWasm0(ptr3, len3);
+    } finally {
+        wasm.__wbindgen_add_to_stack_pointer(16);
+        wasm.__wbindgen_free(deferred4_0, deferred4_1, 1);
+    }
+}
+
+/**
+* @param {string} lox_cred
+* @param {string} check_migration_cred
+* @param {string} lox_pub
+* @returns {string}
+*/
+function blockage_migration(lox_cred, check_migration_cred, lox_pub) {
+    let deferred5_0;
+    let deferred5_1;
+    try {
+        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+        const ptr0 = passStringToWasm0(lox_cred, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        const ptr1 = passStringToWasm0(check_migration_cred, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len1 = WASM_VECTOR_LEN;
+        const ptr2 = passStringToWasm0(lox_pub, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len2 = WASM_VECTOR_LEN;
+        wasm.blockage_migration(retptr, ptr0, len0, ptr1, len1, ptr2, len2);
+        var r0 = getInt32Memory0()[retptr / 4 + 0];
+        var r1 = getInt32Memory0()[retptr / 4 + 1];
+        var r2 = getInt32Memory0()[retptr / 4 + 2];
+        var r3 = getInt32Memory0()[retptr / 4 + 3];
+        var ptr4 = r0;
+        var len4 = r1;
+        if (r3) {
+            ptr4 = 0; len4 = 0;
+            throw takeObject(r2);
+        }
+        deferred5_0 = ptr4;
+        deferred5_1 = len4;
+        return getStringFromWasm0(ptr4, len4);
+    } finally {
+        wasm.__wbindgen_add_to_stack_pointer(16);
+        wasm.__wbindgen_free(deferred5_0, deferred5_1, 1);
+    }
+}
+
+/**
+* @param {string} blockage_migration_request
+* @param {string} blockage_migration_response
+* @param {string} lox_pub
+* @returns {string}
+*/
+function handle_blockage_migration(blockage_migration_request, blockage_migration_response, lox_pub) {
+    let deferred5_0;
+    let deferred5_1;
+    try {
+        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+        const ptr0 = passStringToWasm0(blockage_migration_request, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        const ptr1 = passStringToWasm0(blockage_migration_response, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len1 = WASM_VECTOR_LEN;
+        const ptr2 = passStringToWasm0(lox_pub, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len2 = WASM_VECTOR_LEN;
+        wasm.handle_blockage_migration(retptr, ptr0, len0, ptr1, len1, ptr2, len2);
+        var r0 = getInt32Memory0()[retptr / 4 + 0];
+        var r1 = getInt32Memory0()[retptr / 4 + 1];
+        var r2 = getInt32Memory0()[retptr / 4 + 2];
+        var r3 = getInt32Memory0()[retptr / 4 + 3];
+        var ptr4 = r0;
+        var len4 = r1;
+        if (r3) {
+            ptr4 = 0; len4 = 0;
+            throw takeObject(r2);
+        }
+        deferred5_0 = ptr4;
+        deferred5_1 = len4;
+        return getStringFromWasm0(ptr4, len4);
+    } finally {
+        wasm.__wbindgen_add_to_stack_pointer(16);
+        wasm.__wbindgen_free(deferred5_0, deferred5_1, 1);
+    }
+}
+
+/**
+* @param {string} lox_cred_str
+* @returns {string}
+*/
+function get_last_upgrade_time(lox_cred_str) {
+    let deferred3_0;
+    let deferred3_1;
+    try {
+        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+        const ptr0 = passStringToWasm0(lox_cred_str, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        wasm.get_last_upgrade_time(retptr, ptr0, len0);
+        var r0 = getInt32Memory0()[retptr / 4 + 0];
+        var r1 = getInt32Memory0()[retptr / 4 + 1];
+        var r2 = getInt32Memory0()[retptr / 4 + 2];
+        var r3 = getInt32Memory0()[retptr / 4 + 3];
+        var ptr2 = r0;
+        var len2 = r1;
+        if (r3) {
+            ptr2 = 0; len2 = 0;
+            throw takeObject(r2);
+        }
+        deferred3_0 = ptr2;
+        deferred3_1 = len2;
+        return getStringFromWasm0(ptr2, len2);
+    } finally {
+        wasm.__wbindgen_add_to_stack_pointer(16);
+        wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
+    }
+}
+
+/**
+* @param {string} lox_cred_str
+* @returns {string}
+*/
+function get_trust_level(lox_cred_str) {
+    let deferred3_0;
+    let deferred3_1;
+    try {
+        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+        const ptr0 = passStringToWasm0(lox_cred_str, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        wasm.get_trust_level(retptr, ptr0, len0);
+        var r0 = getInt32Memory0()[retptr / 4 + 0];
+        var r1 = getInt32Memory0()[retptr / 4 + 1];
+        var r2 = getInt32Memory0()[retptr / 4 + 2];
+        var r3 = getInt32Memory0()[retptr / 4 + 3];
+        var ptr2 = r0;
+        var len2 = r1;
+        if (r3) {
+            ptr2 = 0; len2 = 0;
+            throw takeObject(r2);
+        }
+        deferred3_0 = ptr2;
+        deferred3_1 = len2;
+        return getStringFromWasm0(ptr2, len2);
+    } finally {
+        wasm.__wbindgen_add_to_stack_pointer(16);
+        wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
+    }
+}
+
+/**
+* @param {string} lox_cred_str
+* @returns {string}
+*/
+function get_invites_remaining(lox_cred_str) {
+    let deferred3_0;
+    let deferred3_1;
+    try {
+        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+        const ptr0 = passStringToWasm0(lox_cred_str, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        wasm.get_invites_remaining(retptr, ptr0, len0);
+        var r0 = getInt32Memory0()[retptr / 4 + 0];
+        var r1 = getInt32Memory0()[retptr / 4 + 1];
+        var r2 = getInt32Memory0()[retptr / 4 + 2];
+        var r3 = getInt32Memory0()[retptr / 4 + 3];
+        var ptr2 = r0;
+        var len2 = r1;
+        if (r3) {
+            ptr2 = 0; len2 = 0;
+            throw takeObject(r2);
+        }
+        deferred3_0 = ptr2;
+        deferred3_1 = len2;
+        return getStringFromWasm0(ptr2, len2);
+    } finally {
+        wasm.__wbindgen_add_to_stack_pointer(16);
+        wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
+    }
+}
+
+/**
+* @param {string} lox_cred_str
+* @returns {string}
+*/
+function get_issued_invite_expiry(lox_cred_str) {
+    let deferred3_0;
+    let deferred3_1;
+    try {
+        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+        const ptr0 = passStringToWasm0(lox_cred_str, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        wasm.get_issued_invite_expiry(retptr, ptr0, len0);
+        var r0 = getInt32Memory0()[retptr / 4 + 0];
+        var r1 = getInt32Memory0()[retptr / 4 + 1];
+        var r2 = getInt32Memory0()[retptr / 4 + 2];
+        var r3 = getInt32Memory0()[retptr / 4 + 3];
+        var ptr2 = r0;
+        var len2 = r1;
+        if (r3) {
+            ptr2 = 0; len2 = 0;
+            throw takeObject(r2);
+        }
+        deferred3_0 = ptr2;
+        deferred3_1 = len2;
+        return getStringFromWasm0(ptr2, len2);
+    } finally {
+        wasm.__wbindgen_add_to_stack_pointer(16);
+        wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
+    }
+}
+
+/**
+* @param {string} invite_cred_str
+* @returns {string}
+*/
+function get_received_invite_expiry(invite_cred_str) {
+    let deferred3_0;
+    let deferred3_1;
+    try {
+        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+        const ptr0 = passStringToWasm0(invite_cred_str, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        wasm.get_received_invite_expiry(retptr, ptr0, len0);
+        var r0 = getInt32Memory0()[retptr / 4 + 0];
+        var r1 = getInt32Memory0()[retptr / 4 + 1];
+        var r2 = getInt32Memory0()[retptr / 4 + 2];
+        var r3 = getInt32Memory0()[retptr / 4 + 3];
+        var ptr2 = r0;
+        var len2 = r1;
+        if (r3) {
+            ptr2 = 0; len2 = 0;
+            throw takeObject(r2);
+        }
+        deferred3_0 = ptr2;
+        deferred3_1 = len2;
+        return getStringFromWasm0(ptr2, len2);
+    } finally {
+        wasm.__wbindgen_add_to_stack_pointer(16);
+        wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
+    }
+}
+
+/**
+* @param {string} lox_cred_str
+* @param {string} encrypted_table
+* @returns {string}
+*/
+function get_bridgelines_from_bucket(lox_cred_str, encrypted_table) {
+    let deferred4_0;
+    let deferred4_1;
+    try {
+        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+        const ptr0 = passStringToWasm0(lox_cred_str, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        const ptr1 = passStringToWasm0(encrypted_table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len1 = WASM_VECTOR_LEN;
+        wasm.get_bridgelines_from_bucket(retptr, ptr0, len0, ptr1, len1);
+        var r0 = getInt32Memory0()[retptr / 4 + 0];
+        var r1 = getInt32Memory0()[retptr / 4 + 1];
+        var r2 = getInt32Memory0()[retptr / 4 + 2];
+        var r3 = getInt32Memory0()[retptr / 4 + 3];
+        var ptr3 = r0;
+        var len3 = r1;
+        if (r3) {
+            ptr3 = 0; len3 = 0;
+            throw takeObject(r2);
+        }
+        deferred4_0 = ptr3;
+        deferred4_1 = len3;
+        return getStringFromWasm0(ptr3, len3);
+    } finally {
+        wasm.__wbindgen_add_to_stack_pointer(16);
+        wasm.__wbindgen_free(deferred4_0, deferred4_1, 1);
+    }
+}
+
+/**
+* @param {string} unspecified_invitation_str
+* @returns {boolean}
+*/
+function invitation_is_trusted(unspecified_invitation_str) {
+    try {
+        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+        const ptr0 = passStringToWasm0(unspecified_invitation_str, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        wasm.invitation_is_trusted(retptr, ptr0, len0);
+        var r0 = getInt32Memory0()[retptr / 4 + 0];
+        var r1 = getInt32Memory0()[retptr / 4 + 1];
+        var r2 = getInt32Memory0()[retptr / 4 + 2];
+        if (r2) {
+            throw takeObject(r1);
+        }
+        return r0 !== 0;
+    } finally {
+        wasm.__wbindgen_add_to_stack_pointer(16);
+    }
+}
+
+/**
+* @param {string} constants_str
+* @param {string} lox_cred_str
+* @returns {string}
+*/
+function get_next_unlock(constants_str, lox_cred_str) {
+    let deferred4_0;
+    let deferred4_1;
+    try {
+        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+        const ptr0 = passStringToWasm0(constants_str, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        const ptr1 = passStringToWasm0(lox_cred_str, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len1 = WASM_VECTOR_LEN;
+        wasm.get_next_unlock(retptr, ptr0, len0, ptr1, len1);
+        var r0 = getInt32Memory0()[retptr / 4 + 0];
+        var r1 = getInt32Memory0()[retptr / 4 + 1];
+        var r2 = getInt32Memory0()[retptr / 4 + 2];
+        var r3 = getInt32Memory0()[retptr / 4 + 3];
+        var ptr3 = r0;
+        var len3 = r1;
+        if (r3) {
+            ptr3 = 0; len3 = 0;
+            throw takeObject(r2);
+        }
+        deferred4_0 = ptr3;
+        deferred4_1 = len3;
+        return getStringFromWasm0(ptr3, len3);
+    } finally {
+        wasm.__wbindgen_add_to_stack_pointer(16);
+        wasm.__wbindgen_free(deferred4_0, deferred4_1, 1);
+    }
+}
+
+function handleError(f, args) {
+    try {
+        return f.apply(this, args);
+    } catch (e) {
+        wasm.__wbindgen_exn_store(addHeapObject(e));
+    }
+}
+
+async function __wbg_load(module, imports) {
+    if (typeof Response === 'function' && module instanceof Response) {
+        if (typeof WebAssembly.instantiateStreaming === 'function') {
+            try {
+                return await WebAssembly.instantiateStreaming(module, imports);
+
+            } catch (e) {
+                if (module.headers.get('Content-Type') != 'application/wasm') {
+                    console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
+
+                } else {
+                    throw e;
+                }
+            }
+        }
+
+        const bytes = await module.arrayBuffer();
+        return await WebAssembly.instantiate(bytes, imports);
+
+    } else {
+        const instance = await WebAssembly.instantiate(module, imports);
+
+        if (instance instanceof WebAssembly.Instance) {
+            return { instance, module };
+
+        } else {
+            return instance;
+        }
+    }
+}
+
+function __wbg_get_imports(window) {
+    const imports = {};
+    imports.wbg = {};
+    imports.wbg.__wbg_new0_622c21a64f3d83ea = function() {
+        const ret = new Date();
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbg_getTime_9272be78826033e1 = function(arg0) {
+        const ret = getObject(arg0).getTime();
+        return ret;
+    };
+    imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
+        takeObject(arg0);
+    };
+    imports.wbg.__wbg_log_9b9925d843c39805 = function(arg0, arg1) {
+        console.log(getStringFromWasm0(arg0, arg1));
+    };
+    imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
+        const ret = getStringFromWasm0(arg0, arg1);
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbg_new_abda76e883ba8a5f = function() {
+        const ret = new Error();
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbg_stack_658279fe44541cf6 = function(arg0, arg1) {
+        const ret = getObject(arg1).stack;
+        const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len1 = WASM_VECTOR_LEN;
+        getInt32Memory0()[arg0 / 4 + 1] = len1;
+        getInt32Memory0()[arg0 / 4 + 0] = ptr1;
+    };
+    imports.wbg.__wbg_error_f851667af71bcfc6 = function(arg0, arg1) {
+        let deferred0_0;
+        let deferred0_1;
+        try {
+            deferred0_0 = arg0;
+            deferred0_1 = arg1;
+            console.error(getStringFromWasm0(arg0, arg1));
+        } finally {
+            wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
+        }
+    };
+    imports.wbg.__wbindgen_object_clone_ref = function(arg0) {
+        const ret = getObject(arg0);
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbg_crypto_c48a774b022d20ac = function(arg0) {
+        const ret = getObject(arg0).crypto;
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbindgen_is_object = function(arg0) {
+        const val = getObject(arg0);
+        const ret = typeof(val) === 'object' && val !== null;
+        return ret;
+    };
+    imports.wbg.__wbg_process_298734cf255a885d = function(arg0) {
+        const ret = getObject(arg0).process;
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbg_versions_e2e78e134e3e5d01 = function(arg0) {
+        const ret = getObject(arg0).versions;
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbg_node_1cd7a5d853dbea79 = function(arg0) {
+        const ret = getObject(arg0).node;
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbindgen_is_string = function(arg0) {
+        const ret = typeof(getObject(arg0)) === 'string';
+        return ret;
+    };
+    imports.wbg.__wbg_require_8f08ceecec0f4fee = function() { return handleError(function () {
+        const ret = module.require;
+        return addHeapObject(ret);
+    }, arguments) };
+    imports.wbg.__wbindgen_is_function = function(arg0) {
+        const ret = typeof(getObject(arg0)) === 'function';
+        return ret;
+    };
+    imports.wbg.__wbg_call_5da1969d7cd31ccd = function() { return handleError(function (arg0, arg1, arg2) {
+        const ret = getObject(arg0).call(getObject(arg1), getObject(arg2));
+        return addHeapObject(ret);
+    }, arguments) };
+    imports.wbg.__wbg_msCrypto_bcb970640f50a1e8 = function(arg0) {
+        const ret = getObject(arg0).msCrypto;
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbg_newwithlength_6c2df9e2f3028c43 = function(arg0) {
+        const ret = new Uint8Array(arg0 >>> 0);
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbg_self_f0e34d89f33b99fd = function() { return handleError(function () {
+        const ret = window;
+        return addHeapObject(ret);
+    }, arguments) };
+    imports.wbg.__wbg_window_d3b084224f4774d7 = function() { return handleError(function () {
+        const ret = window.window;
+        return addHeapObject(ret);
+    }, arguments) };
+    imports.wbg.__wbg_globalThis_9caa27ff917c6860 = function() { return handleError(function () {
+        const ret = globalThis.globalThis;
+        return addHeapObject(ret);
+    }, arguments) };
+    imports.wbg.__wbg_global_35dfdd59a4da3e74 = function() { return handleError(function () {
+        const ret = global.global;
+        return addHeapObject(ret);
+    }, arguments) };
+    imports.wbg.__wbindgen_is_undefined = function(arg0) {
+        const ret = getObject(arg0) === undefined;
+        return ret;
+    };
+    imports.wbg.__wbg_newnoargs_c62ea9419c21fbac = function(arg0, arg1) {
+        const ret = new Function(getStringFromWasm0(arg0, arg1));
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbg_call_90c26b09837aba1c = function() { return handleError(function (arg0, arg1) {
+        const ret = getObject(arg0).call(getObject(arg1));
+        return addHeapObject(ret);
+    }, arguments) };
+    imports.wbg.__wbindgen_memory = function() {
+        const ret = wasm.memory;
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbg_buffer_a448f833075b71ba = function(arg0) {
+        const ret = getObject(arg0).buffer;
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbg_newwithbyteoffsetandlength_d0482f893617af71 = function(arg0, arg1, arg2) {
+        const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbg_randomFillSync_dc1e9a60c158336d = function() { return handleError(function (arg0, arg1) {
+        getObject(arg0).randomFillSync(takeObject(arg1));
+    }, arguments) };
+    imports.wbg.__wbg_subarray_2e940e41c0f5a1d9 = function(arg0, arg1, arg2) {
+        const ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0);
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbg_getRandomValues_37fa2ca9e4e07fab = function() { return handleError(function (arg0, arg1) {
+        getObject(arg0).getRandomValues(getObject(arg1));
+    }, arguments) };
+    imports.wbg.__wbg_new_8f67e318f15d7254 = function(arg0) {
+        const ret = new Uint8Array(getObject(arg0));
+        return addHeapObject(ret);
+    };
+    imports.wbg.__wbg_set_2357bf09366ee480 = function(arg0, arg1, arg2) {
+        getObject(arg0).set(getObject(arg1), arg2 >>> 0);
+    };
+    imports.wbg.__wbindgen_throw = function(arg0, arg1) {
+        throw new Error(getStringFromWasm0(arg0, arg1));
+    };
+
+    return imports;
+}
+
+function __wbg_init_memory(imports, maybe_memory) {
+
+}
+
+function __wbg_finalize_init(instance, module) {
+    wasm = instance.exports;
+    init.__wbindgen_wasm_module = module;
+    cachedInt32Memory0 = null;
+    cachedUint8Memory0 = null;
+
+
+    return wasm;
+}
+
+function initSync(module, window) {
+    if (wasm !== undefined) return wasm;
+
+    const imports = __wbg_get_imports(window);
+
+    __wbg_init_memory(imports);
+
+    if (!(module instanceof WebAssembly.Module)) {
+        module = new WebAssembly.Module(module);
+    }
+
+    const instance = new WebAssembly.Instance(module, imports);
+
+    return __wbg_finalize_init(instance, module);
+}
+
+async function init(window, input) {
+    if (wasm !== undefined) return wasm;
+
+    if (typeof input === 'undefined') {{
+        input = "chrome://global/content/lox/lox_wasm_bg.wasm";
+    }}
+    const imports = __wbg_get_imports(window);
+
+    if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
+        input = fetch(input);
+    }
+
+    __wbg_init_memory(imports);
+
+    const { instance, module } = await __wbg_load(await input, imports);
+
+    return __wbg_finalize_init(instance, module);
+}
+


=====================================
toolkit/components/lox/moz.build
=====================================
@@ -0,0 +1,7 @@
+EXTRA_JS_MODULES += [
+  "Lox.sys.mjs",
+  # Let's keep the old jsm format until wasm-bindgen is updated
+  "lox_wasm.jsm",
+]
+
+JAR_MANIFESTS += ["jar.mn"]


=====================================
toolkit/components/moz.build
=====================================
@@ -46,6 +46,7 @@ DIRS += [
     "httpsonlyerror",
     "jsoncpp/src/lib_json",
     "kvstore",
+    "lox",
     "mediasniffer",
     "mozintl",
     "mozprotocol",


=====================================
toolkit/components/tor-launcher/TorStartupService.sys.mjs
=====================================
@@ -54,5 +54,6 @@ export class TorStartupService {
 
     lazy.TorProviderBuilder.uninit();
     lazy.TorLauncherUtil.cleanupTempDirectories();
+    lazy.TorSettings.uninit();
   }
 }


=====================================
toolkit/modules/TorSettings.sys.mjs
=====================================
@@ -8,6 +8,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
   TorLauncherUtil: "resource://gre/modules/TorLauncherUtil.sys.mjs",
   TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
   TorProviderTopics: "resource://gre/modules/TorProviderBuilder.sys.mjs",
+  Lox: "resource://gre/modules/Lox.sys.mjs",
 });
 
 ChromeUtils.defineLazyGetter(lazy, "logger", () => {
@@ -40,6 +41,8 @@ const TorSettingsPrefs = Object.freeze({
     enabled: "torbrowser.settings.bridges.enabled",
     /* int: See TorBridgeSource */
     source: "torbrowser.settings.bridges.source",
+    /* string: output of crypto.randomUUID() */
+    lox_id: "torbrowser.settings.bridges.lox_id",
     /* string: obfs4|meek-azure|snowflake|etc */
     builtin_type: "torbrowser.settings.bridges.builtin_type",
     /* preference branch: each child branch should be a bridge string */
@@ -86,6 +89,7 @@ export const TorBridgeSource = Object.freeze({
   BuiltIn: 0,
   BridgeDB: 1,
   UserProvided: 2,
+  Lox: 3,
 });
 
 export const TorProxyType = Object.freeze({
@@ -159,6 +163,7 @@ class TorSettingsImpl {
     bridges: {
       enabled: false,
       source: TorBridgeSource.Invalid,
+      lox_id: "",
       builtin_type: "",
       bridge_strings: [],
     },
@@ -292,6 +297,20 @@ class TorSettingsImpl {
           this.bridges.source = TorBridgeSource.Invalid;
         },
       },
+      /**
+       * The lox id is used with the Lox "source", and remains set with the stored value when
+       * other sources are used.
+       *
+       * @type {string}
+       */
+      lox_id: {
+        callback: (val, addError) => {
+          if (!val) {
+            return;
+          }
+          this.bridges.bridge_strings = lazy.Lox.getBridges(val);
+        },
+      },
     });
     this.#addProperties("proxy", {
       enabled: {},
@@ -379,6 +398,9 @@ class TorSettingsImpl {
       if (this.bridges.source !== TorBridgeSource.BuiltIn) {
         this.bridges.builtin_type = "";
       }
+      if (this.bridges.source !== TorBridgeSource.Lox) {
+        this.bridges.lox_id = "";
+      }
       if (!this.proxy.enabled) {
         this.proxy.type = TorProxyType.Invalid;
         this.proxy.address = "";
@@ -639,6 +661,14 @@ class TorSettingsImpl {
       lazy.logger.error("Could not load the built-in PT config.", e);
     }
 
+    // Initialize this before loading from prefs because we need Lox initialized before
+    // any calls to Lox.getBridges()
+    try {
+      await lazy.Lox.init();
+    } catch (e) {
+      lazy.logger.error("Could not initialize Lox.", e.type);
+    }
+
     // TODO: We could use a shared promise, and wait for it to be fullfilled
     // instead of Service.obs.
     if (lazy.TorLauncherUtil.shouldStartAndOwnTor) {
@@ -668,6 +698,13 @@ class TorSettingsImpl {
     }
   }
 
+  /**
+   * Unload or uninit our settings, and unregister observers.
+   */
+  async uninit() {
+    await lazy.Lox.uninit();
+  }
+
   /**
    * Check whether the object has been successfully initialized, and throw if
    * it has not.
@@ -738,6 +775,10 @@ class TorSettingsImpl {
       TorSettingsPrefs.bridges.source,
       TorBridgeSource.Invalid
     );
+    this.bridges.lox_id = Services.prefs.getStringPref(
+      TorSettingsPrefs.bridges.lox_id,
+      ""
+    );
     if (this.bridges.source == TorBridgeSource.BuiltIn) {
       this.bridges.builtin_type = Services.prefs.getStringPref(
         TorSettingsPrefs.bridges.builtin_type,
@@ -823,6 +864,10 @@ class TorSettingsImpl {
       TorSettingsPrefs.bridges.builtin_type,
       this.bridges.builtin_type
     );
+    Services.prefs.setStringPref(
+      TorSettingsPrefs.bridges.lox_id,
+      this.bridges.lox_id
+    );
     // erase existing bridge strings
     const bridgeBranchPrefs = Services.prefs
       .getBranch(TorSettingsPrefs.bridges.bridge_strings)
@@ -1013,6 +1058,9 @@ class TorSettingsImpl {
           case TorBridgeSource.BuiltIn:
             this.bridges.builtin_type = settings.bridges.builtin_type;
             break;
+          case TorBridgeSource.Lox:
+            this.bridges.lox_id = settings.bridges.lox_id;
+            break;
           case TorBridgeSource.Invalid:
             break;
         }



View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/a48e4389c43402cc1934bef62c5da1f09bf72517...478ddecbea33fcb11e9c8b1160ce37939be303e0

-- 
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/a48e4389c43402cc1934bef62c5da1f09bf72517...478ddecbea33fcb11e9c8b1160ce37939be303e0
You're receiving this email because of your account on gitlab.torproject.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.torproject.org/pipermail/tbb-commits/attachments/20240126/57b63f5b/attachment-0001.htm>


More information about the tbb-commits mailing list