client/protocols/numbers/comparison.js

// Comparison operations on shares
module.exports = function (SecretShare) {
  /**
   * Greater than or equal with another share.
   * @method sgteq
   * @param {SecretShare} o - the other share.
   * @param {string} [op_id=auto_gen()] - the operation id which is used to identify this operation.
   *                         This id must be unique, and must be passed by all parties to the same instruction, to
   *                         ensure that corresponding instructions across different parties are matched correctly.
   * @return {SecretShare} this party's share of the result, the final result is 1 if this >= o, and 0 otherwise.
   * @memberof SecretShare
   * @instance
   */
  SecretShare.prototype.sgteq = function (o, op_id) {
    if (!(o.jiff === this.jiff)) {
      throw new Error('shares do not belong to the same instance (>=)');
    }
    if (!this.jiff.helpers.Zp_equals(this, o)) {
      throw new Error('shares must belong to the same field (>=)');
    }
    if (!this.jiff.helpers.array_equals(this.holders, o.holders)) {
      throw new Error('shares must be held by the same parties (>=)');
    }

    if (op_id == null) {
      op_id = this.jiff.counters.gen_op_id('sgteq', this.holders);
    }

    return this.islt(o, op_id).inot();
  };

  /**
   * Greater than with another share.
   * @method sgt
   * @param {SecretShare} o - the other share.
   * @param {string} [op_id=auto_gen()] - the operation id which is used to identify this operation.
   *                         This id must be unique, and must be passed by all parties to the same instruction, to
   *                         ensure that corresponding instructions across different parties are matched correctly.
   * @return {SecretShare} this party's share of the result, the final result is 1 if this > o, and 0 otherwise.
   * @memberof SecretShare
   * @instance
   */
  SecretShare.prototype.sgt = function (o, op_id) {
    if (!(o.jiff === this.jiff)) {
      throw new Error('shares do not belong to the same instance (>)');
    }
    if (!this.jiff.helpers.Zp_equals(this, o)) {
      throw new Error('shares must belong to the same field (>)');
    }
    if (!this.jiff.helpers.array_equals(this.holders, o.holders)) {
      throw new Error('shares must be held by the same parties (>)');
    }

    if (op_id == null) {
      op_id = this.jiff.counters.gen_op_id('sgt', this.holders);
    }

    return o.islt(this, op_id);
  };

  /**
   * Less than or equal with another share.
   * @method slteq
   * @param {SecretShare} o - the other share.
   * @param {string} [op_id=auto_gen()] - the operation id which is used to identify this operation.
   *                         This id must be unique, and must be passed by all parties to the same instruction, to
   *                         ensure that corresponding instructions across different parties are matched correctly.
   * @return {SecretShare} this party's share of the result, the final result is 1 if this <= o, and 0 otherwise.
   * @memberof SecretShare
   * @instance
   */
  SecretShare.prototype.slteq = function (o, op_id) {
    if (!(o.jiff === this.jiff)) {
      throw new Error('shares do not belong to the same instance (<=)');
    }
    if (!this.jiff.helpers.Zp_equals(this, o)) {
      throw new Error('shares must belong to the same field (<=)');
    }
    if (!this.jiff.helpers.array_equals(this.holders, o.holders)) {
      throw new Error('shares must be held by the same parties (<=)');
    }

    if (op_id == null) {
      op_id = this.jiff.counters.gen_op_id('slteq', this.holders);
    }

    return o.islt(this, op_id).inot();
  };

  /**
   * Less than with another share.
   * @method slt
   * @param {SecretShare} o - the other share.
   * @param {string} [op_id=auto_gen()] - the operation id which is used to identify this operation.
   *                         This id must be unique, and must be passed by all parties to the same instruction, to
   *                         ensure that corresponding instructions across different parties are matched correctly.
   * @return {SecretShare} this party's share of the result, the final result is 1 if this < o, and 0 otherwise.
   * @memberof SecretShare
   * @instance
   */
  SecretShare.prototype.slt = function (o, op_id) {
    if (!(o.jiff === this.jiff)) {
      throw new Error('shares do not belong to the same instance (<)');
    }
    if (!this.jiff.helpers.Zp_equals(this, o)) {
      throw new Error('shares must belong to the same field (<)');
    }
    if (!this.jiff.helpers.array_equals(this.holders, o.holders)) {
      throw new Error('shares must be held by the same parties (<)');
    }

    if (op_id == null) {
      op_id = this.jiff.counters.gen_op_id('slt', this.holders);
    }

    var final_deferred = new this.jiff.helpers.Deferred();
    var final_promise = final_deferred.promise;
    var result = new this.jiff.SecretShare(final_promise, this.holders, Math.max(this.threshold, o.threshold), this.Zp);

    var w = this.ilt_halfprime(op_id + ':halfprime:1');

    var self = this;
    w.wThen(function () {
      var x = o.ilt_halfprime(op_id + ':halfprime:2');
      x.wThen(function () {
        var y = self.issub(o).ilt_halfprime(op_id + ':halfprime:3');
        y.wThen(function () {
          var xy = x.ismult(y, op_id + ':smult1');
          var answer = x.icmult(-1).icadd(1).issub(y).isadd(xy).isadd(w.ismult(x.isadd(y).issub(xy.icmult(2)), op_id + ':smult2'));
          answer.wThen(final_deferred.resolve);
        });
      });
    });

    return result;
  };

  /**
   * Greater than or equal with a constant.
   * @method cgteqn
   * @param {number} cst - the constant to compare with.
   * @param {string} [op_id=auto_gen()] - the operation id which is used to identify this operation.
   *                         This id must be unique, and must be passed by all parties to the same instruction, to
   *                         ensure that corresponding instructions across different parties are matched correctly.
   * @return {SecretShare} this party's share of the result, the final result is 1 if this >= cst, and 0 otherwise.
   * @memberof SecretShare
   * @instance
   */
  SecretShare.prototype.cgteq = function (cst, op_id) {
    if (!(this.isConstant(cst))) {
      throw new Error('parameter should be a number (>=)');
    }

    if (op_id == null) {
      op_id = this.jiff.counters.gen_op_id('cgteq', this.holders);
    }

    return this.iclt(cst, op_id).inot();
  };

  /**
   * Greater than with a constant.
   * @method cgt
   * @param {number} cst - the constant to compare with.
   * @param {string} [op_id=auto_gen()] - the operation id which is used to identify this operation.
   *                         This id must be unique, and must be passed by all parties to the same instruction, to
   *                         ensure that corresponding instructions across different parties are matched correctly.default ids suffice when all parties execute the
   *                         instructions in the same order.
   * @return {SecretShare} this party's share of the result, the final result is 1 if this > cst, and 0 otherwise.
   * @memberof SecretShare
   * @instance
   */
  SecretShare.prototype.cgt = function (cst, op_id) {
    if (!(this.isConstant(cst))) {
      throw new Error('parameter should be a number (>)');
    }

    if (op_id == null) {
      op_id = this.jiff.counters.gen_op_id('cgt', this.holders);
    }

    var final_deferred = new this.jiff.helpers.Deferred();
    var final_promise = final_deferred.promise;
    var result = new this.jiff.SecretShare(final_promise, this.holders, this.threshold, this.Zp);

    var w = this.jiff.share_helpers['<'](cst, this.jiff.share_helpers['/'](this.Zp, 2)) ? 1 : 0;
    var x = this.ilt_halfprime(op_id + ':halfprime:1');

    var self = this;
    x.wThen(function () {
      var y = self.icmult(-1).icadd(cst).ilt_halfprime(op_id + ':halfprime:2');
      y.wThen(function () {
        var xy = y.ismult(x, op_id + ':smult1');
        var answer = x.icmult(-1).icadd(1).issub(y).isadd(xy).isadd(x.isadd(y).issub(xy.icmult(2)).icmult(w));
        answer.wThen(final_deferred.resolve);
      });
    });

    return result;
  };

  /**
   * Less than or equal with a constant.
   * @method clteq
   * @param {number} cst - the constant to compare with.
   * @param {string} [op_id=auto_gen()] - the operation id which is used to identify this operation.
   *                         This id must be unique, and must be passed by all parties to the same instruction, to
   *                         ensure that corresponding instructions across different parties are matched correctly.
   * @return {SecretShare} this party's share of the result, the final result is 1 if this <= cst, and 0 otherwise.
   * @memberof SecretShare
   * @instance
   */
  SecretShare.prototype.clteq = function (cst, op_id) {
    if (!(this.isConstant(cst))) {
      throw new Error('parameter should be a number (<=)');
    }

    if (op_id == null) {
      op_id = this.jiff.counters.gen_op_id('clteq', this.holders);
    }

    return this.icgt(cst, op_id).inot();
  };

  /**
   * Less than with a constant.
   * @method clt
   * @param {number} cst - the constant to compare with.
   * @param {string} [op_id=auto_gen()] - the operation id which is used to identify this operation.
   *                         This id must be unique, and must be passed by all parties to the same instruction, to
   *                         ensure that corresponding instructions across different parties are matched correctly.
   * @return {SecretShare} this party's share of the result, the final result is 1 if this < cst, and 0 otherwise.
   * @memberof SecretShare
   * @instance
   */
  SecretShare.prototype.clt = function (cst, op_id) {
    if (!(this.isConstant(cst))) {
      throw new Error('parameter should be a number (<)');
    }

    if (op_id == null) {
      op_id = this.jiff.counters.gen_op_id('clt', this.holders);
    }

    var final_deferred = new this.jiff.helpers.Deferred();
    var final_promise = final_deferred.promise;
    var result = new this.jiff.SecretShare(final_promise, this.holders, this.threshold, this.Zp);

    var w = this.ilt_halfprime(op_id + ':halfprime:1');

    var self = this;
    w.wThen(function () {
      var x = self.jiff.share_helpers['<'](cst, self.jiff.share_helpers['/'](self.Zp, 2)) ? 1 : 0;
      var y = self.icsub(cst).ilt_halfprime(op_id + ':halfprime:2');
      y.wThen(function () {
        var xy = y.icmult(x);
        var answer = y.icmult(-1).icadd(1 - x).isadd(xy).isadd(w.ismult(y.icadd(x).issub(xy.icmult(2)), op_id + ':smult1'));
        answer.wThen(final_deferred.resolve);
      });
    });

    return result;
  };

  /**
   * Equality test with two shares.
   * @method seq
   * @param {SecretShare} o - the share to compare with.
   * @param {string} [op_id=auto_gen()] - the operation id which is used to identify this operation.
   *                         This id must be unique, and must be passed by all parties to the same instruction, to
   *                         ensure that corresponding instructions across different parties are matched correctly.
   * @return {SecretShare} this party's share of the result, the final result is 1 if this = o, and 0 otherwise.
   * @memberof SecretShare
   * @instance
   */
  SecretShare.prototype.seq = function (o, op_id) {
    if (!(o.jiff === this.jiff)) {
      throw new Error('shares do not belong to the same instance (==)');
    }
    if (!this.jiff.helpers.Zp_equals(this, o)) {
      throw new Error('shares must belong to the same field (==)');
    }
    if (!this.jiff.helpers.array_equals(this.holders, o.holders)) {
      throw new Error('shares must be held by the same parties (==)');
    }
    if (op_id == null) {
      op_id = this.jiff.counters.gen_op_id('seq', this.holders);
    }

    return this.isneq(o, op_id).inot();
  };

  /**
   * Unequality test with two shares.
   * @method sneq
   * @param {SecretShare} o - the share to compare with.
   * @param {string} [op_id=auto_gen()] - the operation id which is used to identify this operation.
   *                         This id must be unique, and must be passed by all parties to the same instruction, to
   *                         ensure that corresponding instructions across different parties are matched correctly.
   * @return {SecretShare} this party's share of the result, the final result is 0 if this = o, and 1 otherwise.
   * @memberof SecretShare
   * @instance
   */
  SecretShare.prototype.sneq = function (o, op_id) {
    if (!(o.jiff === this.jiff)) {
      throw new Error('shares do not belong to the same instance (!=)');
    }
    if (!this.jiff.helpers.Zp_equals(this, o)) {
      throw new Error('shares must belong to the same field (!=)');
    }
    if (!this.jiff.helpers.array_equals(this.holders, o.holders)) {
      throw new Error('shares must be held by the same parties (!=)');
    }
    if (op_id == null) {
      op_id = this.jiff.counters.gen_op_id('sneq', this.holders);
    }

    return this.issub(o).icpow(this.jiff.share_helpers['-'](this.Zp, 1), op_id + ':cpow');
  };

  /**
   * Equality test with a constant.
   * @method ceq
   * @param {number} cst - the constant to compare with.
   * @param {string} [op_id=auto_gen()] - the operation id which is used to identify this operation.
   *                         This id must be unique, and must be passed by all parties to the same instruction, to
   *                         ensure that corresponding instructions across different parties are matched correctly.
   * @return {SecretShare} this party's share of the result, the final result is 0 if this = o, and 1 otherwise.
   * @memberof SecretShare
   * @instance
   */
  SecretShare.prototype.ceq = function (cst, op_id) {
    if (!(this.isConstant(cst))) {
      throw new Error('parameter should be a number (==)');
    }
    if (op_id == null) {
      op_id = this.jiff.counters.gen_op_id('ceq', this.holders);
    }

    return this.icneq(cst, op_id).inot();
  };

  /**
   * Unequality test with a constant.
   * @method cneq
   * @param {number} cst - the constant to compare with.
   * @param {string} [op_id=auto_gen()] - the operation id which is used to identify this operation.
   *                         This id must be unique, and must be passed by all parties to the same instruction, to
   *                         ensure that corresponding instructions across different parties are matched correctly.
   * @return {SecretShare} this party's share of the result, the final result is 0 if this = o, and 1 otherwise.
   * @memberof SecretShare
   * @instance
   */
  SecretShare.prototype.cneq = function (cst, op_id) {
    if (!(this.isConstant(cst))) {
      throw new Error('parameter should be a number (!=)');
    }
    if (op_id == null) {
      op_id = this.jiff.counters.gen_op_id('cneq', this.holders);
    }

    return this.icsub(cst).icpow(this.jiff.share_helpers['-'](this.Zp, 1), op_id + ':cpow');
  };

  /**
   * Checks whether the share is less than half the field size.
   * @method lt_halfprime
   * @memberof SecretShare
   * @instance
   * @param {string} [op_id=auto_gen()] - the operation id which is used to identify this operation.
   *                         This id must be unique, and must be passed by all parties to the same instruction, to
   *                         ensure that corresponding instructions across different parties are matched correctly.
   * @return {SecretShare} this party's share of the result.
   */
  SecretShare.prototype.lt_halfprime = function (op_id) {
    if (op_id == null) {
      op_id = this.jiff.counters.gen_op_id('lt_halfprime', this.holders);
    }

    // if share is even, then this is less than half the prime, otherwise, share is greater than half the prime
    var share = this.icmult(2);

    // to check if share is even, we will use pre-shared bits as some form of a bit mask
    var bitLength = this.jiff.share_helpers['ceil'](this.jiff.helpers.bLog(share.Zp, 2));

    // Create result share
    var final_deferred = new this.jiff.helpers.Deferred();
    var final_promise = final_deferred.promise;
    var result = new this.jiff.SecretShare(final_promise, this.holders, this.threshold, this.Zp);

    // Execute protocol when randomly sampled bit-wise random number is ready
    var self = this;
    var ready_sampling = function (bits) {
      // if 2*this is even, then this is less than half prime, otherwise this is greater or equal to half prime
      if (bits.length !== bitLength) {
        throw new Error('Preprocessed bits sequence has incorrect length, expected: ' + bitLength + ' actual: ' + bits.length);
      }

      // bit composition: r = (rl ... r1 r0)_10
      var r = self.jiff.protocols.bits.bit_composition(bits);
      // open share + noise, and utilize opened value with shared bit representation of noise to check the least significant digit of share.
      share.jiff.internal_open(r.isadd(share), share.holders, op_id + ':open').then(function (result) {
        var wrapped = self.jiff.protocols.bits.cgt(bits, result, op_id + ':bits.cgt');
        var isOdd = self.jiff.helpers.mod(result, 2);
        isOdd = bits[0].icxor_bit(isOdd);
        isOdd = isOdd.isxor_bit(wrapped, op_id + ':sxor_bit');

        var answer = isOdd.inot();
        answer.wThen(final_deferred.resolve);
      });
    };

    // generate the bits of a random number less than our prime
    var bits = this.jiff.get_preprocessing(op_id + ':sampling');
    if (bits == null) {
      var promise = this.jiff.from_crypto_provider('numbers', this.holders, this.threshold, this.Zp, op_id + ':sampling', {
        bitLength: bitLength,
        count: 1,
        max: this.Zp
      });
      promise.then(function (msg) {
        ready_sampling(msg['shares']);
      });
    } else {
      ready_sampling(bits);
    }

    return result;
  };
};