-
Notifications
You must be signed in to change notification settings - Fork 22
/
jiff-client.js
2581 lines (2264 loc) · 117 KB
/
jiff-client.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* The exposed API from jiff-client.js (The client side library of JIFF).
* Wraps the jiff API. Internal members can be accessed with jiff.<member-name>.
* @namespace jiff
* @version 1.0
*/
(function(exports, node) {
if(node) {
io = require('socket.io-client');
$ = require('jquery-deferred');
// Setup libsodium wrapper instance for this client
// sodium = require('libsodium-wrappers');
// sodium_promise = sodium.ready;
} else { // sodium should be available in global scope from including sodium.js
// sodium_promise = sodium.ready;
}
/**
* The default modulos to be used in a jiff instance if a custom modulos was not provided.
*/
var gZp = 15485867;
/** Return the maximum of two numbers */
function max(x, y) {
return x > y ? x : y;
}
/** Doubly linked list with add and delete functions and pointers to head and tail**/
var linked_list = function() {
// attributes: list.head and list.tail
// functions: list.add(object) (returns pointer), list.delete(pointer)
// list.head/list.tail/any element contains:
// next: pointer to next,
// previous: pointer to previous,
// object: stored object.
var list = { "head": null, "tail": null };
list.add = function(obj) {
var node = { "object": obj, "next": null, "tail": null };
if(list.head == null) {
list.head = node;
list.tail = node;
}
else {
list.tail.next = node;
node.previous = list.tail;
list.tail = node;
}
return node;
};
list.delete = function(ptr) {
var prev = ptr.previous;
var next = ptr.next;
if(prev == null) {
list.head = next;
if(list.head != null) list.head.prev = null;
else list.tail = null;
}
else {
prev.next = next;
if(next != null) next.previous = prev;
}
};
return list;
};
/**
* Encrypts and signs the given message, the function will execute message.toString(10)
* internally to ensure type of message is a string before encrypting.
* @memberof jiff.utils
* @instance
* @param {number/string} message - the message to encrypt.
* @param {Uint8Array} encryption_public_key - ascii-armored public key to encrypt with.
* @param {Uint8Array} signing_private_key - the private key of the encrypting party to sign with.
* @param {string} operation_type - the operation for which this encryption is performed, one of the following: 'share', 'open', 'triplet', 'number'
* @returns {object} the signed cipher, includes two properties: 'cipher' and 'nonce'.
*/
function encrypt_and_sign(message, encryption_public_key, signing_private_key, operation_type) {
return message;
/*
if(operation_type == 'share' || operation_type == 'open') message = message.toString(10);
var nonce = sodium.randombytes_buf(sodium.crypto_box_NONCEBYTES);
var cipher = sodium.crypto_box_easy(message, nonce, encryption_public_key, signing_private_key);
return { "nonce": '['+nonce.toString()+']', "cipher": '['+cipher.toString()+']'};
*/
}
/**
* Decrypts and checks the signature of the given ciphertext, the function will execute
* parseInt internally to ensure returned value is a number.
* @memberof jiff.utils
* @instance
* @param {object} cipher_text - the ciphertext to decrypt, includes two properties: 'cipher' and 'nonce'.
* @param {Uint8Array} decryption_secret_key - the secret key to decrypt with.
* @param {Uint8Array} signing_public_key - ascii-armored public key to verify against signature.
* @param {string} operation_type - the operation for which this decryption is performed, one of the following: 'share', 'open', 'triplet', 'number'
* @returns {number/string} the decrypted message if the signature was correct, the decrypted message type should
* the type of operation, such that the returned value has the appropriate type and does
* not need any type modifications.
* @throws error if signature or nonce was forged/incorrect.
*/
function decrypt_and_sign(cipher_text, decryption_secret_key, signing_public_key, operation_type) {
return cipher_text;
/*
var nonce = new Uint8Array(JSON.parse(cipher_text.nonce));
cipher_text = new Uint8Array(JSON.parse(cipher_text.cipher));
try {
var decryption = sodium.crypto_box_open_easy(cipher_text, nonce, signing_public_key, decryption_secret_key, 'text');
if(operation_type == 'share' || operation_type == 'open') return parseInt(decryption, 10);
return decryption;
} catch (_) {
throw "Bad signature or Bad nonce";
} */
}
/**
* Share given secret to the participating parties.
* @param {jiff-instance} jiff - the jiff instance.
* @param {number} secret - the secret to share.
* @param {number} threshold - the minimimum number of parties needed to reconstruct the secret, defaults to all the recievers. [optional]
* @param {array} receivers_list - array of party ids to share with, by default, this includes all parties. [optional]
* @param {array} senders_list - array of party ids to receive from, by default, this includes all parties. [optional]
* @param {number} Zp - the modulos (if null then the default Zp for the instance is used). [optional]
* @param {string/number} share_id - 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. [optional]
* @returns {object} a map where the key is the sender party id
* and the value is the share object that wraps
* what was sent from that party (the internal value maybe deferred).
* if the party that calls this function is not a receiver then the map
* will be empty.
*/
function jiff_share(jiff, secret, threshold, receivers_list, senders_list, Zp, share_id) {
// defaults
if(Zp == null) Zp = jiff.Zp;
if(receivers_list == null) {
receivers_list = [];
for(var i = 1; i <= jiff.party_count; i++) receivers_list.push(i);
}
if(senders_list == null) {
senders_list = [];
for(var i = 1; i <= jiff.party_count; i++) senders_list.push(i);
}
if(threshold == null) threshold = receivers_list.length;
if(threshold < 0) threshold = 2;
if(threshold > receivers_list.length) threshold = receivers_list.length;
// if party is uninvolved in the share, do nothing
if(receivers_list.indexOf(jiff.id) == -1 && senders_list.indexOf(jiff.id) == -1)
return {};
// compute operation id
receivers_list.sort(); // sort to get the same order
senders_list.sort();
if(share_id == null) share_id = jiff.counters.gen_share_id(receivers_list, senders_list);
// stage sending of shares
if(senders_list.indexOf(jiff.id) > -1) {
// Call hook
secret = jiff.execute_array_hooks('beforeShare', [jiff, secret, threshold, receivers_list, senders_list, Zp], 1);
// compute shares
var shares = jiff.hooks.computeShares(jiff, secret, receivers_list, threshold, Zp);
// Call hook
shares = jiff.execute_array_hooks('afterComputeShare', [jiff, shares, threshold, receivers_list, senders_list, Zp], 1);
// send shares
for(var i = 0; i < receivers_list.length; i++) {
var p_id = receivers_list[i];
if(p_id == jiff.id) continue;
// send encrypted and signed shares_id[p_id] to party p_id
var cipher_share = jiff.hooks.encryptSign(shares[p_id], jiff.keymap[p_id], jiff.secret_key, 'share');
var msg = { party_id: p_id, share: cipher_share, op_id: share_id };
jiff.socket.safe_emit('share', JSON.stringify(msg));
}
}
// stage receiving of shares
var result = {};
if(receivers_list.indexOf(jiff.id) > -1) {
// setup a map of deferred for every received share
if(jiff.deferreds[share_id] == null) jiff.deferreds[share_id] = {};
for(var i = 0; i < senders_list.length; i++) {
var p_id = senders_list[i];
if(p_id == jiff.id) {
var my_share = jiff.execute_array_hooks('receiveShare', [jiff, p_id, shares[p_id]], 2);
result[p_id] = jiff.secret_share(jiff, true, null, my_share, receivers_list, threshold, Zp);
continue; // Keep party's own share
}
// check if a deferred is set up (maybe the message was previously received)
if(jiff.deferreds[share_id][p_id] == null)
// not ready, setup a deferred
jiff.deferreds[share_id][p_id] = $.Deferred();
var promise = jiff.deferreds[share_id][p_id].promise();
// destroy deferred when done
(function(promise, p_id) { // p_id is modified in a for loop, must do this to avoid scoping issues.
promise.then(function() { jiff.deferreds[share_id][p_id] = null; });
})(promise, p_id);
// receive share_i[id] from party p_id
result[p_id] = jiff.secret_share(jiff, false, promise, undefined, receivers_list, threshold, Zp, share_id+":"+p_id);
}
}
return result;
}
/**
* Default way of computing shares (can be overriden using hooks).
* Compute the shares of the secret (as many shares as parties) using shamir secret sharing:
* a polynomial of degree: ceil(parties/2) - 1 (honest majority).
* @param {number} secret - the secret to share.
* @param {array} parties_list - array of party ids to share with.
* @param {number} threshold - the minimimum number of parties needed to reconstruct the secret, defaults to all the recievers. [optional]
* @param {number} Zp - the modulos.
* @param {function(number)} random - a function that provides a random number between 0 and the provided number-1 inclusive
* @returns {object} a map between party number (from 1 to parties) and its
* share, this means that (party number, share) is a
* point from the polynomial.
*
*/
function jiff_compute_shares(jiff, secret, parties_list, threshold, Zp) {
var shares = {}; // Keeps the shares
// Each player's random polynomial f must have
// degree threshold - 1, so that threshold many points are needed
// to interpolate/reconstruct.
var t = threshold - 1;
var polynomial = Array(t+1); // stores the coefficients
// Each players's random polynomial f must be constructed
// such that f(0) = secret
polynomial[0] = secret;
// Compute the random polynomial f's coefficients
for(var i = 1; i <= t; i++) polynomial[i] = jiff.helpers.random(Zp);
// Compute each players share such that share[i] = f(i)
for(var i = 0; i < parties_list.length; i++) {
var p_id = parties_list[i];
shares[p_id] = polynomial[0];
var power = jiff.helpers.get_party_number(p_id);
for(var j = 1; j < polynomial.length; j++) {
var tmp = jiff.helpers.mod((polynomial[j] * power), Zp);
shares[p_id] = jiff.helpers.mod((shares[p_id] + tmp), Zp);
power = jiff.helpers.mod(power * jiff.helpers.get_party_number(p_id), Zp);
}
}
return shares;
}
/*
* Store the received share and resolves the corresponding
* deferred if needed.
* @param {jiff-instance} jiff - the jiff instance.
* @param {number} sender_id - the id of the sender.
* @param {string} share - the encrypted share, unless sender
* is the same as receiver, then it is
* an unencrypted number.
* @param {number} op_id - the id of the share operation.
*
*/
function receive_share(jiff, sender_id, share, op_id) {
// Decrypt share
share = jiff.hooks.decryptSign(share, jiff.secret_key, jiff.keymap[sender_id], 'share');
// Call hook
share = jiff.execute_array_hooks('receiveShare', [jiff, sender_id, share], 2);
// check if a deferred is set up (maybe the share was received early)
if(jiff.deferreds[op_id] == null) jiff.deferreds[op_id] = {};
if(jiff.deferreds[op_id][sender_id] == null)
// Share is received before deferred was setup, store it.
jiff.deferreds[op_id][sender_id] = $.Deferred();
// Deferred is already setup, resolve it.
jiff.deferreds[op_id][sender_id].resolve(share);
}
/*
* Open up the given share to the participating parties.
* @param {jiff-instance} jiff - the jiff instance.
* @param {share-object} share - the share of the secret to open that belongs to this party.
* @param {array} parties - an array with party ids (1 to n) of receiving parties. [optional]
* @param {string/number/object} op_ids - the operation id (or a map from receiving party to operation id) to be used to tag outgoing messages. [optional]
* @returns {promise} a (JQuery) promise to the open value of the secret, null if the calling party is not a receiving party.
* @throws error if share does not belong to the passed jiff instance.
*
*/
function jiff_open(jiff, share, parties, op_ids) {
if(!(share.jiff === jiff)) throw "share does not belong to given instance";
// Default values
if(parties == null || parties == []) {
parties = [];
for(var i = 1; i <= jiff.party_count; i++)
parties.push(i);
}
// If not a receiver nor holder, do nothing
if(share.holders.indexOf(jiff.id) == -1 && parties.indexOf(jiff.id) == -1) return null;
// Compute operation ids (one for each party that will receive a result
if(op_ids == null) op_ids = {};
if(typeof(op_ids) == "string" || typeof(op_ids) == "String" || typeof(op_ids) == "number") {
var tmp = {};
for(var i = 0; i < parties.length; i++) tmp[parties[i]] = op_ids;
op_ids = tmp;
}
else {
var holders_label = share.holders.join(",");
for(var i = 0; i < parties.length; i++)
if(op_ids[parties[i]] == null) op_ids[parties[i]] = jiff.counters.gen_open_id(parties[i], holders_label);
}
// Party is a holder
if(share.holders.indexOf(jiff.id) > -1) {
// Call hook
share = jiff.execute_array_hooks('beforeOpen', [jiff, share, parties], 1);
// refresh/reshare, so that the original share remains secret, instead
// a new share is sent/open without changing the actual value.
share = share.refresh("refresh:"+op_ids[parties[0]]);
// The given share has been computed, share it to all parties
if(share.ready) jiff_broadcast(jiff, share, parties, op_ids);
// Share is not ready, setup sharing as a callback to its promise
else share.promise.then(function() { jiff_broadcast(jiff, share, parties, op_ids); }, share.error);
}
// Party is a receiver
if(parties.indexOf(jiff.id) > -1) {
var shares = []; // this will store received shares
var final_deferred = $.Deferred(); // will be resolved when the final value is reconstructed
var final_promise = final_deferred.promise();
for(var i = 0; i < share.holders.length; i++) {
var p_id = share.holders[i];
// Setup a deferred for receiving a share from party p_id
if(jiff.deferreds[op_ids[jiff.id]] == null) jiff.deferreds[op_ids[jiff.id]] = {};
if(jiff.deferreds[op_ids[jiff.id]][p_id] == null) jiff.deferreds[op_ids[jiff.id]][p_id] = $.Deferred();
// Clean up deferred when fulfilled
var promise = jiff.deferreds[op_ids[jiff.id]][p_id].promise();
// destroy deferred when done
(function(promise, p_id) { // p_id is modified in a for loop, must do this to avoid scoping issues.
promise.then(function(received_share) {
jiff.deferreds[op_ids[jiff.id]][p_id] = null;
shares.push(received_share);
// Too few shares, nothing to do.
if(shares.length < share.threshold) return;
// Enough shares to reconstruct.
// If did not already reconstruct, do it.
if(final_deferred != null) {
var recons_secret = jiff.hooks.reconstructShare(jiff, shares);
recons_secret = jiff.execute_array_hooks('afterReconstructShare', [jiff, recons_secret], 1);
final_deferred.resolve(recons_secret);
final_deferred = null;
}
// If all shares were received, clean up.
if(shares.length == share.holders.length) {
shares = null;
jiff.deferreds[op_ids[jiff.id]] = null;
}
});
})(promise, p_id);
}
return final_promise;
}
return null;
}
/**
* Opens a bunch of secret shares.
* @param {jiff-instance} jiff - the jiff instance.
* @param {array<share-object>} shares - an array containing this party's shares of the secrets to reconstruct.
* @param {array} parties - an array with party ids (1 to n) of receiving parties. [optional]
* 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.
* @returns {promise} a (JQuery) promise to ALL the open values of the secret, the promise will yield
* an array of values, each corresponding to the given share in the shares parameter
* at the same index.
* @throws error if some shares does not belong to the passed jiff instance.
*/
function jiff_open_all(jiff, shares, parties) {
var parties_nested_arrays = (parties != null && (parties[0] == null || (typeof(parties[0]) != "number" && typeof(parties[0]) != "string")));
var promises = [];
for(var i = 0; i < shares.length; i++) {
var party = parties_nested_arrays ? parties[i] : parties;
promises.push(jiff.open(shares[i], party));
}
return Promise.all(promises);
}
/*
* Share the given share to all the parties in the jiff instance.
* @param {jiff-instance} jiff - the jiff instance.
* @param {number} share - the share.
* @param {array} parties - the parties to broadcast the share to.
* @param {map} op_ids - a map from party id to operation id, this allows different messages
* to have different operation id, in case operation id contains
* the id of the receiver as well.
*
*/
function jiff_broadcast(jiff, share, parties, op_ids) {
for(var index = 0; index < parties.length; index++) {
var i = parties[index]; // Party id
if(i == jiff.id) { receive_open(jiff, i, share.value, op_ids[i], share.Zp); continue; }
// encrypt, sign and send
var cipher_share = jiff.hooks.encryptSign(share.value, jiff.keymap[i], jiff.secret_key, 'open');
var msg = { party_id: i, share: cipher_share, op_id: op_ids[i], Zp: share.Zp };
jiff.socket.safe_emit('open', JSON.stringify(msg));
}
}
/*
* Resolves the deferred corresponding to operation_id and sender_id.
* @param {jiff_instance} jiff - the jiff instance.
* @param {number} sender_id - the id of the sender.
* @param {string} share - the encrypted share, unless sender
* is the same as receiver, then it is
* an unencrypted number..
* @param {number} op_id - the id of the share operation.
* @param {number} Zp - the modulos.
*/
function receive_open(jiff, sender_id, share, op_id, Zp) {
// Decrypt share
if(sender_id != jiff.id)
share = jiff.hooks.decryptSign(share, jiff.secret_key, jiff.keymap[sender_id], 'open');
// call hook
share = jiff.execute_array_hooks('receiveOpen', [jiff, sender_id, share, Zp], 2);
// Resolve the deferred.
if(jiff.deferreds[op_id] == null) jiff.deferreds[op_id] = {};
if(jiff.deferreds[op_id][sender_id] == null) jiff.deferreds[op_id][sender_id] = $.Deferred();
jiff.deferreds[op_id][sender_id].resolve( { "value": share, "sender_id": sender_id, "Zp": Zp } );
}
/*
* Uses Lagrange polynomials to interpolate the polynomial
* described by the given shares (points).
* @param {jiff-instance} jiff - the jiff instance.
* @param {array} shares - an array of objects representing shares to reconstruct, every object has 3 attributes: value, sender_id, Zp.
* @returns {number} the value of the polynomial at x=0 (the secret value).
*
*/
function jiff_lagrange(jiff, shares) {
var party_count = jiff.party_count;
var lagrange_coeff = []; // will contain shares.length many elements.
// Compute the Langrange coefficients at 0.
for(var i = 0; i < shares.length; i++) {
var pi = jiff.helpers.get_party_number(shares[i].sender_id);
lagrange_coeff[pi] = 1;
for(var j = 0; j < shares.length; j++) {
var pj = jiff.helpers.get_party_number(shares[j].sender_id);
if(pj != pi) {
var inv = jiff.helpers.extended_gcd(pi - pj, shares[i].Zp)[0];
lagrange_coeff[pi] = jiff.helpers.mod(lagrange_coeff[pi] * (0 - pj), shares[i].Zp) * inv;
lagrange_coeff[pi] = jiff.helpers.mod(lagrange_coeff[pi], shares[i].Zp);
}
}
}
// Reconstruct the secret via Lagrange interpolation
var recons_secret = 0;
for(var i = 0; i < shares.length; i++) {
var pi = jiff.helpers.get_party_number(shares[i].sender_id);
var tmp = jiff.helpers.mod((shares[i].value * lagrange_coeff[pi]), shares[i].Zp);
recons_secret = jiff.helpers.mod((recons_secret + tmp), shares[i].Zp);
}
return recons_secret;
}
/*
* Creates 3 shares, a share for every one of three numbers from a beaver triplet.
* The server generates and sends the triplets on demand.
* @param {jiff-instance} jiff - the jiff instance.
* @param {array} receivers_list - array of party ids that want to receive the triplet shares, by default, this includes all parties. [optional]
* @param {number} threshold - the minimimum number of parties needed to reconstruct the triplet.
* @param {number} Zp - the modulos (if null then the default Zp for the instance is used). [optional]
* @param {string} triplet_id - the triplet id which is used to identify the triplet requested, so that every party
* gets a share from the same triplet for every matching instruction. An automatic triplet id
* is generated by increasing a local counter, default ids suffice when all parties execute the
* instructions in the same order. [optional]
* @returns {array<share-object>} an array of 3 share-objects [share_a, share_b, share_c] such that a * b = c.
*/
function jiff_triplet(jiff, receivers_list, threshold, Zp, triplet_id) {
if(Zp == null) Zp = jiff.Zp;
if(receivers_list == null)
for(var i = 1; i <= jiff.party_count; i++) receivers_list.push(i);
// Get the id of the triplet needed.
if(triplet_id == null) triplet_id = jiff.counters.gen_triplet_id(receivers_list);
// Send a request to the server.
var msg = JSON.stringify({triplet_id: triplet_id, receivers: receivers_list, threshold: threshold, Zp: Zp});
// Setup deferreds to handle receiving the triplets later.
var a_deferred = $.Deferred();
var b_deferred = $.Deferred();
var c_deferred = $.Deferred();
jiff.deferreds[triplet_id] = { a: a_deferred, b: b_deferred, c: c_deferred };
// send a request to the server.
if(jiff.id == "s1")
jiff.triplets_socket.safe_emit('triplet', msg);
else
jiff.triplets_socket.safe_emit('triplet', jiff.hooks.encryptSign(msg, jiff.keymap["s1"], jiff.secret_key, 'triplet'));
var a_share = jiff.secret_share(jiff, false, a_deferred.promise(), undefined, receivers_list, threshold, Zp, triplet_id+":a");
var b_share = jiff.secret_share(jiff, false, b_deferred.promise(), undefined, receivers_list, threshold, Zp, triplet_id+":b");
var c_share = jiff.secret_share(jiff, false, c_deferred.promise(), undefined, receivers_list, threshold, Zp, triplet_id+":c");
return [ a_share, b_share, c_share ];
}
/*
* Store the received beaver triplet and resolves the corresponding deferred.
* @param {jiff-instance} jiff - the jiff instance.
* @param {number} triplet_id - the id of the triplet.
* @param {object} triplet - the triplet (on the form: { a: share_a, b: share_b, c: share_c }).
*
*/
function receive_triplet(jiff, triplet_id, triplet) {
triplet = jiff.execute_array_hooks('receiveTriplet', [jiff, triplet], 1);
// Deferred is already setup, resolve it.
jiff.deferreds[triplet_id]["a"].resolve(triplet["a"]);
jiff.deferreds[triplet_id]["b"].resolve(triplet["b"]);
jiff.deferreds[triplet_id]["c"].resolve(triplet["c"]);
jiff.deferreds[triplet_id] = null;
}
/**
* Can be used to generate shares of a random number, or shares of zero.
* For a random number, every party generates a local random number and secret share it,
* then every party sums its share, resulting in a single share of an unknown random number for every party.
* The same approach is followed for zero, but instead, all the parties know that the total number is zero, but they
* do not know the value of any resulting share (except their own).
* @param {jiff-instance} jiff - the jiff instance.
* @param {number} n - the number to share.
* @param {number} threshold - the minimimum number of parties needed to reconstruct the secret, defaults to all the recievers. [optional]
* @param {array} receivers_list - array of party ids to share with, by default, this includes all parties. [optional]
* @param {array} senders_list - array of party ids to receive from, by default, this includes all parties. [optional]
* @param {number} Zp - the modulos (if null then the default Zp for the instance is used). [optional]
* @return {share-object} this party's share of the the number, null if this party is not a receiver.
*/
function jiff_share_all_number(jiff, n, threshold, receivers_list, senders_list, Zp) {
if(Zp == null) Zp = jiff.Zp;
if(receivers_list == null) {
receivers_list = [];
for(var i = 1; i <= jiff.party_count; i++) receivers_list.push(i);
}
if(senders_list == null) {
senders_list = [];
for(var i = 1; i <= jiff.party_count; i++) senders_list.push(i);
}
var shares = jiff_share(jiff, n, threshold, receivers_list, senders_list, Zp);
var share = shares[senders_list[0]];
if(share != null) // only do this if you are a receiving party.
for(var i = 1; i < senders_list.length; i++)
share = share.sadd(shares[senders_list[i]]);
return share;
}
/**
* Use the server to generate shares for a random bit, zero, random non-zero number, or a random number.
* The parties will not know the value of the number (unless the request is for shares of zero) nor other parties' shares.
* @param {jiff-instance} jiff - the jiff instance.
* @param {object} options - an object with these properties:
* { "number": number, "bit": boolean, "nonzero": boolean, "max": number}
* @param {array} receivers_list - array of party ids that want to receive the triplet shares, by default, this includes all parties. [optional]
* @param {number} threshold - the minimimum number of parties needed to reconstruct the number.
* @param {number} Zp - the modulos (if null then the default Zp for the instance is used). [optional]
* @param {string} number_id - the number id which is used to identify this request, so that every party
* gets a share from the same number for every matching instruction. An automatic number id
* is generated by increasing a local counter, default ids suffice when all parties execute the
* instructions in the same order. [optional]
* @return {share-object} - this party's share of the generated number.
*/
function jiff_server_share_number(jiff, options, receivers_list, threshold, Zp, number_id) {
if(Zp == null) Zp = jiff.Zp;
if(receivers_list == null)
for(var i = 1; i <= jiff.party_count; i++) receivers_list.push(i);
if(threshold == null) threshold = receivers_list.length;
// Get the id of the number.
if(number_id == null) number_id = jiff.counters.gen_number_id(receivers_list);
var msg = { number_id: number_id, receivers: receivers_list, threshold: threshold, Zp: Zp };
msg = Object.assign(msg, options);
msg = JSON.stringify(msg);
// Setup deferreds to handle receiving the triplets later.
var deferred = $.Deferred();
jiff.deferreds[number_id] = deferred;
// Send a request to the server.
if(jiff.id == "s1")
jiff.numbers_socket.safe_emit('number', msg);
else
jiff.numbers_socket.safe_emit('number', jiff.hooks.encryptSign(msg, jiff.keymap["s1"], jiff.secret_key, 'number'));
var share = jiff.secret_share(jiff, false, deferred.promise(), undefined, receivers_list, threshold, Zp, number_id+":n");
return share;
}
/*
* Store the received share of a previously requested number from the server.
* @param {jiff-instance} jiff - the jiff instance.
* @param {number} number_id - the id of the number.
* @param {number} share - the value of the share.
*/
function receive_server_share_number(jiff, number_id, share) {
share = jiff.execute_array_hooks('receiveNumber', [jiff, share], 1);
// Deferred is already setup, resolve it.
jiff.deferreds[number_id].resolve(share);
jiff.deferreds[number_id] = null;
}
/**
* Coerce a number into a share. THIS DOES NOT SHARE THE GIVEN NUMBER.
* It is a local type-coersion by invoking the constructor on the given parameter,
* this is useful for for operating on constants, not sharing secret data.
* If all parties use this function with the same input number, then
* you can think of their shares as being a share of that constant with threshold 1.
* In other words, a trivial sharing scheme where the share is the number itself.
* However, if some parties used different input numbers, then the actual value
* yielded by reconstruction/opening of all these shares is arbitrary and depends
* on all the input numbers of all parties.
* @param {jiff-instance} jiff - the jiff instance.
* @param {number} number - the number to coerce.
* @param {array} holders - array of party ids that will hold the shares, by default, this includes all parties. [optional]
* @param {number} Zp - the modulos (if null then the default Zp for the instance is used). [optional]
* @param {string} id - this share's id (should be unique). [optional]
* @returns {share-object} a share object containing the given number.
*
*/
function jiff_coerce_to_share(jiff, number, holders, Zp, share_id) {
if(Zp == null) Zp = jiff.Zp;
if(holders == null)
for(var i = 1; i <= jiff.party_count; i++) holders.push(i);
return jiff.secret_share(jiff, true, null, number, holders, 1, Zp, share_id);
}
/**
* Create a new share.
* A share is a value wrapper with a share object, it has a unique id
* (per computation instance), and a pointer to the instance it belongs to.
* A share also has methods for performing operations.
* @memberof jiff
* @method secret_share
* @param {jiff-instance} jiff - the jiff instance.
* @param {boolean} ready - whether the value of the share is ready or deferred.
* @param {promise} promise - a promise to the value of the share.
* @param {number} value - the value of the share (null if not ready).
* @param {array} holders - the parties that hold all the corresponding shares (must be sorted).
* @param {number} threshold - the minimimum number of parties needed to reconstruct the secret.
* @param {number} Zp - the modulos under which this share was created.
* @param {string} id - this share's id (should be unique). [optional]
* @returns {secret-share} the secret share object containing the give value.
*
*/
function secret_share(jiff, ready, promise, value, holders, threshold, Zp, id) {
/**
* Secret share objects: provides API to perform operations on shares securly, wrap promises
* and communication primitives to ensure operations are executed when shares are available (asynchrounously)
* without requiring the user to perform promise management/synchronization.
* @memberof jiff
* @namespace secret-share
*/
var self = {};
/**
* @member {jiff-instance} jiff
* @memberof jiff.secret-share
*/
self.jiff = jiff;
/**
* @member {boolean} ready
* @memberof jiff.secret-share
*/
self.ready = ready;
/**
* @member {promise} promise
* @memberof jiff.secret-share
*/
self.promise = promise;
/**
* @member {number} value
* @memberof jiff.secret-share
*/
self.value = value;
/**
* @member {array} holders
* @memberof jiff.secret-share
*/
self.holders = holders;
/**
* @member {array} threshold
* @memberof jiff.secret-share
*/
self.threshold = threshold;
/**
* @member {number} Zp
* @memberof jiff.secret-share
*/
self.Zp = Zp;
if(id == null) id = jiff.counters.gen_share_obj_id();
/**
* @member {string} id
* @memberof jiff.secret-share
*/
self.id = id;
/**
* Gets the value of this share.
* @method valueOf
* @returns {number} the value (undefined if not ready yet).
* @memberof jiff.secret-share
*/
self.valueOf = function() {
if(ready) return self.value;
else return undefined;
};
/**
* Gets a string representation of this share.
* @method toString
* @returns {string} the id and value of the share as a string.
* @memberof jiff.secret-share
*/
self.toString = function() {
if(ready) return self.id + ": " + self.value;
else return self.id + ": <deferred>";
};
/**
* Logs an error.
* @method error
* @memberof jiff.secret-share
*/
self.error = function() { console.log("Error receiving " + self.toString()); };
/**
* Receives the value of this share when ready.
* @method receive_share
* @param {number} value - the value of the share.
* @memberof jiff.secret-share
*/
self.receive_share = function(value) { self.value = value; self.ready = true; self.promise = null; };
/**
* Joins the pending promises of this share and the given share.
* @method pick_promise
* @param {share-object} o - the other share object.
* @returns {promise} the joined promise for both shares (or whichever is pending).
* @memberof jiff.secret-share
*/
self.pick_promise = function(o) {
if(self.ready && o.ready) return null;
if(self.ready) return o.promise;
else if(o.ready) return self.promise;
else return Promise.all([self.promise, o.promise]);
};
/**
* Checks if the given parameter is a constant, used to determine whether constant or secret
* operations should be executed.
* @param {number/object} o - the parameter to determine.
* @return true if o is a valid constant, false otherwise.
*/
self.isConstant = function(o) {
return typeof(o) == "number";
}
/**
* Reshares/refreshes the sharing of this number, used before opening to keep the share secret.
* @method refresh
* @param {string} op_id - the operation id with which to tag the messages sent by this refresh, by default
* an automatic operation id is generated by increasing a local counter, default operation ids
* suffice when all parties execute the instructions in the same order. [optional]
* @returns {secret-share} a new share of the same number.
* @memberof jiff.secret-share
*/
self.refresh = function(op_id) {
return self.sadd(self.jiff.server_generate_and_share({"number": 0}, self.holders, self.threshold, self.Zp, op_id));
};
/**
* Reveals/Opens the value of this share.
* @method open
* @param {function(number)} success - the function to handle successful open.
* @param {function(string)} error - the function to handle errors and error messages. [optional]
* @returns {promise} a (JQuery) promise to the open value of the secret.
* @throws error if share does not belong to the passed jiff instance.
* @memberof jiff.secret-share
*/
self.open = function(success, failure) {
if(failure == null) failure = self.error;
var promise = self.jiff.open(self);
if(promise != null) promise.then(success, failure);
return promise;
};
/**
* Reveals/Opens the value of this share to a specific array of parties.
* @method open_to
* @param {array} parties - the ids of parties to reveal secret to.
* @param {function(number)} success - the function to handle successful open.
* @param {function(string)} error - the function to handle errors and error messages. [optional]
* @memberof jiff.secret-share
*/
self.open_to = function(parties, success, failure) {
if(failure == null) failure = self.error;
var promise = self.jiff.open(self, parties);
if(promise != null) promise.then(success, failure);
};
/**
* Generic Addition.
* Uses either the constant or secret version of this operator depending on type of paramter.
* @method add
* @param {number/share-object} o - the other operand (can be either number or share).
* @return {share-object} this party's share of the result.
* @memberof jiff.secret-share
*/
self.add = function(o) {
if(self.isConstant(o)) return self.cadd(o);
return self.sadd(o);
}
/**
* Generic Subtraction.
* Uses either the constant or secret version of this operator depending on type of paramter.
* @method sub
* @param {number/share-object} o - the other operand (can be either number or share).
* @return {share-object} this party's share of the result.
* @memberof jiff.secret-share
*/
self.sub = function(o) {
if(self.isConstant(o)) return self.csub(o);
return self.ssub(o);
}
/**
* Generic Multiplication.
* Uses either the constant or secret version of this operator depending on type of paramter.
* @method mult
* @param {number/share-object} o - the other operand (can be either number or share).
* @return {share-object} this party's share of the result.
* @memberof jiff.secret-share
*/
self.mult = function(o) {
if(self.isConstant(o)) return self.cmult(o);
return self.smult(o);
}
/**
* Generic XOR for bits (both this and o have to be bits to work correctly).
* Uses either the constant or secret version of this operator depending on type of paramter.
* @method xor_bit
* @param {number/share-object} o - the other operand (can be either number or share).
* @return {share-object} this party's share of the result.
* @memberof jiff.secret-share
*/
self.xor_bit = function(o) {
if(self.isConstant(o)) return self.cxor_bit(o);
return self.sxor_bit(o);
}
/**
* Generic Greater or equal.
* Uses either the constant or secret version of this operator depending on type of paramter.
* @method gteq
* @param {number/share-object} o - the other operand (can be either number or share).
* @param {number} l - the maximum bit length of the two shares. [optional]
* @return {share-object} this party's share of the result.
* @memberof jiff.secret-share
*/
self.gteq = function(o, l) {
if(self.isConstant(o)) return self.cgteq(o, l);
return self.sgteq(o, l);
}
/**
* Generic Greater than.
* Uses either the constant or secret version of this operator depending on type of paramter.
* @method gt
* @param {number/share-object} o - the other operand (can be either number or share).
* @param {number} l - the maximum bit length of the two shares. [optional]
* @return {share-object} this party's share of the result.
* @memberof jiff.secret-share
*/
self.gt = function(o, l) {
if(self.isConstant(o)) return self.cgt(o, l);
return self.sgt(o, l);
}
/**
* Generic Less or equal.
* Uses either the constant or secret version of this operator depending on type of paramter.
* @method lteq
* @param {number/share-object} o - the other operand (can be either number or share).
* @param {number} l - the maximum bit length of the two shares. [optional]
* @return {share-object} this party's share of the result.
* @memberof jiff.secret-share
*/
self.lteq = function(o, l) {
if(self.isConstant(o)) return self.clteq(o, l);
return self.slteq(o, l);
}
/**
* Generic Less than.
* Uses either the constant or secret version of this operator depending on type of paramter.
* @method lt
* @param {number/share-object} o - the other operand (can be either number or share).
* @param {number} l - the maximum bit length of the two shares. [optional]
* @return {share-object} this party's share of the result.
* @memberof jiff.secret-share
*/
self.lt = function(o, l) {
if(self.isConstant(o)) return self.clt(o, l);
return self.slt(o, l);
}
/**
* Generic Equals.
* Uses either the constant or secret version of this operator depending on type of paramter.
* @method eq
* @param {number/share-object} o - the other operand (can be either number or share).
* @param {number} l - the maximum bit length of the two shares. [optional]
* @return {share-object} this party's share of the result.
* @memberof jiff.secret-share
*/
self.eq = function(o, l) {
if(self.isConstant(o)) return self.ceq(o, l);
return self.seq(o, l);
}
/**
* Generic Not Equals.
* Uses either the constant or secret version of this operator depending on type of paramter.
* @method neq
* @param {number/share-object} o - the other operand (can be either number or share).
* @param {number} l - the maximum bit length of the two shares. [optional]
* @return {share-object} this party's share of the result.
* @memberof jiff.secret-share
*/
self.neq = function(o, l) {
if(self.isConstant(o)) return self.cneq(o, l);
return self.sneq(o, l);
}
/**
* Generic Integer Divison.