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.
|