-
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?
Conversation
Group structure 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. This code has been ported from keep-network/keep-core random beacon's GJKR with small tweaks.
GJKR protocol allows the key generation to continue even if some misbehavior inside of the group is detected. We do not want to stop protocols returning an error yet we want to log all the misbehavior. To not complicate the API of GJKR, a Logger interface implementation will have to be provided when instantiating GJKR protocol members. The current implementation is aligned with the Cosmos SDK logger interface but the client code for the library can implements their own adapters.
GJKR protocol requires group members to have access to messages exchanged between the accuser and the accused party for the stake of complaint resolution. This is what the evidence log provides. For now, only functions for the ephemeral public key message are included. The code has been ported from keep-network/keep-core GJKR implementation for the random beacon with small changes.
The first two steps generate a symmetric key between every two group members for the further communication over the broadcast channel. generateEphemeralKeyPair takes the group member list and generates an ephemeral ECDH keypair for every other group member. Generated public ephemeral keys are broadcasted within the group. generateSymmetricKeys attempts to generate symmetric keys for all remote group members via ECDH. It generates this symmetric key for each remote group member by doing an ECDH between the ephemeral private key generated for a remote group member, and the public key for this member, generated and broadcasted by the remote group member. The code was ported from keep-network/keep-core random beacon with small changes. There are two important parts missing that will be addressed in separate commits: - No member inactivity marking, - Unit tests for the two steps.
gjkr/evidence_log.go
Outdated
// 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 { |
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 removing dkgEvidenceLog
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 in keep-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.
gjkr/evidence_log.go
Outdated
|
||
type messageStorage struct { | ||
cache map[memberIndex]interface{} | ||
cacheLock sync.Mutex |
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.
Using sync.RWMutex
will be a small improvement here.
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.
gjkr/evidence_log.go
Outdated
} | ||
|
||
type messageStorage struct { | ||
cache map[memberIndex]interface{} |
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.
Loose idea: We can rejuvenate this code and use generics:
type messageStorage[[T interface{}] struct {
cache map[memberIndex]T
(...)
}
This way, we can create typed storages within dkgEvidenceLog
:
type dkgEvidenceLog struct {
pubKeyMessageLog *messageStorage[*ephemeralPublicKeyMessage]
}
and get rid of the type assertion in getEphemeralPublicKeyMessage
.
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 comment
The reason will be displayed to describe this comment to others. Learn more.
Good call. Done in baff3ad.
// 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: dishonestThreshold
caused some confusion in keep-core
in the past. What do you think about leaning on threshold
and exposing a function dishonestThreshold
that will compute it as groupSize - threshold
? This approach feels much more intuitive.
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.
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 +1
in keep-core
unit tests which is a symptom of the problem you described but I did not find yet in the protocol where the dishonest threshold would be problematic and I assumed we could have some reason for leaving it. What about not worrying about it now but leaving this thread open and revisiting in the next PR?
|
||
import "threshold.network/roast/ephemeral" | ||
|
||
type memberIndex uint16 |
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.
Worth dropping a comment that memberIndex
starts from 1
.
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.
See 78a4eae, please.
gjkr/message.go
Outdated
ephemeralPublicKeys map[memberIndex]*ephemeral.PublicKey // j -> Y_ij | ||
} | ||
|
||
func (m *ephemeralPublicKeyMessage) senderIdx() memberIndex { |
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.
I think Idx
can be easily confused with Id
. What about the full name:
func (m *ephemeralPublicKeyMessage) senderIdx() memberIndex { | |
func (m *ephemeralPublicKeyMessage) senderIndex() memberIndex { |
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.
This is what I started with but the compiler does not like the fact the field name and the function name are the same. I would be happy to remove this function altogether but we need a type constraint for func deduplicateBySender[T interface{ senderIndex() memberIndex }](list []T,) []T
. Since senderIdx
is used only there, I figured this might be okay.
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.
I refactored it in 02c828c. After adding sessionID
, this conflict was not the only one so I ended up with get*
functions.
gjkr/group.go
Outdated
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 comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above:
for _, disqualifiedMemberIndex := range g.disqualifiedMemberIndexes { | |
if memberIndex == disqualifiedMemberIndex { | |
return true | |
} | |
} | |
return false | |
return slices.Contains(g.disqualifiedMemberIndexes, memberIndex) |
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.
Totally! Please see 2ea14cd.
// must know its ephemeral public key prior to exchanging any messages. Hence, | ||
// this message contains all the generated public keys and it is broadcast | ||
// within the group. | ||
type ephemeralPublicKeyMessage struct { |
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.
I think we need a sessionID
as well. If we don't want to add it in this PR, let's add a TODO at least.
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.
I was thinking about moving session management outside of the main protocol but after reconsidering it, I think we should have it included, as originally. It could happen inactivity or disqualification could lead to slashing (out of the scope of this protocol) and we should not make assumptions about how the messages are signed. Including session ID on the protocol level would eliminate replay attacks at the core. Added a point to the TODO list.
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.
Added session ID filtering in 02c828c.
It does not make sense to separate the interface from the implementation as we keep them in the same package and will use the concrete implementation for the code and tests.
The messageStore structure used internally for the evidenceLog can use generics for a shorter code. We could also use RWMutex to separate read and write locks.
With this version of Go we can use slices.Contains instead of reinventing the wheel.
This will allow us to use slices.Equal (to be used in the next commit). Also, 1.22.2 is the version used by mezo-org/mezod that will probably be the first integration of this library.
The phase 2 of the GJKR protocol generating symmetric keys should find members inactive in phase 1 and mark them accordingly. I introduced this logic in a new file, message_filter.go and moved there the deduplication logic as well. The goal is to have all the message pre-processing for all phases in one place. For phase 2, the pre-processing includes deduplication, inactive member detection, and session ID handling (to be added in the next commits). For further phases, we will also have to filter out members marked as inactive or disqualified in previous phases of the protocol.
Added message pre-processing step ensuring messages with a session ID different from the current one are filtered out. This protects against the replay attacks on the protocol level.
In the previous commit a filtering by session ID was implemented but I missed adding the session ID to the ephemeralPublicKeyMessage generated by the first phase of the protocol. Doing it now.
When we pre-process messages before executing GJKR protocol phase, we now also remove the from-self message from the list.
The test executes phase 1 and phase 2 and ensures the expected number of symmetric keys has been generated.
This is ready for another chance @lukasz-zimnoch. |
Depends on #16The first two steps generate a symmetric key between every two group members for further communication over the broadcast channel.
generateEphemeralKeyPair
takes the group member list and generates an ephemeral ECDH key pair for every other group member. Generated public ephemeral keys are broadcasted within the group.generateSymmetricKeys
attempts to generate symmetric keys for all remote group members via ECDH. It generates this symmetric key for each remote group member by doing an ECDH between the ephemeral private key generated for a remote group member, and the public key for this member, generated and broadcasted by the remote group member.The code was ported from the keep-network/keep-core random beacon with some changes.
I am intentionally making no functions/types as exported - leaving it for later once it is decided how to use this package.