-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The first two steps for the key generation protocol #17
base: main
Are you sure you want to change the base?
Changes from 4 commits
0ff7215
60227fa
f81defc
64205f4
20254f7
baff3ad
2ea14cd
78a4eae
f7fe459
12980f7
02c828c
ec297fd
8449f5d
c8a5ae4
6c17028
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
package gjkr | ||
|
||
import ( | ||
"fmt" | ||
"sync" | ||
) | ||
|
||
// For complaint resolution, group members need to have access to messages | ||
// exchanged between the accuser and the accused party. There are two situations | ||
// in the DKG protocol where group members generate values individually for | ||
// every other group member: | ||
// | ||
// - Ephemeral ECDH (phase 2) - after each group member generates an ephemeral | ||
// keypair for each other group member and broadcasts those ephemeral public keys | ||
// in the clear (phase 1), group members must ECDH those public keys with the | ||
// ephemeral private key for that group member to derive a symmetric key. | ||
// In the case of an accusation, members performing compliant resolution need to | ||
// validate the private ephemeral key revealed by the accuser. To perform the | ||
// validation, members need to compare public ephemeral key published by the | ||
// accuser in phase 1 with the private ephemeral key published by the accuser. | ||
// | ||
// - Polynomial generation (phase 3) - each group member generates two sharing | ||
// polynomials, and calculates shares as points on these polynomials individually | ||
// for each other group member. Shares are publicly broadcast, encrypted with a | ||
// symmetric key established between the sender and receiver. In the case of an | ||
// accusation, members performing compliant resolution need to look at the shares | ||
// sent by the accused party. To do this, they read the round 3 message from the | ||
// log, and decrypt it using the symmetric key used between the accuser and | ||
// accused party. The key is publicly revealed by the accuser. | ||
type evidenceLog interface { | ||
// getEphemeralPublicKeyMessage returns the `ephemeralPublicKeyMessage` | ||
// broadcast in the first protocol round by the given sender. | ||
getEphemeralPublicKeyMessage(sender memberIndex) *ephemeralPublicKeyMessage | ||
|
||
// putEphemeralMessage is a function that takes a single | ||
// EphemeralPubKeyMessage, and stores that as evidence for future | ||
// accusation trials for a given (sender, receiver) pair. If a message | ||
// already exists for the given sender, we return an error to the user. | ||
putEphemeralPublicKeyMessage(pubKeyMessage *ephemeralPublicKeyMessage) error | ||
} | ||
|
||
// dkgEvidenceLog is the default implementation of an evidenceLog. | ||
type dkgEvidenceLog struct { | ||
// senderIndex -> *ephemeralPublicKeyMessage | ||
pubKeyMessageLog *messageStorage | ||
} | ||
|
||
func newDkgEvidenceLog() *dkgEvidenceLog { | ||
return &dkgEvidenceLog{ | ||
pubKeyMessageLog: newMessageStorage(), | ||
} | ||
} | ||
|
||
func (d *dkgEvidenceLog) putEphemeralPublicKeyMessage( | ||
pubKeyMessage *ephemeralPublicKeyMessage, | ||
) error { | ||
return d.pubKeyMessageLog.putMessage( | ||
pubKeyMessage.senderIndex, | ||
pubKeyMessage, | ||
) | ||
} | ||
|
||
func (d *dkgEvidenceLog) getEphemeralPublicKeyMessage( | ||
sender memberIndex, | ||
) *ephemeralPublicKeyMessage { | ||
storedMessage := d.pubKeyMessageLog.getMessage(sender) | ||
switch message := storedMessage.(type) { | ||
case *ephemeralPublicKeyMessage: | ||
return message | ||
} | ||
return nil | ||
} | ||
|
||
type messageStorage struct { | ||
cache map[memberIndex]interface{} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Loose idea: We can rejuvenate this code and use generics:
This way, we can create typed storages within
and get rid of the type assertion in With generics, this code will be more elegant once we add more messages. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call. Done in baff3ad. |
||
cacheLock sync.Mutex | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
} | ||
|
||
func newMessageStorage() *messageStorage { | ||
return &messageStorage{ | ||
cache: make(map[memberIndex]interface{}), | ||
} | ||
} | ||
|
||
func (ms *messageStorage) getMessage(sender memberIndex) interface{} { | ||
ms.cacheLock.Lock() | ||
defer ms.cacheLock.Unlock() | ||
|
||
message, ok := ms.cache[sender] | ||
if !ok { | ||
return nil | ||
} | ||
|
||
return message | ||
} | ||
|
||
func (ms *messageStorage) putMessage( | ||
sender memberIndex, message interface{}, | ||
) error { | ||
ms.cacheLock.Lock() | ||
defer ms.cacheLock.Unlock() | ||
|
||
if _, ok := ms.cache[sender]; ok { | ||
return fmt.Errorf( | ||
"message exists for sender %v", | ||
sender, | ||
) | ||
} | ||
|
||
ms.cache[sender] = message | ||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package gjkr | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
|
||
"threshold.network/roast/internal/testutils" | ||
) | ||
|
||
func TestPutEphemeralPublicKeyMessageTwice(t *testing.T) { | ||
dkgEvidenceLog := newDkgEvidenceLog() | ||
err := dkgEvidenceLog.putEphemeralPublicKeyMessage( | ||
&ephemeralPublicKeyMessage{ | ||
senderIndex: memberIndex(1), | ||
}) | ||
if err != nil { | ||
t.Fatalf("unexpected error: [%v]", err) | ||
} | ||
|
||
err = dkgEvidenceLog.putEphemeralPublicKeyMessage( | ||
&ephemeralPublicKeyMessage{ | ||
senderIndex: memberIndex(1), | ||
}) | ||
if err == nil { | ||
t.Fatal("expected an error") | ||
} | ||
|
||
testutils.AssertStringsEqual( | ||
t, | ||
"error", | ||
"message exists for sender 1", | ||
err.Error(), | ||
) | ||
} | ||
|
||
func TestPutGetEphemeralPublicKeyMessage(t *testing.T) { | ||
dkgEvidenceLog := newDkgEvidenceLog() | ||
|
||
message := &ephemeralPublicKeyMessage{ | ||
senderIndex: memberIndex(1), | ||
} | ||
|
||
m := dkgEvidenceLog.getEphemeralPublicKeyMessage(memberIndex(1)) | ||
if m != nil { | ||
t.Fatalf("expected message not to be found but has [%v]", m) | ||
} | ||
|
||
err := dkgEvidenceLog.putEphemeralPublicKeyMessage(message) | ||
if err != nil { | ||
t.Fatalf("unexpected error: [%v]", err) | ||
} | ||
|
||
m = dkgEvidenceLog.getEphemeralPublicKeyMessage(memberIndex(1)) | ||
if !reflect.DeepEqual(message, m) { | ||
t.Fatalf( | ||
"unexpected message\nexpected: %v\nactual: %v", | ||
message, | ||
m, | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,78 @@ | ||||||||||||||||||
package gjkr | ||||||||||||||||||
|
||||||||||||||||||
// group represents the current state of information about the GJKR key | ||||||||||||||||||
// generation group. Each GJKR protocol participant should have the same group | ||||||||||||||||||
// state at the end of each protocol step. | ||||||||||||||||||
type group struct { | ||||||||||||||||||
dishonestThreshold uint16 | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was not sure about it either but wanted to approach it in the next PR when we will be adding the remaining steps of the protocol. I noticed we usually do |
||||||||||||||||||
groupSize uint16 | ||||||||||||||||||
|
||||||||||||||||||
allMemberIndexes []memberIndex | ||||||||||||||||||
inactiveMemberIndexes []memberIndex | ||||||||||||||||||
disqualifiedMemberIndexes []memberIndex | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
func newGroup(dishonestThreshold uint16, groupSize uint16) *group { | ||||||||||||||||||
allMemberIndexes := make([]memberIndex, groupSize) | ||||||||||||||||||
for i := uint16(0); i < groupSize; i++ { | ||||||||||||||||||
allMemberIndexes[i] = memberIndex(i + 1) | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
return &group{ | ||||||||||||||||||
dishonestThreshold: dishonestThreshold, | ||||||||||||||||||
groupSize: groupSize, | ||||||||||||||||||
allMemberIndexes: allMemberIndexes, | ||||||||||||||||||
inactiveMemberIndexes: []memberIndex{}, | ||||||||||||||||||
disqualifiedMemberIndexes: []memberIndex{}, | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
// markMemberAsDisqualified adds the member with the given index to the list of | ||||||||||||||||||
// disqualified members. If the member is not a part of the group, is already | ||||||||||||||||||
// disqualified or marked as inactive, the function does nothing. | ||||||||||||||||||
func (g *group) markMemberAsDisqualified(memberIndex memberIndex) { | ||||||||||||||||||
if g.isOperating(memberIndex) { | ||||||||||||||||||
g.disqualifiedMemberIndexes = append(g.disqualifiedMemberIndexes, memberIndex) | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
// markMemberAsInactive adds the member with the given index to the list of | ||||||||||||||||||
// inactive members. If the member is not a part of the group, is already | ||||||||||||||||||
// disqualified or marked as inactive, the function does nothing. | ||||||||||||||||||
func (g *group) markMemberAsInactive(memberIndex memberIndex) { | ||||||||||||||||||
if g.isOperating(memberIndex) { | ||||||||||||||||||
g.inactiveMemberIndexes = append(g.inactiveMemberIndexes, memberIndex) | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
// isOperating returns true if member with the given index belongs to the group | ||||||||||||||||||
// and has not been marked as inactive or disqualified. | ||||||||||||||||||
func (g *group) isOperating(memberIndex memberIndex) bool { | ||||||||||||||||||
return g.isInGroup(memberIndex) && | ||||||||||||||||||
!g.isInactive(memberIndex) && | ||||||||||||||||||
!g.isDisqualified(memberIndex) | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
func (g *group) isInGroup(memberIndex memberIndex) bool { | ||||||||||||||||||
return memberIndex > 0 && uint16(memberIndex) <= g.groupSize | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
func (g *group) isInactive(memberIndex memberIndex) bool { | ||||||||||||||||||
for _, inactiveMemberIndex := range g.inactiveMemberIndexes { | ||||||||||||||||||
if memberIndex == inactiveMemberIndex { | ||||||||||||||||||
return true | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
return false | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
func (g *group) isDisqualified(memberIndex memberIndex) bool { | ||||||||||||||||||
for _, disqualifiedMemberIndex := range g.disqualifiedMemberIndexes { | ||||||||||||||||||
if memberIndex == disqualifiedMemberIndex { | ||||||||||||||||||
return true | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
return false | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above:
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Totally! Please see 2ea14cd. |
||||||||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about making
evidenceLog
a concrete structure and removingdkgEvidenceLog
altogether? It does not make much sense to have an interface here. We are using one in-house implementation anyway. I know this was the original design inkeep-core
but maybe we can take the opportunity and clean it up.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. I was blindly assuming we'll use some sort of a mock interface in protocol integration tests but I browsed
keep-core
and I see it is not the case. Updated in 20254f7.