jiggClient.js

/**
 * Main client module: garbled circuit protocol agents.
 * This is the module exported by dist/jigg.js for browsers.
 *
 * When included using a script tag, this module exposes the global
 * constructor JIGG, which is an alias for the class {@link Agent}.
 *
 * @module Browser
 */

'use strict';

const LABEL_SIZE = 16; // 16 bytes => 128 bits

const garble = require('./garble.js');
const evaluate = require('./evaluate.js');

const circuitParser = require('./parse/parse.js');

const Socket = require('./comm/clientSocket.js');
const OT = require('./comm/ot.js');

const hexutils = require('./util/hexutils.js');
const sodium = require('libsodium-wrappers-sumo');

/**
 * Create a new JIGG agent with the given role.
 * @param {string} role - Agent role ('Garbler' or 'Evaluator').
 * @param {string} hostname - hostname and port of the server, should be acceptable by socket.io.
 * @param {object} [options] - additional optional options including:
 *
 *     debug: boolean, defaults to false.
 *
 *     labelSize: number, defaults to 16 bytes.
 *
 * @constructor
 * @alias Agent
 */
function Agent(role, hostname, options) {
  const self = this;

  if (options == null) {
    options = {};
  }

  if (options.__Socket) {
    this.socket = new options.__Socket(hostname, this);
  } else {
    this.socket = new Socket(hostname, this);
  }

  this.role = role;
  this.OT = new OT(this.socket);
  this.hexutils = hexutils;

  this.listeners = [];
  this.log = function () {};

  this._outputPromise = new Promise (function (resolve) {
    self._outputResolve = resolve;
  });
  this._outputPromise.then(this.socket.disconnect.bind(this.socket));

  this.throttle = options.throttle == null ? 0 : options.throttle;
  this.parallel = options.parallel == null ? Number.MAX_VALUE : options.parallel;
  this.labelSize = options.labelSize == null ? LABEL_SIZE : options.labelSize;

  if (options.debug) {
    this.log = console.log.bind(console, this.role);
    this.addProgressListener(this.log);
  }
}

/**
 * Loads the given circuit.
 * @param {string|Circuit} circuit - the circuit encoded as specified in encoding.
 * @param {string} [encoding='text'] - the encoding of the circuit, defaults to 'text' indicating a text encoding of a bristol fashion circuit.
 *                                     Alternatively, 'object' can be used for parsed circuits provided as a Circuit object.
 */
Agent.prototype.loadCircuit = function (circuit, encoding) {
  if (encoding == null || encoding === 'text') {
    this.circuit = circuitParser(circuit);
  } else {
    this.circuit = circuit;
  }
};

/**
 * Sets the input of this party.
 * @param {number[]|number|string} input - the input to the circuit.
 * @param [encoding='bits'] - the encoding of the input, defaults to 'bits' for array of 0|1. The order of bits depends on the
 *                            underlying circuit, but typically, index 0 represents the least significant bit.
 *                            Alternatively, it accepts 'number' and 'hex' for a number and a hex string.
 */
Agent.prototype.setInput = function (input, encoding) {
  const size = (this.role === 'Garbler' ? this.circuit.garblerInputSize : this.circuit.evaluatorInputSize);

  if (encoding === 'number') {
    this.input = input.toString(2).split('').map(function (bit) {
      return parseInt(bit);
    }).reverse();

    while (this.input.length < size) {
      this.input.push(0);
    }
  }

  if (encoding === 'hex') {
    this.input = hexutils.hex2bin(input).split('').map(function (bit) {
      return parseInt(bit);
    }).reverse();

    while (this.input.length < size) {
      this.input.push(0);
    }
  }

  if (encoding === 'bits' || encoding == null) {
    if (input.length !== size) {
      throw new Error('Input has wrong length');
    }

    this.input = input.slice();
  }
};

/**
 * Returns a promise to the output encoded as specified by the encoding.
 * @param [encoding='bits'] - the encoding of the input, defaults to 'bits' for array of 0|1. The order of bits depends on the
 *                            underlying circuit, but typically, index 0 represents the least significant bit.
 *                            Alternatively, it accepts 'number' and 'hex' for a number and a hex string.
 * @return {Promise} a promise to the output, which is either number, number[], or string.
 */
Agent.prototype.getOutput = function (encoding) {
  return this._outputPromise.then(function (output) {
    output = output.slice();
    if (encoding == null || encoding === 'bits') {
      return output;
    }

    if (encoding === 'hex') {
      return hexutils.bin2hex(output.reverse().join(''));
    }

    if (encoding === 'number') {
      return parseInt(output.reverse().join(''), 2);
    }
  });
};

/**
 * Adds a listener for progress events.
 * @param {module:JIGG~progressListener} progressListener - the listener.
 */
Agent.prototype.addProgressListener = function (progressListener) {
  this.listeners.push(progressListener);
};

Agent.prototype.progress = function (state, current, total, error) {
  for (let i = 0; i < this.listeners.length; i++) {
    this.listeners[i](state, current, total, error);
  }
};

/**
 * Run the agent on the circuit.
 */
Agent.prototype.start = function () {
  const self = this;

  sodium.ready.then(function () {
    self.socket.join(self.role);
    self.socket.hear('go').then(function () {
      self.progress('connected');
      if (self.role === 'Garbler') {
        garble(self);
      } else {
        evaluate(self);
      }
    });
  });
};

/**
 * Disconnects the socket with the server.
 */
Agent.prototype.disconnect = function () {
  this.socket.disconnect();
};

module.exports = Agent;