Skip to content
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

Open
wants to merge 15 commits into
base: main
Choose a base branch
from

Conversation

pdyraga
Copy link
Member

@pdyraga pdyraga commented Oct 11, 2024

Depends on #16

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

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.
Base automatically changed from one-curve to main October 15, 2024 15:37
// 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 {
Copy link
Member

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.

Copy link
Member Author

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.


type messageStorage struct {
cache map[memberIndex]interface{}
cacheLock sync.Mutex
Copy link
Member

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

type messageStorage struct {
cache map[memberIndex]interface{}
Copy link
Member

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.

Copy link
Member Author

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
Copy link
Member

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.

Copy link
Member Author

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
Copy link
Member

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.

Copy link
Member Author

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 {
Copy link
Member

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:

Suggested change
func (m *ephemeralPublicKeyMessage) senderIdx() memberIndex {
func (m *ephemeralPublicKeyMessage) senderIndex() memberIndex {

Copy link
Member Author

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.

Copy link
Member Author

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
Comment on lines 71 to 77
for _, disqualifiedMemberIndex := range g.disqualifiedMemberIndexes {
if memberIndex == disqualifiedMemberIndex {
return true
}
}

return false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above:

Suggested change
for _, disqualifiedMemberIndex := range g.disqualifiedMemberIndexes {
if memberIndex == disqualifiedMemberIndex {
return true
}
}
return false
return slices.Contains(g.disqualifiedMemberIndexes, memberIndex)

Copy link
Member Author

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 {
Copy link
Member

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.

Copy link
Member Author

@pdyraga pdyraga Oct 21, 2024

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.

Copy link
Member Author

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.
@pdyraga pdyraga marked this pull request as ready for review October 25, 2024 15:13
@pdyraga
Copy link
Member Author

pdyraga commented Oct 25, 2024

This is ready for another chance @lukasz-zimnoch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants