diff --git a/demos/run-test.sh b/demos/run-test.sh
index 79e9009ec..2bffdb6df 100755
--- a/demos/run-test.sh
+++ b/demos/run-test.sh
@@ -6,7 +6,7 @@ if [ "$1" == "*" ]; then
EXIT_CODE=0
for i in demos/*; do
if [ -f "$i/test.js" ]; then
- if ! [[ "$i" =~ ^demos/(pca|mpc-web|template|mpc-as-a-service)$ ]]; then
+ if ! [[ "$i" =~ ^demos/(pca|restAPI|template|mpc-as-a-service)$ ]]; then
sleep 2
npm run-script test-demo -- "$i"
CODE=$?
diff --git a/lib/jiff-client.js b/lib/jiff-client.js
index fe9784371..16f5fd186 100644
--- a/lib/jiff-client.js
+++ b/lib/jiff-client.js
@@ -859,14 +859,19 @@
jiff.emit(share_id + 'length', receivers_list, array.length.toString(10));
}
- jiff.listen(share_id + 'length', function (sender, message) {
- lengths[sender] = parseInt(message, 10);
- total++;
- if (total === senders_list.length) {
- jiff.remove_listener(share_id + 'length');
- share_array_deferred.resolve(lengths);
- }
- });
+ if (receivers_list.indexOf(jiff.id) > -1) {
+ jiff.listen(share_id + 'length', function (sender, message) {
+ lengths[sender] = parseInt(message, 10);
+ total++;
+ if (total === senders_list.length) {
+ jiff.remove_listener(share_id + 'length');
+ share_array_deferred.resolve(lengths);
+ }
+ });
+ } else if (senders_list[0] === jiff.id && senders_list.length === 1) {
+ lengths[jiff.id] = array.length;
+ share_array_deferred.resolve(lengths);
+ }
} else if (typeof(lengths) === 'number') {
// All arrays are of the same length
var l = lengths;
diff --git a/lib/jiff-server.js b/lib/jiff-server.js
index c302f0714..3efbdaaf7 100644
--- a/lib/jiff-server.js
+++ b/lib/jiff-server.js
@@ -358,7 +358,7 @@ exports.make_jiff = function (http, options) {
};
jiff.custom = function (computation_id, from_id, msg) {
- jiff.hooks.log(jiff, 'custom from ' + computation_id + '-' + from_id + ' : ' + msg);
+ jiff.hooks.log(jiff, 'custom from ' + computation_id + '-' + from_id + ' : ' + JSON.stringify(msg));
try {
msg = jiff.execute_array_hooks('beforeOperation', [jiff, 'custom', computation_id, from_id, msg], 4);
diff --git a/package.json b/package.json
index 918fda7db..f8665d882 100644
--- a/package.json
+++ b/package.json
@@ -67,6 +67,7 @@
"jsdoc": "^3.5.5",
"marked": "^0.3.9",
"minimist": "^1.2.0",
- "mocha": "^4.0.1"
+ "mocha": "^4.0.1",
+ "nunjucks": "^3.1.7"
}
}
diff --git a/presentation/least-squares/analyst/analyst.html b/presentation/least-squares/analyst/analyst.html
new file mode 100644
index 000000000..d25e4becc
--- /dev/null
+++ b/presentation/least-squares/analyst/analyst.html
@@ -0,0 +1,52 @@
+
+
+
+
+ Linear Least Squares
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Analyst
+ Analysis:
+
+ Output:
+
+
+
+
+
diff --git a/presentation/least-squares/analyst/analyst.js b/presentation/least-squares/analyst/analyst.js
new file mode 100644
index 000000000..a488d6d5d
--- /dev/null
+++ b/presentation/least-squares/analyst/analyst.js
@@ -0,0 +1,103 @@
+/**
+ * Do not modify this file unless you have to.
+ * This file has UI handlers.
+ */
+
+// eslint-disable-next-line no-unused-vars
+var computes = [];
+function connect(party_id, party_count, compute_count, computation_id) {
+ // Compute parties ids
+ for (var c = 1; c <= compute_count; c++) {
+ computes.push(c);
+ }
+
+ // jiff options
+ var options = { party_count: party_count, party_id: party_id };
+ options.onError = function (error) {
+ $('#output').append(''+error+'
');
+ };
+ options.onConnect = function () {
+ $('#output').append('Connected!
');
+ };
+
+ // host name
+ var hostname = window.location.hostname.trim();
+ var port = window.location.port;
+ if (port == null || port === '') {
+ port = '80';
+ }
+ if (!(hostname.startsWith('http://') || hostname.startsWith('https://'))) {
+ hostname = 'http://' + hostname;
+ }
+ if (hostname.endsWith('/')) {
+ hostname = hostname.substring(0, hostname.length-1);
+ }
+ if (hostname.indexOf(':') > -1 && hostname.lastIndexOf(':') > hostname.indexOf(':')) {
+ hostname = hostname.substring(0, hostname.lastIndexOf(':'));
+ }
+
+ hostname = hostname + ':' + port;
+
+ // eslint-disable-next-line no-undef
+ mpc.connect(hostname, computation_id, options, computes, function (id, cols) {
+ var schema = 'Party ' + (id - computes.length - 1) + ': ' + cols.join(' | ') + '
';
+ document.getElementById('schema').innerHTML += schema;
+ });
+}
+
+// eslint-disable-next-line no-unused-vars
+function submit() {
+ $('#output').append('Starting...
');
+ $('#submit').attr('disabled', true);
+
+ // eslint-disable-next-line no-undef
+ mpc.compute().then(plot);
+}
+
+/**
+ * Helpers for drawing lines in a chart
+ */
+// The limits of the graph. The minimum and maximum X and Y values.
+var minX = -60;
+var maxX = 60;
+var minY = -60;
+var maxY = 60;
+
+// Chart for drawing
+function initChart(line) {
+ var ctx = document.getElementById('myChart').getContext('2d');
+
+ var scales = {
+ yAxes: [{ ticks: { min: minY, max: maxY, maxTicksLimit: 5 } }],
+ xAxes: [{ type: 'linear', position: 'bottom', ticks: { min: minX, max: maxX, maxTicksLimit: 20 } }]
+ };
+
+ // eslint-disable-next-line no-undef
+ var myChart = new Chart(ctx, {
+ type: 'line',
+ data: {
+ datasets: [
+ { id: 'least-line', label: '', data: line, fill: false, pointBackgroundColor: '#000099', borderColor: '#000099', pointRadius: 0 }
+ ]
+ },
+ options: {
+ elements: { line: { tension: 1 } },
+ scales: scales
+ }
+ });
+ myChart.update();
+}
+
+function plot(data) {
+ console.log(data.slope.toString(), data.yIntercept.toString());
+ var m = data.slope;
+ var p = data.yIntercept;
+
+ var points = [];
+ for (var i = minX; i <= maxX; i+=1) {
+ points.push({x: i, y: m.times(i).plus(p).toNumber()});
+ }
+
+ $('#output').show();
+ initChart(points);
+}
\ No newline at end of file
diff --git a/presentation/least-squares/analyst/mpc.js b/presentation/least-squares/analyst/mpc.js
new file mode 100644
index 000000000..8b8825d00
--- /dev/null
+++ b/presentation/least-squares/analyst/mpc.js
@@ -0,0 +1,36 @@
+(function (exports) {
+ var jiff_instance;
+ var computes;
+
+ /**
+ * Connect to the server and initialize the jiff instance
+ */
+ exports.connect = function (hostname, computation_id, options, _computes, schema_callback) {
+ computes = _computes;
+ var opt = Object.assign({}, options);
+ opt.Zp = '4503599627370449';
+ opt.integer_digits = 9;
+ opt.decimal_digits = 3;
+
+ // eslint-disable-next-line no-undef
+ jiff_instance = jiff.make_jiff(hostname, computation_id, opt);
+ // eslint-disable-next-line no-undef
+ jiff_instance.apply_extension(jiff_bignumber, opt);
+ // eslint-disable-next-line no-undef
+ jiff_instance.apply_extension(jiff_fixedpoint, opt);
+ // eslint-disable-next-line no-undef
+ jiff_instance.apply_extension(jiff_negativenumber, opt);
+ };
+
+ /**
+ * The MPC computation
+ */
+ exports.compute = function () {
+ var promise1 = jiff_instance.receive_open(computes);
+ var promise2 = jiff_instance.receive_open(computes);
+
+ return Promise.all([promise1, promise2]).then(function (res) {
+ return {slope: res[0], yIntercept: res[1]};
+ });
+ };
+}(this.mpc = {}));
diff --git a/presentation/least-squares/compute/mpc.js b/presentation/least-squares/compute/mpc.js
new file mode 100644
index 000000000..a918ef813
--- /dev/null
+++ b/presentation/least-squares/compute/mpc.js
@@ -0,0 +1,96 @@
+// Configurations
+var decimal_digits = 3;
+
+var config = require('../config.json');
+var computes = [];
+for (var c = 1; c <= config.compute; c++) {
+ computes.push(c);
+}
+var analyst = computes.length + 1;
+var inputs = [];
+for (var i = config.compute + 2; i <= config.total; i++) {
+ inputs.push(i);
+}
+
+/**
+ * Connect to the server and initialize the jiff instance
+ */
+exports.connect = function (hostname, computation_id, options) {
+ var opt = Object.assign({}, options);
+ opt.Zp = '4503599627370449';
+ opt.integer_digits = 9;
+ opt.decimal_digits = decimal_digits;
+
+ var jiff = require('../../../lib/jiff-client');
+ var jiff_bignumber = require('../../../lib/ext/jiff-client-bignumber');
+ var jiff_fixedpoint = require('../../../lib/ext/jiff-client-fixedpoint');
+ var jiff_negativenumber = require('../../../lib/ext/jiff-client-negativenumber');
+
+ var jiff_instance = jiff.make_jiff(hostname, computation_id, opt);
+ jiff_instance.apply_extension(jiff_bignumber, opt);
+ jiff_instance.apply_extension(jiff_fixedpoint, opt);
+ jiff_instance.apply_extension(jiff_negativenumber, opt);
+ jiff_instance.wait_for(computes, function () {
+ compute(jiff_instance);
+ });
+ jiff_instance.wait_for(['s1'], function () {
+ console.log('This is compute party ', jiff_instance.id);
+ })
+};
+
+/**
+ * The MPC computation
+ */
+function compute(jiff_instance) {
+ var avgs = jiff_instance.share(null, computes.length, computes, inputs);
+ var xAvg = avgs[inputs[0]];
+ var yAvg = avgs[inputs[1]];
+
+ var xBar = jiff_instance.share_array([], null, computes.length, computes, [inputs[0]]);
+ var yBar = jiff_instance.share_array([], null, computes.length, computes, [inputs[1]]);
+ xBar.then(function (arr) {
+ console.log('First input party secret shared their data!');
+ var share = arr[inputs[0]][0];
+ if (share.value != null) {
+ console.log('First secret share value ', share.value.toString());
+ } else {
+ share.promise.then(function () {
+ console.log('First secret share value ', share.value.toString());
+ });
+ }
+ });
+ yBar.then(function (arr) {
+ console.log('Second input party secret shared their data!');
+ var share = arr[inputs[1]][0];
+ if (share.value != null) {
+ console.log('First secret share value ', share.value.toString());
+ } else {
+ share.promise.then(function () {
+ console.log('First secret share value ', share.value.toString());
+ });
+ }
+ });
+
+ var xBarSquare = jiff_instance.share(null, computes.length, computes, [inputs[0]])[inputs[0]];
+
+ Promise.all([xBar, yBar]).then(function (res) {
+ xBar = res[0][inputs[0]];
+ yBar = res[1][inputs[1]];
+
+ var numerator = xBar[0].smult(yBar[0], null, false);
+ for (var i = 1; i < xBar.length; i++) {
+ numerator = numerator.sadd(xBar[i].smult(yBar[i], null, false));
+ }
+ var denumerator = xBarSquare.cmult(jiff_instance.helpers.magnitude(decimal_digits));
+
+ // Compute slope and y Intercept of line
+ var slope = numerator.sdiv(denumerator);
+ var yIntercept = yAvg.ssub(slope.smult(xAvg, 'main-smult'));
+
+ // Done
+ jiff_instance.wait_for([analyst], function () {
+ jiff_instance.open(slope, [analyst]);
+ jiff_instance.open(yIntercept, [analyst]);
+ });
+ });
+}
diff --git a/presentation/least-squares/compute/party.js b/presentation/least-squares/compute/party.js
new file mode 100644
index 000000000..cf3466b4d
--- /dev/null
+++ b/presentation/least-squares/compute/party.js
@@ -0,0 +1,16 @@
+/**
+ * Do not change this unless you have to.
+ * This code parses input command line arguments,
+ * and calls the appropriate initialization and MPC protocol from ./mpc.js
+ */
+var mpc = require('./mpc');
+var config = require('../config.json');
+
+// JIFF options
+var options = {
+ initialization: {role: 'compute'},
+ party_count: config.total
+};
+
+// Connect
+mpc.connect('http://localhost:8080', config.computation_id, options);
diff --git a/presentation/least-squares/config.json b/presentation/least-squares/config.json
new file mode 100644
index 000000000..cf6e14706
--- /dev/null
+++ b/presentation/least-squares/config.json
@@ -0,0 +1,5 @@
+{
+ "computation_id": "test",
+ "compute": 2,
+ "total": 5
+}
\ No newline at end of file
diff --git a/presentation/least-squares/input/input.html b/presentation/least-squares/input/input.html
new file mode 100644
index 000000000..5381de14a
--- /dev/null
+++ b/presentation/least-squares/input/input.html
@@ -0,0 +1,78 @@
+
+
+
+
+ Linear Least Squares
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Input party {{ id - config.compute - 1}}
+ Linear Least Squares
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/presentation/least-squares/input/input.js b/presentation/least-squares/input/input.js
new file mode 100644
index 000000000..5a88e1a4d
--- /dev/null
+++ b/presentation/least-squares/input/input.js
@@ -0,0 +1,103 @@
+/**
+ * Do not modify this file unless you have to.
+ * This file has UI handlers.
+ */
+
+// eslint-disable-next-line no-unused-vars
+var computes = [];
+function connect(party_id, party_count, compute_count, computation_id) {
+ def = defaults[party_id];
+
+ // Compute parties ids
+ for (var c = 1; c <= compute_count; c++) {
+ computes.push(c);
+ }
+
+ // jiff options
+ var options = { party_count: party_count, party_id: party_id };
+ options.onError = function (error) {
+ $('#output').append(''+error+'
');
+ };
+
+ // host name
+ var hostname = window.location.hostname.trim();
+ var port = window.location.port;
+ if (port == null || port === '') {
+ port = '80';
+ }
+ if (!(hostname.startsWith('http://') || hostname.startsWith('https://'))) {
+ hostname = 'http://' + hostname;
+ }
+ if (hostname.endsWith('/')) {
+ hostname = hostname.substring(0, hostname.length-1);
+ }
+ if (hostname.indexOf(':') > -1 && hostname.lastIndexOf(':') > hostname.indexOf(':')) {
+ hostname = hostname.substring(0, hostname.lastIndexOf(':'));
+ }
+
+ hostname = hostname + ':' + port;
+
+ // eslint-disable-next-line no-undef
+ var jiff_instance = mpc.connect(hostname, computation_id, options, computes);
+
+ // Display connection
+ jiff_instance.wait_for(computes, function () {
+ $('#output').append('Connected!
');
+ });
+}
+
+// eslint-disable-next-line no-unused-vars
+function submit() {
+ $('#output').append('Starting...
');
+ $('#submit').attr('disabled', true);
+
+ var parsed = parseInput();
+ // eslint-disable-next-line no-undef
+ mpc.compute(parsed);
+
+ $('#output').append('Shared data successfully!
');
+}
+
+/**
+ * Helpers for HTML data generation and parsing
+ */
+function generateTable(points, id) {
+ $('#generate').attr('disabled', true);
+ $('#submit').attr('disabled', false);
+
+ var table = '
';
+ $('#input_area').html(table);
+}
+function parseInput() {
+ var data = [];
+
+ var table = document.getElementById('input_table');
+
+ // body
+ for (var i = 2; i < table.rows.length; i++) {
+ var row = table.rows[i];
+ data.push(Number(row.getElementsByTagName('input')[0].value));
+ }
+
+ return data;
+}
+
+var defaults = {
+ 4: [ 1, 10, -5.3, -30.3 ],
+ 5: [ 2, 22.1, -9.813, -45.2 ]
+};
+var def;
\ No newline at end of file
diff --git a/presentation/least-squares/input/mpc.js b/presentation/least-squares/input/mpc.js
new file mode 100644
index 000000000..f5e0f4847
--- /dev/null
+++ b/presentation/least-squares/input/mpc.js
@@ -0,0 +1,53 @@
+/* global BigNumber */
+(function (exports) {
+ var jiff_instance;
+ var computes;
+
+ /**
+ * Connect to the server and initialize the jiff instance
+ */
+ exports.connect = function (hostname, computation_id, options, _computes) {
+ computes = _computes;
+ var opt = Object.assign({}, options);
+ opt.Zp = '4503599627370449';
+ opt.integer_digits = 9;
+ opt.decimal_digits = 3;
+
+ // eslint-disable-next-line no-undef
+ jiff_instance = jiff.make_jiff(hostname, computation_id, opt);
+ // eslint-disable-next-line no-undef
+ jiff_instance.apply_extension(jiff_bignumber, opt);
+ // eslint-disable-next-line no-undef
+ jiff_instance.apply_extension(jiff_fixedpoint, opt);
+ // eslint-disable-next-line no-undef
+ jiff_instance.apply_extension(jiff_negativenumber, opt);
+ return jiff_instance;
+ };
+
+ /**
+ * The MPC computation
+ */
+ exports.compute = function (data) {
+ var avg = 0;
+ for (var i = 0; i < data.length; i++) {
+ avg += data[i];
+ }
+ avg = new BigNumber(avg.toString()).div(data.length);
+
+ // Secret share average
+ jiff_instance.share(avg, computes.length, computes, [computes.length + 2, computes.length + 3]);
+
+ var bar = [];
+ var squared = new BigNumber(0);
+ for (var j = 0; j < data.length; j++) {
+ bar[j] = new BigNumber(data[j].toString()).minus(avg);
+ squared = squared.plus(bar[j].times(bar[j]));
+ }
+
+ // Secret share array of (avg - diff)
+ jiff_instance.share_array(bar, null, computes.length, computes, [ jiff_instance.id ]);
+
+ // Secret share sum of difference square (denumerator), only used for x.
+ jiff_instance.share(squared, computes.length, computes, [ jiff_instance.id ]);
+ };
+}(this.mpc = {}));
diff --git a/presentation/least-squares/server.js b/presentation/least-squares/server.js
new file mode 100644
index 000000000..78360f2b6
--- /dev/null
+++ b/presentation/least-squares/server.js
@@ -0,0 +1,66 @@
+var express = require('express');
+var app = express();
+
+var http = require('http').Server(app);
+http.listen(8080, function () {
+ console.log('listening on *:8080');
+});
+
+// nunjucks for rendering html template
+var nunjucks = require('nunjucks');
+nunjucks.configure(__dirname, {
+ autoescape: true,
+ express: app
+});
+
+// read configurations
+var config = require('./config.json');
+
+//Serve static files
+app.use('/bignumber.js', express.static(__dirname + '/../../node_modules/bignumber.js'));
+app.use('/lib', express.static(__dirname + '/../../lib'));
+
+// Server input and analyst files
+app.use('/static/input', express.static(__dirname + '/input'));
+app.use('/static/analyst', express.static(__dirname + '/analyst'));
+app.get('/input/:id', function (req, res) {
+ var party_id = parseInt(req.params.id, 10);
+ res.render('input/input.html', {id: party_id + config.compute + 1, config: config});
+});
+app.get('/analyst', function (req, res) {
+ res.render('analyst/analyst.html', {id: config.compute + 1, config: config});
+});
+
+// Manage roles / IDS
+// Keep track of assigned ids
+var assignedCompute = 1;
+var initialize = function (jiff, computation_id, msg, params) {
+ if (params.party_id != null) {
+ return params;
+ }
+
+ if (msg.role === 'input') {
+ return params;
+ }
+ if (msg.role === 'analyst') {
+ params['party_id'] = config.compute + 1;
+ return params;
+ }
+ if (msg.role === 'compute' && assignedCompute <= config.compute) {
+ params['party_id'] = assignedCompute;
+ assignedCompute++;
+ return params;
+ }
+
+ throw new Error('Unrecognized role and party id options');
+};
+
+var jiff_instance = require('../../lib/jiff-server.js').make_jiff(http, {logs: true, hooks: { beforeInitialization: [initialize] }});
+jiff_instance.apply_extension(require('../../lib/ext/jiff-server-bignumber.js'));
+
+
+// Instructions
+console.log('Direct your browser to http://localhost/input/ for inputing data');
+console.log('Direct your browser to http://localhost/analyst for analyst UI');
+console.log('Run compute parties with node compute/party.js');
+console.log();
\ No newline at end of file
diff --git a/presentation/relational/analyst/analyst.html b/presentation/relational/analyst/analyst.html
new file mode 100644
index 000000000..0c29568c9
--- /dev/null
+++ b/presentation/relational/analyst/analyst.html
@@ -0,0 +1,57 @@
+
+
+
+
+ Relational MPC Workflow
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Analyst
+ Input Parties Schemas:
+
+
+ Analysis:
+
+
+
+
+ Output:
+
+
+
+
+
diff --git a/presentation/relational/analyst/analyst.js b/presentation/relational/analyst/analyst.js
new file mode 100644
index 000000000..f82d80c45
--- /dev/null
+++ b/presentation/relational/analyst/analyst.js
@@ -0,0 +1,86 @@
+/**
+ * Do not modify this file unless you have to.
+ * This file has UI handlers.
+ */
+
+// eslint-disable-next-line no-unused-vars
+var computes = [];
+function connect(party_id, party_count, compute_count, computation_id) {
+ // Compute parties ids
+ for (var c = 1; c <= compute_count; c++) {
+ computes.push(c);
+ }
+
+ // jiff options
+ var options = { party_count: party_count, party_id: party_id };
+ options.onError = function (error) {
+ $('#output').append(''+error+'
');
+ };
+ options.onConnect = function () {
+ $('#output').append('Connected!
');
+ };
+
+ // host name
+ var hostname = window.location.hostname.trim();
+ var port = window.location.port;
+ if (port == null || port === '') {
+ port = '80';
+ }
+ if (!(hostname.startsWith('http://') || hostname.startsWith('https://'))) {
+ hostname = 'http://' + hostname;
+ }
+ if (hostname.endsWith('/')) {
+ hostname = hostname.substring(0, hostname.length-1);
+ }
+ if (hostname.indexOf(':') > -1 && hostname.lastIndexOf(':') > hostname.indexOf(':')) {
+ hostname = hostname.substring(0, hostname.lastIndexOf(':'));
+ }
+
+ hostname = hostname + ':' + port;
+
+ // eslint-disable-next-line no-undef
+ mpc.connect(hostname, computation_id, options, computes, function (id, cols) {
+ var schema = 'Party ' + (id - computes.length - 1) + ': ' + cols.join(' | ') + '
';
+ document.getElementById('schema').innerHTML += schema;
+ });
+}
+
+// eslint-disable-next-line no-unused-vars
+function submit() {
+ $('#output').append('Starting...
');
+ $('#submit').attr('disabled', true);
+
+ // eslint-disable-next-line no-undef
+ generateTable(mpc.compute());
+}
+
+/**
+ * Helpers for HTML data generation and parsing
+ */
+function generateTable(result) {
+ result.promise.then(function (data) {
+ var cols = result.cols;
+
+ var table = '
';
+ $('#output').html(table);
+ });
+}
\ No newline at end of file
diff --git a/presentation/relational/analyst/mpc.js b/presentation/relational/analyst/mpc.js
new file mode 100644
index 000000000..71c24e2c4
--- /dev/null
+++ b/presentation/relational/analyst/mpc.js
@@ -0,0 +1,66 @@
+var GROUP_BY_DOMAIN = ['Group A', 'Group B', 'Group C'];
+
+(function (exports) {
+ var jiff_instance;
+ var computes;
+ var schemas = {};
+
+ /**
+ * Connect to the server and initialize the jiff instance
+ */
+ exports.connect = function (hostname, computation_id, options, _computes, schema_callback) {
+ computes = _computes;
+ var opt = Object.assign({}, options);
+
+ opt.Zp = '16381';
+ opt.integer_digits = 2;
+ opt.decimal_digits = 2;
+
+ // eslint-disable-next-line no-undef
+ jiff_instance = jiff.make_jiff(hostname, computation_id, opt);
+ // eslint-disable-next-line no-undef
+ jiff_instance.apply_extension(jiff_bignumber, opt);
+ // eslint-disable-next-line no-undef
+ jiff_instance.apply_extension(jiff_fixedpoint, opt);
+
+ jiff_instance.listen('headers', function (id, cols) {
+ cols = JSON.parse(cols);
+ schemas[id] = cols;
+
+ if (id > computes.length) {
+ schema_callback(id, cols);
+ }
+ });
+ };
+
+ /**
+ * The MPC computation
+ */
+ exports.compute = function () {
+ var inputs = [];
+ for (var i = computes.length + 2; i <= jiff_instance.party_count; i++) {
+ inputs.push(i);
+ }
+
+ // Schema
+ var output_schema = ['GROUP BY', 'AVG'];
+
+ // Receive result
+ var promises = [];
+ for (var g = 0; g < GROUP_BY_DOMAIN.length; g++) {
+ var promise1 = jiff_instance.receive_open(computes);
+ var promise2 = jiff_instance.receive_open(computes);
+
+ (function scope(g) {
+ promises.push(Promise.all([promise1, promise2]).then(function (res) {
+ var obj = {};
+ obj[output_schema[0]] = GROUP_BY_DOMAIN[g];
+ obj[output_schema[1]] = res[1] > 0 ? res[0] / res[1] : '-';
+ return obj;
+ }));
+ }(g));
+ }
+
+ return { promise: Promise.all(promises), cols: output_schema };
+ };
+}(this.mpc = {}));
diff --git a/presentation/relational/compute/join.js b/presentation/relational/compute/join.js
new file mode 100644
index 000000000..e4bd25c4e
--- /dev/null
+++ b/presentation/relational/compute/join.js
@@ -0,0 +1,51 @@
+var binary_search = function (jiff_instance, id, table, schema) {
+ var idCol = schema[0];
+ var valCol = schema[1];
+
+ while (table.length > 1) {
+ // comparison
+ var mid = Math.floor(table.length / 2);
+ var cmp = id.slt(table[mid][idCol]);
+
+ // Slice array in half, choose slice depending on cmp
+ var nTable = [];
+ for (var i = 0; i < mid; i++) {
+ var obj = {};
+ obj[idCol] = cmp.if_else(table[i][idCol], table[mid + i][idCol]);
+ obj[valCol] = cmp.if_else(table[i][valCol], table[mid + i][valCol]);
+ nTable.push(obj);
+ }
+
+ // watch out for off by 1 errors if length is odd.
+ if (2 * mid < table.length) {
+ var obj2 = {};
+ obj2[idCol] = table[2 * mid][idCol];
+ obj2[valCol] = table[2 * mid][valCol];
+ nTable.push(obj2);
+ }
+
+ table = nTable;
+ }
+
+ var row = table[0];
+ var found = row[idCol].seq(id);
+ var val = found.if_else(row[valCol], 0);
+ return { found: found, val: val };
+};
+
+module.exports = function (jiff_instance, table1, table2, schema1, schema2) {
+ for (var d = 0; d < table1.length; d++) {
+ var row = table1[d];
+ var id = row[schema1[0]];
+ var res = binary_search(jiff_instance, id, table2, schema2);
+
+ var found = res.found;
+ for (var i = 2; i < schema1.length; i++) {
+ var col = schema1[i];
+ row[col] = found.if_else(row[col], 0);
+ }
+ row[schema2[1]] = res.val;
+ }
+
+ return table1;
+};
\ No newline at end of file
diff --git a/presentation/relational/compute/mpc.js b/presentation/relational/compute/mpc.js
new file mode 100644
index 000000000..dae47ad6d
--- /dev/null
+++ b/presentation/relational/compute/mpc.js
@@ -0,0 +1,204 @@
+var FILTER_VALUE = 1;
+var GROUP_BY_DOMAIN = [1, 2, 3];
+
+// Configurations
+var config = require('../config.json');
+var computes = [];
+for (var c = 1; c <= config.compute; c++) {
+ computes.push(c);
+}
+var analyst = computes.length + 1;
+var inputs = [];
+for (var i = config.compute + 2; i <= config.total; i++) {
+ inputs.push(i);
+}
+
+// Schemas
+var schemas = {};
+var total = 0;
+
+/**
+ * Connect to the server and initialize the jiff instance
+ */
+exports.connect = function (hostname, computation_id, options) {
+ var opt = Object.assign({}, options);
+
+ // eslint-disable-next-line no-undef
+ opt.Zp = '16381';
+ opt.integer_digits = 2;
+ opt.decimal_digits = 2;
+
+ var jiff = require('../../../lib/jiff-client');
+ var jiff_bignumber = require('../../../lib/ext/jiff-client-bignumber');
+ var jiff_fixedpoint = require('../../../lib/ext/jiff-client-fixedpoint');
+
+ // eslint-disable-next-line no-undef
+ var jiff_instance = jiff.make_jiff(hostname, computation_id, opt);
+ // eslint-disable-next-line no-undef
+ jiff_instance.apply_extension(jiff_bignumber, opt);
+ // eslint-disable-next-line no-undef
+ jiff_instance.apply_extension(jiff_fixedpoint, opt);
+
+ jiff_instance.listen('headers', function (id, cols) {
+ cols = JSON.parse(cols);
+ schemas[id] = cols;
+
+ total++;
+ if (total === inputs.length) {
+ compute(jiff_instance);
+ }
+ });
+
+ jiff_instance.wait_for(['s1'], function () {
+ console.log('This is compute party ', jiff_instance.id);
+ });
+};
+
+function transform(table) {
+ var nTable = [];
+ for (var key in table) {
+ if (!table.hasOwnProperty(key)) {
+ continue;
+ }
+
+ for (var i = 0; i < table[key].length; i++) {
+ if (nTable[i] == null) {
+ nTable[i] = {};
+ }
+
+ nTable[i][key] = table[key][i];
+ }
+ }
+ return nTable;
+}
+
+/**
+ * The MPC computation
+ */
+function compute(jiff_instance) {
+ var join = require('./join.js');
+
+ // ids
+ var in0 = inputs[0];
+ var in1 = inputs[1];
+ var in2 = inputs[2];
+ var in3 = inputs[3];
+
+ // Receive shares
+ var promises = [];
+ for (var i = 0; i < inputs.length; i++) {
+ var input_id = inputs[i];
+ var party_promises = [];
+ for (var c = 0; c < schemas[input_id].length; c++) {
+ party_promises.push(jiff_instance.share_array([], null, computes.length, computes, [ input_id ]));
+ }
+
+ (function scope(input_id) {
+ var promise = Promise.all(party_promises).then(function (results) {
+
+ var matrix = {};
+ for (var k = 0; k < schemas[input_id].length; k++) {
+ var col = schemas[input_id][k];
+ matrix[col] = results[k][input_id];
+ }
+ return matrix;
+ });
+ promises.push(promise);
+ }(input_id));
+ }
+
+ // Begin Work
+ Promise.all(promises).then(function (results) {
+ // Formatting
+ var matrices = {};
+ for (var i = 0; i < inputs.length; i++) {
+ matrices[inputs[i]] = results[i];
+ }
+
+ // Concatenate first and second parties input
+ for (var c = 0; c < schemas[in0].length; c++) {
+ var col0 = schemas[in0][c];
+ var col1 = schemas[in1][c];
+ matrices[in1][col1] = matrices[in0][col0].concat(matrices[in1][col1]);
+ }
+ delete matrices[in0];
+ return matrices;
+
+ }).then(function (matrices) {
+
+ // projections
+ for (var p = 1; p < inputs.length; p++) {
+ var input_id = inputs[p];
+ for (var c = 2; c < schemas[input_id].length; c++) {
+ delete matrices[input_id][schemas[input_id][c]];
+ }
+
+ matrices[input_id] = transform(matrices[input_id]);
+ }
+
+ return matrices;
+
+ }).then(function (matrices) {
+ // have 3 tables, each containing [id, ]
+ var table = join(jiff_instance, matrices[in1], matrices[in2], schemas[in1], schemas[in2]);
+ table = join(jiff_instance, table, matrices[in3], schemas[in1].concat([schemas[in2][1]]), schemas[in3]);
+ return table;
+ }).then(function (table) {
+ // Output is an average and a group.
+ var output_schema = [schemas[in1][1], schemas[in3][1]];
+
+ // Filter
+ var filter_key = schemas[in2][1];
+ var avg_key = output_schema[0];
+ var group_key = output_schema[1];
+ for (var i = 0; i < table.length; i++) {
+ var row = table[i];
+ var outrow = {};
+
+ var condition = row[filter_key].ceq(FILTER_VALUE);
+ outrow[avg_key] = row[avg_key];
+ outrow[group_key] = condition.if_else(row[group_key], 0);
+
+ table[i] = outrow;
+ }
+ return table;
+
+ }).then(function (table) {
+ // Output is an average and a group.
+ var output_schema = [schemas[in1][1], schemas[in3][1]];
+
+ // Group by and average
+ var avg_col = [];
+ var count_col = [];
+
+ var avg_key = output_schema[0];
+ var group_key = output_schema[1];
+ for (var z = 0; z < GROUP_BY_DOMAIN.length; z++) {
+ avg_col.push(0);
+ count_col.push(0);
+ }
+
+ for (var d = 0; d < table.length; d++) {
+ var row = table[d];
+ for (var g = 0; g < GROUP_BY_DOMAIN.length; g++) {
+ var condition = row[group_key].ceq(GROUP_BY_DOMAIN[g]);
+ var if_ = condition.if_else(row[avg_key], 0);
+
+ avg_col[g] = if_.add(avg_col[g]);
+ count_col[g] = condition.add(count_col[g]);
+ }
+ }
+
+ return { avg: avg_col, count: count_col };
+ }).then(function (results) {
+ jiff_instance.wait_for([analyst], function () {
+ var output_schema = ['avg', 'count'];
+ // All is done, open the result.
+ for (var gi = 0; gi < GROUP_BY_DOMAIN.length; gi++) {
+ for (var si = 0; si < output_schema.length; si++) {
+ jiff_instance.open(results[output_schema[si]][gi], [analyst]);
+ }
+ }
+ });
+ });
+}
diff --git a/presentation/relational/compute/party.js b/presentation/relational/compute/party.js
new file mode 100644
index 000000000..37930f969
--- /dev/null
+++ b/presentation/relational/compute/party.js
@@ -0,0 +1,17 @@
+/**
+ * Do not change this unless you have to.
+ * This code parses input command line arguments,
+ * and calls the appropriate initialization and MPC protocol from ./mpc.js
+ */
+var mpc = require('./mpc');
+var config = require('../config.json');
+
+// JIFF options
+var options = {
+ initialization: {role: 'compute'},
+ party_count: config.total,
+ warn: false
+};
+
+// Connect
+mpc.connect('http://localhost:8080', config.computation_id, options);
diff --git a/presentation/relational/config.json b/presentation/relational/config.json
new file mode 100644
index 000000000..150f583f1
--- /dev/null
+++ b/presentation/relational/config.json
@@ -0,0 +1,5 @@
+{
+ "computation_id": "test",
+ "compute": 3,
+ "total": 8
+}
\ No newline at end of file
diff --git a/presentation/relational/input/input.html b/presentation/relational/input/input.html
new file mode 100644
index 000000000..3159e3f70
--- /dev/null
+++ b/presentation/relational/input/input.html
@@ -0,0 +1,128 @@
+
+
+
+
+ Relational MPC Workflow
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Input party {{ id - config.compute - 1}}
+ Join / Filter / Group / Average under MPC
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/presentation/relational/input/input.js b/presentation/relational/input/input.js
new file mode 100644
index 000000000..3beb4d97e
--- /dev/null
+++ b/presentation/relational/input/input.js
@@ -0,0 +1,139 @@
+/**
+ * Do not modify this file unless you have to.
+ * This file has UI handlers.
+ */
+
+// eslint-disable-next-line no-unused-vars
+var computes = [];
+function connect(party_id, party_count, compute_count, computation_id) {
+ def = defaults[party_id];
+ hed = headers[party_id];
+ document.getElementById('rows').value = def.length / 2;
+
+ // Compute parties ids
+ for (var c = 1; c <= compute_count; c++) {
+ computes.push(c);
+ }
+
+ // jiff options
+ var options = { party_count: party_count, party_id: party_id };
+ options.onError = function (error) {
+ $('#output').append(''+error+'
');
+ };
+
+ // host name
+ var hostname = window.location.hostname.trim();
+ var port = window.location.port;
+ if (port == null || port === '') {
+ port = '80';
+ }
+ if (!(hostname.startsWith('http://') || hostname.startsWith('https://'))) {
+ hostname = 'http://' + hostname;
+ }
+ if (hostname.endsWith('/')) {
+ hostname = hostname.substring(0, hostname.length-1);
+ }
+ if (hostname.indexOf(':') > -1 && hostname.lastIndexOf(':') > hostname.indexOf(':')) {
+ hostname = hostname.substring(0, hostname.lastIndexOf(':'));
+ }
+
+ hostname = hostname + ':' + port;
+
+ // eslint-disable-next-line no-undef
+ var jiff_instance = mpc.connect(hostname, computation_id, options, computes);
+
+ // Display connection
+ jiff_instance.wait_for(computes, function () {
+ $('#output').append('Connected!
');
+ });
+}
+
+// eslint-disable-next-line no-unused-vars
+function submit() {
+ $('#output').append('Starting...
');
+ $('#submit').attr('disabled', true);
+
+ var parsed = parseInput();
+ // eslint-disable-next-line no-undef
+ mpc.compute(parsed['cols'], parsed['data']);
+
+ $('#output').append('Shared data successfully!
');
+}
+
+/**
+ * Helpers for HTML data generation and parsing
+ */
+function generateTable(cols, rows) {
+ $('#generate').attr('disabled', true);
+ $('#submit').attr('disabled', false);
+
+ var table = '
';
+ $('#input_area').html(table);
+}
+function parseInput() {
+ var cols = [];
+ var data = [];
+
+ var table = document.getElementById('input_table');
+
+ // headers
+ for (var c = 0; c < table.rows[0].cells.length; c++) {
+ var col = table.rows[0].cells[c].getElementsByTagName('input')[0].value;
+ cols.push(col);
+ }
+
+ // body
+ for (var i = 2; i < table.rows.length; i++) {
+ var row = table.rows[i];
+ var obj = {};
+ for (var j = 0; j < cols.length; j++) {
+ var cell = row.cells[j];
+ obj[cols[j]] = cell.getElementsByTagName('input')[0].value;
+ }
+ data.push(obj);
+ }
+
+ return { cols: cols, data: data};
+}
+
+/* var defaults = {
+ 5: ['A', 10, 'B', 5, 'C', 2],
+ 6: ['Z', 5],
+ 7: ['A', 'True', 'B', 'True', 'Z', 'True'],
+ 8: ['A', 'Group A', 'B', 'Group B', 'Z', 'Group C']
+}; */
+
+var defaults = {
+ 5: [ 'Alice', 10.23, 'Lora', 20.30, 'Bob', 0.12 ],
+ 6: [ 'Kinan', 30, 'Caroline', 5.99 ],
+ 7: [ 'Alice', 'False', 'Kinan', 'True', 'John', 'True', 'Lora', 'True', 'Caroline', 'True' ],
+ 8: [ 'Alice', 'Group A', 'John', 'Group A', 'Kinan', 'Group B', 'Caroline', 'Group B', 'Lora', 'Group C', 'Sam', 'Group B' ]
+};
+
+var headers = {
+ 5: ['ID', 'VALUE'],
+ 6: ['ID', 'VALUE'],
+ 7: ['ID', 'FILTER'],
+ 8: ['ID', 'GROUP']
+};
+
+var def;
+var hed;
\ No newline at end of file
diff --git a/presentation/relational/input/mpc.js b/presentation/relational/input/mpc.js
new file mode 100644
index 000000000..d815aec59
--- /dev/null
+++ b/presentation/relational/input/mpc.js
@@ -0,0 +1,80 @@
+(function (exports) {
+ var jiff_instance;
+ var computes;
+
+ /**
+ * Connect to the server and initialize the jiff instance
+ */
+ exports.connect = function (hostname, computation_id, options, _computes) {
+ computes = _computes;
+ var opt = Object.assign({}, options);
+
+ opt.Zp = '16381';
+ opt.integer_digits = 2;
+ opt.decimal_digits = 2;
+
+ // eslint-disable-next-line no-undef
+ jiff_instance = jiff.make_jiff(hostname, computation_id, opt);
+ // eslint-disable-next-line no-undef
+ jiff_instance.apply_extension(jiff_bignumber, opt);
+ // eslint-disable-next-line no-undef
+ jiff_instance.apply_extension(jiff_fixedpoint, opt);
+
+ return jiff_instance;
+ };
+
+ /**
+ * The MPC computation
+ */
+ exports.compute = function (cols, data) {
+ var scoped_cols = [];
+ for (var i = 0; i < cols.length; i++) {
+ scoped_cols[i] = (jiff_instance.id - computes.length - 1) + '.' + cols[i];
+ }
+
+ // Parsing
+ var l;
+ for (l = 0; l < data.length; l++) {
+ data[l][cols[0]] = data[l][cols[0]].toUpperCase().trim();
+ data[l][cols[0]] = data[l][cols[0]].charCodeAt(0) - 'A'.charCodeAt(0) + 1;
+ }
+
+ if (jiff_instance.id === 5 || jiff_instance.id === 6) {
+ for (l = 0; l < data.length; l++) {
+ data[l][cols[1]] = Number(data[l][cols[1]]);
+ }
+ } else if (jiff_instance.id === 7) {
+ for (l = 0; l < data.length; l++) {
+ data[l][cols[1]] = data[l][cols[1]].toLowerCase() === 'true' ? 1 : 2;
+ }
+ } else if (jiff_instance.id === 8) {
+ for (l = 0; l < data.length; l++) {
+ data[l][cols[1]] = data[l][cols[1]].toUpperCase().trim();
+ var ln = data[l][cols[1]].length;
+ data[l][cols[1]] = data[l][cols[1]].charCodeAt(ln-1) - 'A'.charCodeAt(0) + 1;
+ }
+ }
+
+ // Compute party and analyst ids.
+ var copy = computes.slice();
+ copy.push(computes.length + 1);
+
+ // Send headers / schema
+ jiff_instance.emit('headers', copy, JSON.stringify(scoped_cols), false);
+
+ // Sort by key
+ data.sort(function (x, y) {
+ return x[cols[0]] - y[cols[0]];
+ });
+
+ // Split Array
+ for (var c = 0; c < cols.length; c++) {
+ var column = [];
+ for (var d = 0; d < data.length; d++) {
+ column.push(data[d][cols[c]]);
+ }
+
+ jiff_instance.share_array(column, null, computes.length, computes, [ jiff_instance.id ]);
+ }
+ };
+}(this.mpc = {}));
diff --git a/presentation/relational/server.js b/presentation/relational/server.js
new file mode 100644
index 000000000..baaf6411f
--- /dev/null
+++ b/presentation/relational/server.js
@@ -0,0 +1,66 @@
+var express = require('express');
+var app = express();
+
+var http = require('http').Server(app);
+http.listen(8080, function () {
+ console.log('listening on *:8080');
+});
+
+// nunjucks for rendering html template
+var nunjucks = require('nunjucks');
+nunjucks.configure(__dirname, {
+ autoescape: true,
+ express: app
+});
+
+// read configurations
+var config = require('./config.json');
+
+//Serve static files
+app.use('/bignumber.js', express.static(__dirname + '/../../node_modules/bignumber.js'));
+app.use('/lib', express.static(__dirname + '/../../lib'));
+
+// Server input and analyst files
+app.use('/static/input', express.static(__dirname + '/input'));
+app.use('/static/analyst', express.static(__dirname + '/analyst'));
+app.get('/input/:id', function (req, res) {
+ var party_id = parseInt(req.params.id, 10);
+ res.render('input/input.html', {id: party_id + config.compute + 1, config: config});
+});
+app.get('/analyst', function (req, res) {
+ res.render('analyst/analyst.html', {id: config.compute + 1, config: config});
+});
+
+// Manage roles / IDS
+// Keep track of assigned ids
+var assignedCompute = 1;
+var initialize = function (jiff, computation_id, msg, params) {
+ if (params.party_id != null) {
+ return params;
+ }
+
+ if (msg.role === 'input') {
+ return params;
+ }
+ if (msg.role === 'analyst') {
+ params['party_id'] = config.compute + 1;
+ return params;
+ }
+ if (msg.role === 'compute' && assignedCompute <= config.compute) {
+ params['party_id'] = assignedCompute;
+ assignedCompute++;
+ return params;
+ }
+
+ throw new Error('Unrecognized role and party id options');
+};
+
+var jiff_instance = require('../../lib/jiff-server').make_jiff(http, {logs: true, hooks: { beforeInitialization: [initialize] }});
+jiff_instance.apply_extension(require('../../lib/ext/jiff-server-bignumber.js'));
+
+
+// Instructions
+console.log('Direct your browser to http://localhost/input/ for inputing data');
+console.log('Direct your browser to http://localhost/analyst for analyst UI');
+console.log('Run compute parties with node compute/party.js');
+console.log();
\ No newline at end of file