Skip to content

Commit

Permalink
Detect and mark inactive members after the ephemeral pubkey generation
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
pdyraga committed Oct 22, 2024
1 parent f7fe459 commit 12980f7
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 20 deletions.
55 changes: 55 additions & 0 deletions gjkr/message_filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package gjkr

// findInactive goes through the messages passed as a parameter and finds all
// inactive members for this set of messages. The function does not care if
// the given member was already marked as inactive before. The function makes no
// assumptions about the ordering of the list elements.
func findInactive[T interface{ senderIdx() memberIndex }](
groupSize uint16, list []T,
) []memberIndex {
senders := make(map[memberIndex]bool)
for _, item := range list {
senders[item.senderIdx()] = true
}

inactive := make([]memberIndex, 0)
for i := uint16(1); i <= groupSize; i++ {
if !senders[memberIndex(i)] {
inactive = append(inactive, memberIndex(i))
}
}

return inactive
}

// deduplicateBySender removes duplicated items for the given sender. It always
// takes the first item that occurs for the given sender and ignores the
// subsequent ones.
func deduplicateBySender[T interface{ senderIdx() memberIndex }](
list []T,
) []T {
senders := make(map[memberIndex]bool)
result := make([]T, 0)

for _, item := range list {
if _, exists := senders[item.senderIdx()]; !exists {
senders[item.senderIdx()] = true
result = append(result, item)
}
}

return result
}

func (m *symmetricKeyGeneratingMember) preProcessMessages(
ephemeralPubKeyMessages []*ephemeralPublicKeyMessage,
) []*ephemeralPublicKeyMessage {
inactiveMembers := findInactive(m.group.groupSize, ephemeralPubKeyMessages)
for _, ia := range inactiveMembers {
m.group.markMemberAsInactive(ia)
}

// TODO: validate session ID

return deduplicateBySender(ephemeralPubKeyMessages)
}
93 changes: 93 additions & 0 deletions gjkr/message_filter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package gjkr

import (
"testing"

"threshold.network/roast/internal/testutils"
)

func TestFindInactive(t *testing.T) {
var tests = map[string]struct {
groupSize uint16
senders []memberIndex
expectedIA []memberIndex
}{
"with no inactive senders": {
groupSize: 5,
senders: []memberIndex{1, 4, 3, 2, 5},
expectedIA: []memberIndex{},
},
"with inactivity and senders ordered": {
groupSize: 5,
senders: []memberIndex{1, 3, 5},
expectedIA: []memberIndex{2, 4},
},
"with inactivity and senders not ordered": {
groupSize: 5,
senders: []memberIndex{5, 1, 3},
expectedIA: []memberIndex{2, 4},
},
"with all senders inactive": {
groupSize: 5,
senders: []memberIndex{},
expectedIA: []memberIndex{1, 2, 3, 4, 5},
},
}

for testName, test := range tests {
t.Run(testName, func(t *testing.T) {
messages := make([]*ephemeralPublicKeyMessage, len(test.senders))
for i, senderIndex := range test.senders {
messages[i] = &ephemeralPublicKeyMessage{senderIndex: senderIndex}
}

ia := findInactive(test.groupSize, messages)
testutils.AssertUint16SlicesEqual(
t,
"inactive members",
test.expectedIA,
ia,
)
})
}
}

func TestDeduplicateBySender(t *testing.T) {
var tests = map[string]struct {
senders []memberIndex
expectedDeduplicated []memberIndex
}{
"with no duplicates": {
senders: []memberIndex{1, 4, 3, 2, 5},
expectedDeduplicated: []memberIndex{1, 4, 3, 2, 5},
},
"with duplicates and senders ordered": {
senders: []memberIndex{1, 1, 2, 3, 3, 4, 5, 5},
expectedDeduplicated: []memberIndex{1, 2, 3, 4, 5},
},
"with duplicates and senders not ordered": {
senders: []memberIndex{5, 2, 5, 3, 1, 3, 3, 2, 5, 4, 5},
expectedDeduplicated: []memberIndex{5, 2, 3, 1, 4},
},
}

for testName, test := range tests {
t.Run(testName, func(t *testing.T) {
messages := make([]*ephemeralPublicKeyMessage, len(test.senders))
for i, senderIndex := range test.senders {
messages[i] = &ephemeralPublicKeyMessage{senderIndex: senderIndex}
}

deduplicatedSenders := make([]memberIndex, 0)
for _, msg := range deduplicateBySender(messages) {
deduplicatedSenders = append(deduplicatedSenders, msg.senderIdx())
}
testutils.AssertUint16SlicesEqual(
t,
"deduplicated senders",
test.expectedDeduplicated,
deduplicatedSenders,
)
})
}
}
21 changes: 1 addition & 20 deletions gjkr/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func (m *ephemeralKeyPairGeneratingMember) generateEphemeralKeyPair() (
func (m *symmetricKeyGeneratingMember) generateSymmetricKeys(
ephemeralPubKeyMessages []*ephemeralPublicKeyMessage,
) error {
for _, ephemeralPubKeyMessage := range deduplicateBySender(
for _, ephemeralPubKeyMessage := range m.preProcessMessages(
ephemeralPubKeyMessages,
) {
otherMember := ephemeralPubKeyMessage.senderIndex
Expand Down Expand Up @@ -134,22 +134,3 @@ func (m *symmetricKeyGeneratingMember) isValidEphemeralPublicKeyMessage(

return true
}

// deduplicateBySender removes duplicated items for the given sender.
// It always takes the first item that occurs for the given sender
// and ignores the subsequent ones.
func deduplicateBySender[T interface{ senderIdx() memberIndex }](
list []T,
) []T {
senders := make(map[memberIndex]bool)
result := make([]T, 0)

for _, item := range list {
if _, exists := senders[item.senderIdx()]; !exists {
senders[item.senderIdx()] = true
result = append(result, item)
}
}

return result
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.22.2
require (
github.com/btcsuite/btcd v0.20.1-beta
golang.org/x/crypto v0.14.0
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c
)

require (
Expand Down
9 changes: 9 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
Expand All @@ -23,12 +24,20 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
Expand Down
18 changes: 18 additions & 0 deletions internal/testutils/assert.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"math/big"
"testing"

"golang.org/x/exp/slices"
)

// AssertBigIntNonZero checks if the provided not-nil big integer is non-zero.
Expand Down Expand Up @@ -116,3 +118,19 @@ func testBytesEqual(expectedBytes []byte, actualBytes []byte) error {

return nil
}

func AssertUint16SlicesEqual[T ~uint16](
t *testing.T,
description string,
expected []T,
actual []T,
) {
if !slices.Equal(expected, actual) {
t.Errorf(
"unexpected %s\nexpected: %v\nactual: %v\n",
description,
expected,
actual,
)
}
}

0 comments on commit 12980f7

Please sign in to comment.