client/api/sharing.js

var shareProtocol = require('../protocols/shamir/share.js');
var openProtocol = require('../protocols/shamir/open.js');
var reshareProtocol = require('../protocols/shamir/reshare.js');
var arraysSharing = require('../protocols/arrays/api.js');

module.exports = function (jiffClient) {
  /**
   * Share a secret input
   *
   * Can be overriden by extensions to customize behavior
   *
   * @method share
   * @memberof module:jiff-client~JIFFClient
   * @instance
   * @param {number} secret - the number to share (this party's input)
   * @param {number} [threshold=receivers_list.length] - the minimum number of parties needed to reconstruct the secret, defaults to all the receivers
   * @param {Array} [receivers_list=all_parties] - array of party ids to share with, by default, this includes all parties
   * @param {Array} [senders_list=all_parties] - array of party ids to receive from, by default, this includes all parties
   * @param {number} [Zp=jiff_instance.Zp] - the mod (if null then the default Zp for the instance is used)
   * @param {string|number} [share_id=auto_gen()] - the tag used to tag the messages sent by this share operation, this tag is used
   *                                   so that parties distinguish messages belonging to this share operation from other
   *                                   share operations between the same parties (when the order of execution is not
   *                                   deterministic). An automatic id is generated by increasing a local counter, default
   *                                   ids suffice when all parties execute all sharing operations with the same senders
   *                                   and receivers in the same order
   * @returns {object} a map (of size equal to the number of sending parties)
   *          where the key is the party id (between 1 and n), or 's1' if 's1' is specified in the senders_list,
   *          and the value is the share object that wraps the value received from that party (the internal value maybe
   *          deferred).
   *
   * @example
   * // share an input value with all parties, and receive all other parties' inputs
   * var shares = jiffClient.share(input);
   * // my party id is '1', so the first share is mine (technically my share of my input value)
   * var my_share = shares[1];
   * // my share of party 2's input
   * var p2_share = shares[2];
   */
  jiffClient.share = function (secret, threshold, receivers_list, senders_list, Zp, share_id) {
    // type check to confirm the secret to be shared is a number
    // for fixed-point extension it should allow non-ints
    if (secret != null && (typeof(secret) !== 'number' || Math.floor(secret) !== secret || secret < 0)) {
      throw new Error('secret \'' + secret + '\' must be a non-negative whole number');
    }
    if (secret != null && (secret >= (Zp == null ? jiffClient.Zp : Zp))) {
      throw new Error('secret \'' + secret + '\' must fit inside Zp');
    }
    return jiffClient.internal_share(secret, threshold, receivers_list, senders_list, Zp, share_id);
  };

  /**
   * Same as share, but used by internal JIFF primitives/protocols, do not override this!
   * @see {@link share}
   * @method internal_share
   * @instance
   * @memberof module:jiff-client~JIFFClient
   */
  jiffClient.internal_share = shareProtocol.jiff_share.bind(null, jiffClient);

  /**
   * re-share an existing share (value) under a new threshold or to a new set of parties or both.
   * Do not use this to refresh a share (use {@link SecretShare#refresh} instead)
   * @method reshare
   * @instance
   * @memberof module:jiff-client~JIFFClient
   * @param {SecretShare} [share=null] - the share you would like to reshare (null if you are a receiver but not a sender)
   * @param {number} [threshold=receivers_list.length] - the new threshold, defaults to the length of receivers_list param
   * @param {Array} [receivers_list=all_parties] - array of party ids to receive from, by default, this includes all parties
   * @param {Array} [senders_list=all_parties] - array of party ids that posses the share and will reshare it with the receivers, by default, this includes all parties
   * @param {number} [Zp=jiff.Zp] - the Zp of the existing share
   * @param {string} [op_id=auto_gen()] - the operation id which is used to identify this multiplication (and internally, the corresponding beaver triplet).
   *                         This id must be unique, and must be passed by all parties to the same instruction.
   *                         this ensures that every party gets a share from the same triplet for every matching instruction. An automatic id
   *                         is generated by increasing a local counter, default ids suffice when all parties execute the
   *                         instructions in the same order
   * @return {SecretShare} this party's share of the result under the new threshold, or null if this party is not a receiver
   */
  jiffClient.reshare = reshareProtocol.bind(null, jiffClient);

  /**
   * Open a secret share to reconstruct secret.
   * @method open
   * @memberof module:jiff-client~JIFFClient
   * @instance
   * @param {SecretShare} share - this party's share of the secret to reconstruct.
   * @param {Array} [parties=all_parties] - an array with party ids (1 to n) of receiving parties.
   * @param {string|number} [op_id=auto_gen()] - the operation id to be used to tag outgoing messages.
   * @returns {?promise} a (JQuery) promise to the open value of the secret, null if the party is not specified in the parties array as a receiver.
   * @example
   * var shares = jiff_instance.share(input);
   * //multiply the inputs of party 1 and 2 together
   * var result = shares[1].mult(shares[2]);
   * // reveal the result of the multiplication to all parties
   * return jiff_instance.open(result);
   */
  jiffClient.open = openProtocol.jiff_open.bind(null, jiffClient);

  /**
   * Same as open, but used by internal JIFF primitives/protocols, do not override this!
   * @see {@link open}
   * @method internal_open
   * @instance
   * @memberof module:jiff-client~JIFFClient
   */
  jiffClient.internal_open = jiffClient.open;

  /**
   * Receive shares from the specified parties and reconstruct their secret.
   * Use this function in a party that will receive some answer/value but does not have a share of it.
   * @method receive_open
   * @memberof module:jiff-client~JIFFClient
   * @instance
   * @param {Array} senders - an array with party ids (1 to n) specifying the parties sending the shares, this must be provided!
   * @param {Array} [receivers=all_parties] - an array with party ids (1 to n) specifying the parties receiving the result
   * @param {number} [threshold=senders.length] - the min number of parties needed to reconstruct the secret, defaults to all the senders
   * @param {number} [Zp=jiff_instance.Zp] - the mod (if null then the default Zp for the instance is used)
   * @param {string|number} [op_id=auto_Gen()] - same as jiff-instance.open
   * @returns {!promise} a (JQuery) promise to the open value of the secret.
   */
  jiffClient.receive_open = function (senders, receivers, threshold, Zp, op_id) {
    if (senders == null) {
      throw new Error('Must provide "senders" parameter in receive_open');
    }

    jiffClient.helpers.sort_ids(senders);
    if (receivers == null) {
      receivers = [];
      for (var i = 1; i <= jiffClient.party_count; i++) {
        receivers.push(i);
      }
    } else {
      jiffClient.helpers.sort_ids(receivers);
    }
    if (Zp == null) {
      Zp = jiffClient.Zp;
    }
    if (threshold == null) {
      threshold = senders.length;
    }

    var imitationSecretShare = new jiffClient.SecretShare({}, senders, threshold, Zp);
    return jiffClient.open(imitationSecretShare, receivers, op_id);
  };

  /**
   * Receive arrays of shares from the specified parties and reconstruct their secrets.
   * Use this function in a party that will receive some answer/value but does not have a share of it.
   * @method receive_open
   * @memberof module:jiff-client~JIFFClient
   * @instance
   * @param {Array} senders - an array with party ids (1 to n) specifying the parties sending the shares, this must be provided!
   * @param {number} [threshold=senders.length] - the min number of parties needed to reconstruct the secret, defaults to all the senders
   * @param {number} [Zp=jiff_instance.Zp] - the mod (if null then the default Zp for the instance is used)
   * @param {string|number} [op_id=auto_Gen()] - same as jiff-instance.open
   * @returns {!promise} a (JQuery) promise to the open value of the secret.
   */
  jiffClient.receive_open_array = arraysSharing.jiff_receive_open_ND_array.bind(null, jiffClient);

  /**
   * Share an array of values. Each sender may have an array of different length. This is handled by the lengths parameter.
   * This function will reveal the lengths of the shared array.
   *
   * If parties would like to keep the lengths of their arrays secret, they should agree on some public "max" length apriori (either under MPC
   * or as part of the logistics of the computation), all their arrays should be padded to that length by using appropriate default/identity
   * values
   * @method share_array
   * @memberof module:jiff-client~JIFFClient
   * @instance
   * @param {Array} array - the array to be shared.
   * @param {null|number|object} [lengths] - the lengths of the arrays to be shared, has the following options: <br>
   *                                       1. null: lengths are unknown, each sender will publicly reveal the lengths of its own array. <br>
   *                                       2. number: all arrays are of this length <br>
   *                                       3. object: { <sender_party_id>: length }: must specify the length of the array for each sender. <br>
   * @param {number} [threshold=receivers_list.length] - the min number of parties needed to reconstruct the secret, defaults to all the receivers.
   * @param {Array} [receivers_list=all_parties] - array of party ids to share with, by default, this includes all parties.
   * @param {Array} [senders_list=all_parties] - array of party ids to receive from, by default, this includes all parties.
   * @param {number} [Zp=jiff_instance.Zp] - the mod (if null then the default Zp for the instance is used).
   * @param {string|number} [share_id=auto_gen()] - the base tag used to tag the messages sent by this share operation, every element of the array
   *                                   will get a unique id based on the concatenation of base_share_id and the index of the element.
   *                                   This tag is used so that parties distinguish messages belonging to this share operation from
   *                                   other share operations between the same parties (when the order of execution is not
   *                                   deterministic). An automatic id is generated by increasing a local counter, default
   *                                   ids suffice when all parties execute all sharing operations with the same senders
   *                                   and receivers in the same order.
   * @returns {?promise} if the calling party is a receiver then a promise to the shared arrays is returned, the promise will provide an object
   *                    formatted as follows: { <party_id>: [ <1st_share>, <2nd_share>, ..., <(lengths[party_id])th_share> ] }
   *                    where the party_ids are those of the senders.
   *                    if the calling party is not a receiver, then null is returned.
   */
  jiffClient.share_array = arraysSharing.jiff_share_array.bind(null, jiffClient);

  /**
   * Share an array of values. Each sender may have an array of different length. This is handled by the lengths parameter.
   * This function will reveal the lengths of the shared array.
   *
   * If parties would like to keep the lengths of their arrays secret, they should agree on some "max" length apriori (either under MPC
   * or as part of the logistics of the computation), all their arrays should be padded to that length by using appropriate default/identity
   * values.
   * @method share_2D_array
   * @memberof module:jiff-client~JIFFClient
   * @instance
   * @param {Array} array - the array to be shared.
   * @param {null|number|object} lengths - the lengths of the arrays to be shared. For this to work successfully, the
   *                                       same exact value must be used in the calling code for each party. Any missing
   *                                       lengths for a row will be automatically publicly revealed by this function.
   *                                       Must have the following format:
   *                                       1. null: lengths are unknown, each sender will publicly reveal the lengths of its own array.
   *                                       2. { rows: <number>, cols: <number>, 0: <number>, 1: <number>, ...}: all parties have arrays
   *                                          with the given number of rows and cols. In case of jagged 2D arrays, different rows
   *                                          can have a different number of cols specified by using <row_index>: <col_size>.
   *                                          rows is mandatory, cols and any other number matching a specific row are optional.
   *                                       3. { <sender_party_id>: <length_object> }: must specify the lengths for each party by using
   *                                          an object with the same format as 2. Must include every party.
   * @param {number} [threshold=receivers_list.length] - the min number of parties needed to reconstruct the secret, defaults to all the receivers.
   * @param {Array} [receivers_list=all_parties] - array of party ids to share with, by default, this includes all parties.
   * @param {Array} [senders_list=all_parties] - array of party ids to receive from, by default, this includes all parties.
   * @param {number} [Zp=jiff_instance.Zp] - the mod (if null then the default Zp for the instance is used).
   * @param {string|number} [share_id=auto_gen()] - the base tag used to tag the messages sent by this share operation, every element of the array
   *                                   will get a unique id based on the concatenation of base_share_id and the index of the element.
   *                                   This tag is used so that parties distinguish messages belonging to this share operation from
   *                                   other share operations between the same parties (when the order of execution is not
   *                                   deterministic). An automatic id is generated by increasing a local counter, default
   *                                   ids suffice when all parties execute all sharing operations with the same senders
   *                                   and receivers in the same order.
   * @returns {promise} if the calling party is a receiver then a promise to the shared arrays is returned, the promise will provide an object
   *                    formatted as follows: { <party_id>: [ [ <1st_row_shares> ], [<2nd_row_share> ], ..., [ <(lengths[party_id])th_row_shares> ] ] }
   *                    where the party_ids are those of the senders.
   *                    if the calling party is not a receiver, then null is returned.
   */
  jiffClient.share_2D_array = arraysSharing.jiff_share_2D_array.bind(null, jiffClient);

  /**
   * Share an n-dimensional array of secrets
   * The arrays can be of different lengths and dimensions.
   * @method share_ND_array
   * @memberof module:jiff-client~JIFFClient
   * @instance
   * @returns {object|promise}
   */
  jiffClient.share_ND_array = arraysSharing.jiff_share_ND_array.bind(null, jiffClient);

  /**
   * Helper function of share_ND_array
   * This method gets called if share_ND_array does not have enough information
   *                 to output an array of shares immediately.  Instead it
   *                 returns a promise to an object holding the unknown-size
   *                 arrays from each sender.
   * @method share_ND_array_deferred
   * @memberof module:jiff-client~JIFFClient
   * @instance
   * @returns {promise}
   */
  //jiffClient.share_ND_array_deferred = arraysSharing.jiff_share_ND_array_deferred.bind(null, jiffClient);

  /**
   * Helper function of share_ND_array
   * This method gets called if share_ND_array when share_ND_array was given
   *                 sufficients information (in the form of array skeletons) to
   *                 infer the size and shape of each of the senders' arrays.
   *                 This allows it to immediately return an object containing
   *                 the secret-shared arrays from each sender.
   * @method share_ND_array_static
   * @memberof module:jiff-client~JIFFClient
   * @instance
   * @returns {object}
   */
  //jiffClient.share_ND_array_static = arraysSharing.jiff_share_ND_array_static.bind(null, jiffClient);

  /*
   * Wipe a secret array of all secrets but preserve the shape
   */
  jiffClient.skeleton_of = arraysSharing.jiff_skeleton_of.bind(null, jiffClient);

  /**
   * Opens an array of secret shares.
   * @method open_array
   * @memberof module:jiff-client~JIFFClient
   * @instance
   * @param {SecretShare[]} shares - an array containing this party's shares of the secrets to reconstruct.
   * @param {Array<number|string|Array>} [parties=all_parties] - an array with party ids of receiving parties.
   *                          This must be one of 3 cases:
   *                          1. null:                       open all shares to all parties.
   *                          2. array of numbers:           open all shares to all the parties specified in the array.
   *                          3. array of array of numbers:  open share with index i to the parties specified
   *                                                         in the nested array at parties[i]. if parties[i] was null,
   *                                                         then shares[i] will be opened to all parties.
   * @param {string|number|object} [op_ids=auto_gen()] - an optional mapping that specifies the ID/Tag associated with each
   *                                        open message sent. Since open_array involves sending many messages per party,
   *                                        this parameter only specifies the BASE OPERATION ID. Each message sent will
   *                                        have this base id attached to it concatenated to a counter.
   *                                        If this is an object, then it should map an id of a receiving parties
   *                                        to the base op_id that should be used to tag the messages sent to that party.
   *                                        Parties left unmapped by this object will get an automatically generated id.
   *                                        If this is a number/string, then it will be used as the base id tagging all messages
   *                                        sent by this open to all parties.
   *                                        You can safely ignore this unless you have multiple opens each containing other opens.
   *                                        In that case, the order by which these opens are executed is not fully deterministic
   *                                        and depends on the order of arriving messages. In this case, use this parameter
   *                                        with every nested_open, to ensure ids are unique and define a total ordering on
   *                                        the execution of the opens (check implementation of slt for an example).
   * @returns {promise} a (JQuery) promise to ALL the open values of the secret, the promise will yield
   *                    a 2D array of values, each corresponding to the given share in the shares parameter
   *                    at the same index. In the case where different values are opened to different parties, the order
   *                    of the values will be preserved, but not the indices, there will be no blanks in the resulting arrays,
   *                    the first share that is opened to this party will appear at index [0], even if it was not initially
   *                    at [0].
   * @throws error if some shares does not belong to the passed jiff instance.
   */
  jiffClient.open_array = arraysSharing.jiff_open_array.bind(null, jiffClient);

  /**
   * Opens an n-dimensional array of secret shares.
   * @method open_ND_array
   * @memberof module:jiff-client~JIFFClient
   * @instance
   * @param {SecretShare|SecretShare[]|SecretShare[][]} shares - an n-dimensional array containing this party's shares of the secrets to reconstruct.
   * @param {Array[]} [parties=all_parties] - an array with party ids (1 to n) of receiving parties.
   * @param {string|number|object} [op_ids=auto_gen()] - an optional ID/Tag associated with these open operations
   * @returns {promise} a (JQuery) promise to ALL the open values of the secret, the promise will yield
   *                    an n-dimensional array of values, each corresponding to the given share in the shares parameter
   *                    at the same index. In the case where different values are opened to different parties, the order
   *                    of the values will be preserved, but not the indices, there will be no blanks in the resulting arrays,
   *                    the first share that is opened to this party will appear at indices [0][0], even if it was not initially
   *                    at [0][0].
   * @throws error if some shares does not belong to the passed jiff instance.
   */
  jiffClient.open_ND_array = arraysSharing.jiff_open_ND_array.bind(null, jiffClient);
};