File: tp-dkg.txt

package info (click to toggle)
liboprf 0.9.2-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,720 kB
  • sloc: ansic: 19,331; python: 1,920; makefile: 418
file content (834 lines) | stat: -rw-r--r-- 28,825 bytes parent folder | download | duplicates (2)
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
Trusted-party (TP) Distributed Key Generation (DKG) v1.0

This document specifies a proposal for a non-robust DKG that can work
for small deployments with a small number of parties and infrequent
DKG executions. Non-robust means that the protocol succeeds only if no
party aborts. If someone aborts then the protocol needs to run again,
possibly after kicking out misbehaving parties. This protocol does
support maximum 127 peers. This is probably already too much for a
non-robust protocol, but it might work in very special circumstances.

Broadcast is implemented by the trusted party (TP) opening a channel
to each peer secured by the peers long-term encryption key. Every
message is routed through the TP.

Peer long-term encryption keys can be either TLS-based, or
Noise_XK-based (https://noiseexplorer.com/patterns/XK/). In the latter
case the long-term public keys must be known and validated in advance
by the TP.

The basis for this protocol is JF-DKG (fig 1.) a variant on Pedersens
DKG from the 2006 paper "Secure Distributed Key Generation for
Discrete-Log Based Cryptosystems" by R. Gennaro, S. Jarecki,
H. Krawczyk, and T. Rabin [GJKR06]. The algorithm JF-DKG is presented
in the paper as a reduced example how an adversary can influence the
bits in the generated secret by manipulating the complaints and thus
the final composition of the QUAL set, gaining a 3/4 chance to
influence a bit. Since in our TP variant is non-robust, we do not
allow individual disqualifications of peers, - either all peers
qualify or the protocol fails - this mitigates the case where an
adversary can adaptively disqualify a peer. Thus the JF-DKG is a
simple and sufficient algorithm for our purposes.

------<=[ Rationale                                     ]=>-----------

Traditionally DKGs are used in setting where all parties are equal and
are using the distributed key together, without having any one party
having a different role in the protocol utilizing the shared key. This
does not translate entirely to threshold OPRFs (tOPRF) and protocols
based on these.

In an OPRF there is normally two parties, one holding the key, and
another one holding the input and learning the output. In a tOPRF the
party holding the key is a group of peers that hold shares of the key
in a threshold setting.

The whole point of OPRFs is to be able to learn the output for a
certain input, without being able to do so without the contribution of
the party/parties holding (parts of) the key. Hence the party with the
input is in a kind of trusted role, and in many protocols based on
OPRFs it is in the best interest of the input-holding party to not
learn the key (or its parts) - otherwise the input-holding party could
just deploy a PRF instead.

And if the input holding party is in such a trusted role, there is two
options to generate a threshold shared key:

 1. the trusted input-holding party just generates a secret and shares
    it with the key-holding parties using Shamir's Secret Sharing.
    This is a very simple approach, with one drawback, the secret
    itself is however briefly know at the input-holding TP.

 2. The input-holding TP can run the simple non-robust DKG specified
    below. This has the benefit that as long as the protocol is
    followed precisely the secret is never "assembled" and thus cannot
    leak, and is never exposed to the TP. Drawback of this is, that
    the protocol below consists of many rounds of communication.

The protocol in this document allows for a variant, were each
keyshare-holder generates a completely new set of ephemeral
(encryption and signature) keys, and thus allows complete anonymity
between the keyshare-holders from each other. While only the TP is
aware of the identities of each of the keyshare-holders (by knowing
their long-term signature and encryption keys). This increases the
security of the whole scheme, as an attacker compromising one
keyshare-holder will not be able to learn the identity of the other
parties - and more importantly the location of the other keyshares. If
this keyshare-holder anonymity is not necessary, steps 3, 4 and the
first half of step 5 in the following protocol can be skipped.

------<=[ Prototocol Phases                             ]=>-----------

The protocol has the following phases:

  1. Initialization and introduction (step 1 - 5)
  2. Setup secure P2P channels (step 5 - 10)
  3. core DKG (step 11 - 17)
  4. Finish with failure: complaint resolution (only if there are
     complaints) (step 17 - 19)
  5. Finish with success: verification of transcript and completion of
     protocol (step 20 - 22)

------<=[ Simplified API                                ]=>-----------

Since the protocol consists of many steps, it is recommended to
abstract the API to the following schema:

0. Initialize
While not done and not error:
  1. Allocate input buffers
  2. input = receive()
  3. allocate output buffer
  4. run next step of protocol
  5. if there is output: send(output)
6. Post-processing

This simple schema simplifies the load of an implementer using this
protocol, reducing opportunities for errors and provides strict
security.

The reference implementation in tp-dkg.c follows this schema for both
the TP and the peers.

------<=[ Protocol transcript                           ]=>-----------

Transcript - all broadcast messages are accumulated into a transcript
by each peer and the trusted party, at the end of the protocol all
parties publish their signed transcripts and only if all signatures
are correct and the transcripts match, is the protocol successful.

The transcript is a hash, that is initialized with the string:
   "tp dkg session transcript"

in pseudo-code:

   transcript_state = hash_init("tp dkg session transcript")

Updating the transcript first updates the hash with the canonical
32bit size of the message to be added to the transcript, then the
message itself is added to the hash.

    transcript_state = hash_update(transcript_state, I2OSP(len(msg))
    transcript_state = hash_update(transcript_state, msg)

The signature of each message is similarly added to the transcript.

A function `update_ts` can be used as a high-level interface to
updating the transcript with messages and their signatures:

```
update_ts(state,msg,sig)
    state = hash_update(state, I2OSP(len(msg))
    state = hash_update(state, msg)
    state = hash_update(state, I2OSP(len(sig))
    state = hash_update(state, sig)
    return state
```

------<=[ Session id                                    ]=>-----------

Every execution of the protocol starts by the TP sending out a message
with a unique and fresh session id, this is to ensure that no messages
can be replayed. The session id is a 256 bit (32B) random value of
cryptographic quality.

------<=[ Message header                                ]=>-----------

All messages have a message header:

  uint8  type
  uint8  version = 0
  uint8  messageno
  uint32 len
  uint8  from
  uint8  to
  uint64 timestamp
  uint8  sessionid[32]

The first field is the protocol type identifier. TP-DKG has an
identifier value of zero (0).

The second field in the header is really a state identifier. A
recipient MUST verify that the messageno is matching with the expected
number related to the state of the protocol.

The len field MUST be equal to the size of the packet received on the
network including the packet header.

The `from` field is simply the index of the peer, since peers are
indexed starting from 1, the value 0 is used for the trusted
party. Any value greater than 128 is invalid. The state defines from
whom to receive messages, and thus the from field MUST be validated
against these expectations.

The `to` field is similar to the `from` field, with the difference
that the value 0xff is reserved for broadcast messages. The peer (or
TP) MUST validate that it is indeed the recipient of a given message.

The timestamp field is just a 64bit timestamp as seconds elapsed since
1970/01/01, for peers that have no accurate clock themselves but do
have an RTC, the first initiating message from the TP SHOULD be used
as a reference for synchronizing during the protocol.

------<=[ Message signatures                            ]=>-----------

Every message MUST be signed using the sender peers ephemeral signing
key. The signature is made over the message and the appended session
id. The session id is announced by the TP in the first message.

------<=[ Verifying messages                            ]=>-----------

Whenever a message is received by any participant, they first MUST
check the correctness of the signature:

```
   msg, sig = recv()
   sign_pk = sign_keys[expected_sender_id]
   assert(verify(sign_pk, msg, sig))
```

The recipient MUST also assert the correctness of all the other header
fields:

```
   assert(msg.type == 0)
   assert(msg.version == 0)
   assert(msg.messageno == expected_messageno)
   assert(msg.from == expected_sender_id)
   assert(msg.to == (own_peer_id or 0xff))
   assert(ref_ts <= msg.ts < ref_ts + timeout))
   ref_ts = msg.ts
```

The value `timeout` should be configurable and be set to the smallest
value that doesn't cause protocol aborts due to slow responses.

If at any step of the protocol the TP receives one or more messages
that fail these checks, the TP MUST abort the protocol and report all
violating peers to the user.

------<=[ Message transmission                          ]=>-----------

A higher level message transmission interface can be provided, for
sending:

```
msg, sig = send_msg(msgno, from, to, sign_sk, session_id, data)
    ts = timestamp()
    msg = messageno: msgno, len: len(header) + len(data) + len(sig), from: from, to: to, ts: ts, data
    sig = sign(sign_sk, msg)
    return msg, sig
```

And for validating incoming messages:

```

data = recv_msg(msgno, from, to, ref_ts, sign_pk, session_id, msg, sig)
    assert(verify(sign_pk, msg, sig)
    assert(msg.type == 0)
    assert(msg.version == 0)
    assert(msg.messageno == msgno)
    assert(msg.len == len(msg|sig))
    assert(msg.from == from)
    assert(msg.to == to)
    assert(ref_ts < msg.ts < ref_ts + timeout))

    if msg.to == 0xff:
        update_ts(state,msg,sig)
```

The parameters `msgno`, `from`, `to`, `session_id` should be the
values expected according to the current protocol state.

------<=[ Cheater detection                             ]=>-----------

The TP MUST report to the user all errors that can identify cheating
peers in a given step. For each detected cheating peer the TP MUST
record the following information:

 - the current protocol step,
 - the violating peer,
 - the other peer involved, and
 - the type of violation

In order to detect other misbehaving peers in the current step,
processing for the rest of the SHOULD peers continue until the end of
the current step. Any further violations should be recorded as above.

Before the next message to the peers is sent, the TP must
check if there are no noted violations, if so the TP aborts and
reports all violators with their parameters to the user.

Abort conditions include any errors detected by recv_msg(), or when
the number of complaints is more than t for one peer, or more than t^2
in total, as well any of the checks of the JF-DKG algorithm from
GJKR06.

------<=[ The protocol                                  ]=>-----------

------<=[ 0. Precondition                               ]=>-----------

Peers use TLS or TP knows long-term encryption keys for all peers.

Client knows long-term signing keys of all peers.

------<=[ 1. DKG Announcement - TP(peers, t, proto_name) ]=>----------

The protocol starts by asking the trusted party (TP) to initiate a new
run of the DKG protocol by providing it with:
  - a list of the peers,
  - a threshold value, and
  - protocol instance name used as a domain separation token.

The TP then sanity checks these parameters:

```
n = len(peers)
assert(2<=t<n)
assert(len(proto_name)>0)
```

The TP then generates a fresh session id, and a hash of the DST.

The TP then creates a broadcast message containing the session id, a
hash (so that the message is always of fixed size) of the DST,
the values N and T and its own public signing key:

```
dst_str = "TP DKG for protocol %s" % proto_name
dst = hash(I2OSP(len(dst_str)) | dst_str)
sessionid = random_bytes(32)
data = {dst, n, t, tp_sign_pk}
msg_0, sig_0 = send_msg(0, 0, 0xff, tp_sign_sk, session_id, data)
broadcast(msg_0 | sig_0)
```

The TPs copy of the transcript is initialized by the TP, and updated
with the value of the 1st broadcast message:

```
state = hash_init("tp dkg session transcript")
state = update_ts(state, msg, sig)
```

Since the order of the peers is random, and important for the protocol
a custom message is created for each peer by the TP and sent
individually notifying each peer of their index in this protocol
run. This is essentially an empty message consisting only of a
header. The msg.to field conveys the index of the peer.

```
# sending each peer its index
for i in 1..n:
  msg_1, sig_1 = send_msg(1, 0, i, tp_sign_sk, session_id, {})
  send(i, msg_1 | sig_1)
```

------<=[ 2. each peer(msg_0, sig_0)                   ]=>------------

In this step each peer receives the initial parameter broadcast,
verifies it, initializes the transcript and adds the initial
message. Then receives the message assigning its index.

```
msg_0, sig_0 = recv()
assert(recv_msg(0, 0, 0xff, ref_ts, msg.data.tp_sign_pk, session_id, msg_0, sig_0))
```

If the peer has no accurate internal clock but has at least an RTC, it
SHOULD set the ref_ts to the message timestamp:

```
ref_ts = msg_0.ts
```

Furthermore the peer MUST also verify that the N&T parameters are
sane, and if possible the peer SHOULD also check if the session id is
fresh (if it is not possible, isfresh() MAY always return true.

```
assert(2 <= msg_0.t < n)
assert(isfresh(msg_0,sessionid))
```

The transcript MUST be initialized by the peer, and updated with the
value of the 1st broadcast message:

```
state = hash_init("tp dkg session transcript")
state = update_ts(state, msg, sig)
```

After processing the broadcast message from the TP, the peers also
have to process the second message from the TP in which they are
assigned their index.

```
sig1, msg1 = recv()
assert(recv_msg(1, 0, msg1.to, ref_ts, tp_sign_pk, session_id, msg_1, sig_1))
assert(msg1.to <= 128 and msg1.to > 0)
peerid = msg.to
```

------<=[ 3. peers broadcast their keys via TP        ]=>-------------

If this protocol requires anonymity from each peer all peers broadcast
fresh signing and noise keys to all peers via the TP. If no
peer-anonymity is required it is ok to either send long-term keys keys
here, or skip to the 2nd half or step 5 below.

In order to assure the TP that the peer is authentic, this message is
additionally signed by the peers long-term signing key - which must be
known in advance by the TP. This ensures that the fresh ephemeral keys
belong to the peer and not some adversary.

```
peer_sign_sk, peer_sign_pk = sign_genkey()
peer_noise_sk, peer_noise_pk = noise_genkey()

msg_2, sig_2 = send_msg(2, peerid, 0xff, peer_sign_sk, session_id, {peer_sign_pk, peer_noise_pk})
ltsig = sign(peer_long_term_sig_sk, msg_2|sig_2)
broadcast(ltsig | msg_2 | sig_2 )
```

------<=[ 4. TP collects and broadcasts all peer keys ]=>-------------

The TP first checks if each of the received messages is signed by the
expected long-term signing key, if this fails the TP aborts. If all
long-term signatures are correct the TP MUST strip those signatures
from all the messages. This is to ensure their anonymity from each
other.

Then the TP acts as a broadcast medium on the long-term
signature-stripped messages.

This is a recurring pattern where the TP acts in its broadcasting
intermediary role:

  1. receives the messages from each peer
  2. validates the message using recv_msg()
  3. extracts all signing pubkeys (or other information depending on
     the current step) for usage by the TP in the rest of the protocol
  4. concatenates all received messages into a new message
  5. signs the message of messages
  6. adds this the message of messages and its signature to the transcript
  7. sends it to all peers

```
peer_sig_pks = []
msgs = []
for i in 1..N
   ltsig, msg_2, sig_2 = recv()
   assert(verify(lt_sign_pk[i], msg_2 | sig_2, ltsig))
   sig_pk, noise_pk = recv_msg(2, i, 0xff, ref_ts, msg_2.data.peer_sign_pk, session_id, msg_2, sig_2)
   peer_sig_pks[i] = sig_pk
   msgs = msgs | { msg_2 , sig_2 }

msg_3, sig_3 = send_msg(3, 0, 0xff, tp_sign_sk, session_id, msgs)

state = update_ts(state, msg_3, sig_3)

broadcast(msg_3|sig_3)
```

------<=[ 5. each peer get all keys and initiate noise channels with all peers ]=>-------

In this phase all peers process the broadcast signing and noise keys
received from all peers, and initiate a noise_xk handshake with each
of them (including themselves for simplicity and thus security).

Note: For performance it MAY be, that each peer only initiates
handshakes with peers having a higher index than themselves. But this
would create a packet-size and timing side-channel revealing the index
of the peer.

```
msg_3, sig_3 = recv()
msgs = recv_msg(3, 0, 0xff, ref_ts, tp_sign_pk, session_id, msg_3, sig_3)

state = update_ts(state, msg_3, sig_3)

peers_sign_pks = []
peers_noise_pks = []
send_session = []
for i in 1..N
   msg, sig = msgs[i]
   peers_sign_pks[i], peers_noise_pks[i] = recv_msg(2, i, 0xff, ref_ts, msg.peer_sign_pk, session_id, msg, sig)
   send_session[i], handshake1 = noisexk_initiator_session(peer_noise_sk, peers_noise_pks[i])
   msg, sig = send_msg(4,peerid,i,peer_sign_sk, session_id, handshake1)
   send(msg | sig)
```

------<=[ 6. TP routes handshakes from each peer to each peer ]=>-------

The TP receives all 1st handshake messages from all peers and routes
them correctly to their destination. These messages are not broadcast,
each of them is a P2P message. The benefit of the TP forming a star
topology here is, that the peers can be on very different physical
networks (wifi, lora, uart, nfc, bluetooth, etc) and only the TP needs
to be able to connect to all of them.

```
for i in 1..N
   handshakes = recv(i)
   for j in 1..N
       send(j, handshakes[j])
```

------<=[ 7. each peer responds to each handshake each peer ]=>-------

Peer receives noise handshake1 from each peer and responds with
handshake2 answer to each peer.

```
for i in 1..N
   msg, sig = recv()
   handshake1 = recv_msg(4, i, peerid, ref_ts, peers_sign_pks[i], session_id, msg, sig)
   receive_session[i], handshake2 = noisexk_responder_session(peer_noise_sk, handshake1)
   msg, sig = send_msg(5, peerid, i, peer_sign_sk, session_id, handshake2)
   send(msg | sig)
```

------<=[ 8. TP routes handshakes from each peer to each peer ]=>-------

TP just routes all P2P messages from all peers to the correct
recipients of the messages.

```
for i in 1..N
   handshakes = recv(i)
   for j in 1..N
       send(j, handshakes[j])
```

------<=[ 9. each peer completes each handshake with each peer ]=>-------

Peers complete the noise handshake.

```
for i in 1..N
   msg, sig = recv()
   handshake3 = recv_msg(5, i, peerid, ref_ts, peers_sign_pks[i], session_id, msg, sig)
   send_session[i] = noisexk_initiator_session_complete(send_session[i], handshake3)
```

------<=[ 10. Setup complete                                  ]=>-------

Each peer has a confidential connection with every peer (including self, for simplicity)

The one time this channel is used, when distributing the shares from
step 13. The sender uses the initiator interface of the noise session,
and the receiver uses the responder interface.

------<=[ 11. each peer executes DKG Round 1                  ]=>-------

This step is as described by GJKR06 (fig 1. JF-DKG) step 1: Each party
P_i (as a dealer) chooses a random polynomial f_i(z) over Z_q of degree t:

      f_i(z) = a_(i0) + a_(i1)z + ยทยทยท + a_(it)z^t

P_i broadcasts A_ik = g^(a_ik) mod p for k = 0,... ,t.
Each P_i computes the shares s_ij = f_i(j) mod q for j = 1, ... ,n.

```
a = []
A = []
for i in 0..t
  a[i]=randombytes(32)
  A[i]=g*a[i]

s = []
for i in 1..N
  for j in 0..t
    s[i]+=a[j]*i^j

msg_6, sig_6 = send_msg(6, peerid, 0xff, peer_sign_sk, session_id, A)
send(msg_6 | sig_6)
```

------<=[ 12. TP collects and broadcasts all A vectors        ]=>-------

This is another broadcast pattern instance:
receive-verify-collect-sign-transcript-broadcast. The TP keeps a copy
of all commitments being broadcast.

```
A = [][]
msgs = []
for i in 1..N
   msg_6, sig_6 = recv(i)
   A[i] = recv_msg(6, i, 0xff, ref_ts, peer_sign_pks[i], session_id, msg_6, sig_6)
   msgs = msgs | { msg_6 , sig_6 }

msg_7, sig_7 = send_msg(7, 0, 0xff, tp_sign_sk, session_id, msgs)

state = update_ts(state, msg_7, sig_7)

broadcast(msg_7|sig_7)
```

------<=[ 13. each peer collects all A vectors and distributes their generated shares ]=>-------

All peers receive the bundled A commitment messages which have been
sent by all peers and re-broadcast by the TP. First the bundle is
verified, then each message containing the j-th A commitment vector is
also verified. A copy of all A commitment vectors is retained for
later usage. Then the share for the j-th peer is sent using the
previously established noise channel to the j-th peer. These shares
have been already computed in step 11, as per the step 1 of the JF-DKG
algorithm from the GJKR06 paper.

```
msg_7, sig_7 = recv()
msgs = recv_msg(7, 0, 0xff, ref_ts, tp_sign_pk, session_id, msg_7, sig_7)

state = update_ts(state, msg_7, sig_7)

A=[][]
for i in 1..N
   msg, sig = msgs[i]
   A[i] = recv_msg(6, i, 0xff, ref_ts, peer_sign_pks[i], session_id, msg, sig)

   pkt = noise_send(send_session[i], s[i])
   msg, sig = send_msg(8,peerid,i,peer_sign_sk, session_id, pkt)
   send(msg | sig)
```

------<=[ 14. TP routes noise protected messages between peers ]=>-------

Since all these messages are confidential P2P messages protected by
noise, all the TP is doing in this step is routing each packet to its
correct destination. For the resolution of complaints and cheater
identification, TP keeps a copy of all messages.

```
encrypted_shares = [][]

for i in 1..N
   for j in 1..N
      msg = recv(i)
      send(j, msg)
      encrypted_shares[i][j] = msg
```

------<=[ 15. each peer executes DKG Round 2                  ]=>-------

Each peer having received all their shares from all the peers,
verifies the messages, and then verifies the shares against the
previously broadcast A commitment vectors. For each s_ij, A_i pair
that fails, a complaint against the peer producing the conflicting
commitment and share is logged in an array, which is broadcast to
everyone. This is essentially step 2 from the JF-DKG algorithm
described in GJKR06.

```
s=[]
for i in 1..N
   msg, sig = recv()
   pkt = recv_msg(8, i, peerid, ref_ts, peer_sign_pks[i], session_id, msg, sig)
   s[i] = noise_recv(receive_session[i], pkt)


complaints = []
for i in 1..N
   v = 0
   for k in 0..t
      v += A[i][k]*peerid*k
   if (g*s[i] != v)
      complaints = complaints | i

msg, sig = send_msg(9, peerid, 0xff, peer_sign_sk, session_id, len(complaints) | complaints)
send(msg | sig)
```

------<=[ 16. TP collects complaints                          ]=>-------

Another receive-verify-collect-sign-transcribe-broadcast
instantiation. The TP keeps a copy of all complaints for the 18th
step.

If any peer complaints about more than t peers, that complaining peer
is a cheater, and must be disqualified. Furthermore if there are in
total more than t^2 complaints there are multiple cheaters and the
protocol must be aborted and new peers must be chosen in case a rerun
is initiated.

```
complaints = []
msgs = []
for i in 1..N
   msg_9, sig_9 = recv(i)
   complaints_i = recv_msg(9, i, 0xff, ref_ts, peer_sign_pks[i], session_id, msg_9, sig_9)
   assert(len(complaints_i) < t)
   complaints = complaints | complaints_i
   msgs = msgs | { msg_9 , sig_9 }

assert(len(complaints) < t^2)

msg_10, sig_10 = send_msg(10, 0, 0xff, tp_sign_sk, session_id, msgs)

state = update_ts(state, msg_10, sig_10)

broadcast(msg_10|sig_10)
```

The next step of the protocol depends on the number of complaints
received, if none then the next step is 21. otherwise 18.

If the next TP step is 18 (there are complaints) the next input buffer
size depends on the number of complaints against each peer.

Each complaint is answered by the symmetric encryption key used to
encrypt the share of the accused belonging to the complainer. Each
accused packs all answers into one message.

------<=[ 17. Each peer receives all complaints               ]=>-------

All complaint messages broadcast are received by each peer. If peer_i
is being complained about by peer_j, peer_i sends the symmetric
encryption key that was used to encrypt s_ij to the TP. This is the
first part of step 3. in JF-DKG of GJKR06. There is a slight
variation, instead of broadcasting the share, the accused peer reveals
the symmetric encryption key that was used to encrypt the share. The
TP has a copy of this encrypted message, and with the symmetric
encryption key, it can decrypt the originally sent share. This is some
kind of poor mans provable encryption.

If any complaints have been lodged by any peer the protocol ends here
for all the peers.

```
msg_10, sig_10 = recv()
msgs = recv_msg(10, 0, 0xff, ref_ts, tp_sign_pk, session_id, msg_10, sig_10)
state = update_ts(state, msg_10, sig_10)
keys = []

for i in 1..N
   msg, sig = msgs[i]
   complaints_len, complaints = recv_msg(9, i, 0xff, ref_ts, peers_sign_pks[i], session_id, msg, sig)

   for k in 0..complaints_len
      if complaints[k] == peerid
          # complaint about current peer, publish key used to encrypt s_ij
          keys = keys | send_session[i].key

if len(keys) > 0
   msg_11, sig_11 = send_msg(11, peer, 0, peer_sign_sk, session_id, keys)
   send(msg_11, sig_11)
```

------<=[ 18. TP collects all s_ij, broadcasts and verifies them ]=>-------

In this step TP checks equation 3 from step 2 in JF-DKG of GJKR06.

TP also checks if all complaints lodged earlier are answered by the
correct s_ij shares. The shares to be verified are decrypted from the
previously encrypted messages, using the revealed encryption keys by
the accused peers.

The protocol ends here, as either the complainer or the accused tried
to cheat.

```
for i in 1..N
    if len(complaints[i]) < 1
        continue

    msg, sig = recv(i)
    keys = recv_msg(11, i, 0, ref_ts, peers_sign_pks[i], session_id, msg, sig)
    assert(len(keys) == len(complaints[i]))
    sij=[][]
    for j, key in keys
       sij[i][j]=decrypt(key, encrypted_shares[i][j])

    for complaint in complaints[i]
        v = 0
        for k in 0..t
            v += A[i][k]*peerid*k
        if(g*sij[complaint.from][complaint.data] != v)
            suspicious = suspicious | identity(i)
        else
            suspicious = suspicious | identity(j)
```

------<=[ 19. Compare all transcripts                         ]=>-------

Each peer calculates the final transcripts and sends it to TP.

```
transcript = final_ts(state)
msg_20, sig_20 = send_msg(20, peerid, 0, peer_sign_sk, session_id, transcript)
send(msg_20, sig_20)
```

------<=[ 20. TP receives all and verifies transcripts        ]=>-------

TP receives all transcripts, and asserts that they all match its own
transcript, it aborts if any transcript mismatch is detected. If
everything matches it broadcasts the result either as OK.

```
transcript = final_ts(state)

for i in 1..N
    msg, sig = recv(i)
    ts = recv_msg(20, i, 0xff, ref_ts, peers_sign_pks[i], session_id, msg, sig)
    assert( ts == transcript)

msg_21, sig_21 = send_msg(21, 0, 0xff, tp_sign_sk, session_id, { "OK" })

------<=[ 21. SUCCESS, peers set their share and confirm      ]=>-------

All peers receive the OK acknowledgment from the TP and calculate the
final share, this is equivalent with the calculation of x_j in the
4. step in JF-DKG of GJKR06. Finally all peers acknowledge this step
with another "OK" message sent to the TP. This is the final step for
the peers, each needs to persist the calculated x_j share for usage in
later threshold protocol runs (such as tOPRF).

```
msg_21, sig_21 = recv()
recv_msg(21, 0, 0xff, ref_ts, tp_sign_pk, session_id, msg_21, sig_21)

share = 0
for i in 1..N
   share += s[i]

msg_22, sig_22 = send_msg(22, peerid, 0, peers_sign_sk, session_id, "OK")

persist(own_peer_id, share)
```

------<=[ 22. TP asserts all peers respond with "OK"          ]=>-------

The TP collects all "OK" messages from all peers.

```
for i in 1..N
    msg, sig = recv(i)
    ok = recv_msg(22, i, 0, ref_ts, peers_sign_pks[i], session_id, msg, sig)
    assert( ok == "OK")
```

This successfully concludes the protocol.