Skip to content

Commit 8ca5f26

Browse files
committed
Implement gnosis message handlers and interceptors
1 parent bffe04e commit 8ca5f26

File tree

3 files changed

+347
-25
lines changed

3 files changed

+347
-25
lines changed

rolling-shutter/keyperimpl/gnosis/handlers.go

Lines changed: 182 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,222 @@ package gnosis
22

33
import (
44
"context"
5+
"math"
56

7+
"github.com/ethereum/go-ethereum/common"
8+
"github.com/jackc/pgx/v4"
9+
"github.com/jackc/pgx/v4/pgxpool"
610
pubsub "github.com/libp2p/go-libp2p-pubsub"
711
"github.com/pkg/errors"
12+
"github.com/rs/zerolog/log"
813

14+
obskeyperdatabase "github.com/shutter-network/rolling-shutter/rolling-shutter/chainobserver/db/keyper"
15+
corekeyperdatabase "github.com/shutter-network/rolling-shutter/rolling-shutter/keyper/database"
16+
"github.com/shutter-network/rolling-shutter/rolling-shutter/keyperimpl/gnosis/database"
917
"github.com/shutter-network/rolling-shutter/rolling-shutter/p2pmsg"
18+
"github.com/shutter-network/rolling-shutter/rolling-shutter/shdb"
1019
)
1120

12-
type DecryptionKeySharesHandler struct{}
21+
type DecryptionKeySharesHandler struct {
22+
dbpool *pgxpool.Pool
23+
}
1324

1425
func (h *DecryptionKeySharesHandler) MessagePrototypes() []p2pmsg.Message {
1526
return []p2pmsg.Message{&p2pmsg.DecryptionKeyShares{}}
1627
}
1728

1829
func (h *DecryptionKeySharesHandler) ValidateMessage(_ context.Context, msg p2pmsg.Message) (pubsub.ValidationResult, error) {
1930
keyShares := msg.(*p2pmsg.DecryptionKeyShares)
20-
_, ok := keyShares.Extra.(*p2pmsg.DecryptionKeyShares_Gnosis)
31+
extra, ok := keyShares.Extra.(*p2pmsg.DecryptionKeyShares_Gnosis)
2132
if !ok {
2233
return pubsub.ValidationReject, errors.Errorf("unexpected extra type %T, expected Gnosis", keyShares.Extra)
2334
}
35+
if extra.Gnosis == nil {
36+
return pubsub.ValidationReject, errors.New("missing extra Gnosis data")
37+
}
38+
39+
if extra.Gnosis.Slot > math.MaxInt64 {
40+
return pubsub.ValidationReject, errors.New("slot number too large")
41+
}
42+
if extra.Gnosis.TxPointer > math.MaxInt64 {
43+
return pubsub.ValidationReject, errors.New("tx pointer too large")
44+
}
45+
// TODO: check signature
46+
2447
return pubsub.ValidationAccept, nil
2548
}
2649

27-
func (h *DecryptionKeySharesHandler) HandleMessage(_ context.Context, _ p2pmsg.Message) ([]p2pmsg.Message, error) {
50+
func (h *DecryptionKeySharesHandler) HandleMessage(ctx context.Context, msg p2pmsg.Message) ([]p2pmsg.Message, error) {
51+
keyShares := msg.(*p2pmsg.DecryptionKeyShares)
52+
extra := keyShares.Extra.(*p2pmsg.DecryptionKeyShares_Gnosis).Gnosis
53+
54+
gnosisDB := database.New(h.dbpool)
55+
keyperCoreDB := corekeyperdatabase.New(h.dbpool)
56+
obsKeyperDB := obskeyperdatabase.New(h.dbpool)
57+
58+
identitiesHash := computeIdentitiesHashFromShares(keyShares.Shares)
59+
err := gnosisDB.InsertSlotDecryptionSignature(ctx, database.InsertSlotDecryptionSignatureParams{
60+
Eon: int64(keyShares.Eon),
61+
Block: int64(extra.Slot),
62+
KeyperIndex: int64(keyShares.KeyperIndex),
63+
TxPointer: int64(extra.TxPointer),
64+
IdentitiesHash: identitiesHash,
65+
Signature: extra.Signature,
66+
})
67+
if err != nil {
68+
return []p2pmsg.Message{}, errors.Wrap(err, "failed to insert tx pointer vote")
69+
}
70+
71+
eonData, err := keyperCoreDB.GetEon(ctx, int64(keyShares.Eon))
72+
if err != nil {
73+
return []p2pmsg.Message{}, errors.Wrapf(err, "failed to get eon data from database for eon %d", keyShares.Eon)
74+
}
75+
keyperSet, err := obsKeyperDB.GetKeyperSetByKeyperConfigIndex(ctx, eonData.KeyperConfigIndex)
76+
if err != nil {
77+
return []p2pmsg.Message{}, errors.Wrapf(err, "failed to get keyper set from database for eon %d", keyShares.Eon)
78+
}
79+
80+
signaturesDB, err := gnosisDB.GetSlotDecryptionSignatures(ctx, database.GetSlotDecryptionSignaturesParams{
81+
Eon: int64(keyShares.Eon),
82+
Block: int64(extra.Slot),
83+
TxPointer: int64(extra.TxPointer),
84+
IdentitiesHash: identitiesHash,
85+
Limit: keyperSet.Threshold,
86+
})
87+
if err != nil {
88+
return []p2pmsg.Message{}, errors.Wrap(err, "failed to count slot decryption signatures")
89+
}
90+
91+
// send a keys message if we have reached the required number of both the signatures and the key shares
92+
if len(signaturesDB) >= int(keyperSet.Threshold) {
93+
keys := []*p2pmsg.Key{}
94+
for _, share := range keyShares.GetShares() {
95+
decryptionKeyDB, err := keyperCoreDB.GetDecryptionKey(ctx, corekeyperdatabase.GetDecryptionKeyParams{
96+
Eon: int64(keyShares.Eon),
97+
EpochID: share.EpochID,
98+
})
99+
if err == pgx.ErrNoRows {
100+
return []p2pmsg.Message{}, nil
101+
}
102+
key := &p2pmsg.Key{
103+
Identity: share.EpochID,
104+
Key: decryptionKeyDB.DecryptionKey,
105+
}
106+
keys = append(keys, key)
107+
}
108+
signerIndices := []uint64{}
109+
signatures := [][]byte{}
110+
for _, signature := range signaturesDB {
111+
signerIndices = append(signerIndices, uint64(signature.KeyperIndex))
112+
signatures = append(signatures, signature.Signature)
113+
}
114+
decryptionKeysMsg := &p2pmsg.DecryptionKeys{
115+
InstanceID: keyShares.InstanceID,
116+
Eon: keyShares.Eon,
117+
Keys: keys,
118+
Extra: &p2pmsg.DecryptionKeys_Gnosis{
119+
Gnosis: &p2pmsg.GnosisDecryptionKeysExtra{
120+
Slot: extra.Slot,
121+
TxPointer: extra.TxPointer,
122+
SignerIndices: signerIndices,
123+
Signatures: signatures,
124+
},
125+
},
126+
}
127+
return []p2pmsg.Message{decryptionKeysMsg}, nil
128+
}
129+
28130
return []p2pmsg.Message{}, nil
29131
}
30132

31-
type DecryptionKeysHandler struct{}
133+
type DecryptionKeysHandler struct {
134+
dbpool *pgxpool.Pool
135+
}
32136

33137
func (h *DecryptionKeysHandler) MessagePrototypes() []p2pmsg.Message {
34138
return []p2pmsg.Message{&p2pmsg.DecryptionKeys{}}
35139
}
36140

37-
func (h *DecryptionKeysHandler) ValidateMessage(_ context.Context, msg p2pmsg.Message) (pubsub.ValidationResult, error) {
141+
func (h *DecryptionKeysHandler) ValidateMessage(ctx context.Context, msg p2pmsg.Message) (pubsub.ValidationResult, error) {
38142
keys := msg.(*p2pmsg.DecryptionKeys)
39-
_, ok := keys.Extra.(*p2pmsg.DecryptionKeys_Gnosis)
143+
extra, ok := keys.Extra.(*p2pmsg.DecryptionKeys_Gnosis)
40144
if !ok {
41145
return pubsub.ValidationReject, errors.Errorf("unexpected extra type %T, expected Gnosis", keys.Extra)
42146
}
147+
if extra.Gnosis == nil {
148+
return pubsub.ValidationReject, errors.New("missing extra Gnosis data")
149+
}
150+
151+
if extra.Gnosis.Slot > math.MaxInt64 {
152+
return pubsub.ValidationReject, errors.New("slot number too large")
153+
}
154+
if extra.Gnosis.TxPointer > math.MaxInt32 { // the pointer will have to be incremented
155+
return pubsub.ValidationReject, errors.New("tx pointer too large")
156+
}
157+
if len(keys.Keys) == 0 {
158+
return pubsub.ValidationReject, errors.New("msg does not contain any keys")
159+
}
160+
161+
keyperCoreDB := corekeyperdatabase.New(h.dbpool)
162+
obsKeyperDB := obskeyperdatabase.New(h.dbpool)
163+
eonData, err := keyperCoreDB.GetEon(ctx, int64(keys.Eon))
164+
if err != nil {
165+
return pubsub.ValidationReject, errors.Wrapf(err, "failed to get eon data from database for eon %d", keys.Eon)
166+
}
167+
keyperSet, err := obsKeyperDB.GetKeyperSet(ctx, eonData.ActivationBlockNumber)
168+
if err != nil {
169+
return pubsub.ValidationReject, errors.Wrapf(err, "failed to get keyper set from database for eon %d", keys.Eon)
170+
}
171+
172+
if int32(len(extra.Gnosis.SignerIndices)) != keyperSet.Threshold {
173+
return pubsub.ValidationReject, errors.Errorf("expected %d signers, got %d", keyperSet.Threshold, len(extra.Gnosis.SignerIndices))
174+
}
175+
signers := []common.Address{}
176+
for i, signerIndex := range extra.Gnosis.SignerIndices {
177+
if i >= 1 {
178+
prevSignerIndex := extra.Gnosis.SignerIndices[i-1]
179+
if signerIndex == prevSignerIndex {
180+
return pubsub.ValidationReject, errors.New("duplicate signer index found")
181+
}
182+
if signerIndex < prevSignerIndex {
183+
return pubsub.ValidationReject, errors.New("signer indices not ordered")
184+
}
185+
}
186+
if signerIndex >= uint64(len(keyperSet.Keypers)) {
187+
return pubsub.ValidationReject, errors.New("signer index out of range")
188+
}
189+
signer, err := shdb.DecodeAddress(keyperSet.Keypers[signerIndex])
190+
if err != nil {
191+
return pubsub.ValidationReject, errors.Wrap(err, "failed to decode signer address")
192+
}
193+
signers = append(signers, signer)
194+
}
195+
196+
// TODO: check signatures
197+
43198
return pubsub.ValidationAccept, nil
44199
}
45200

46-
func (h *DecryptionKeysHandler) HandleMessage(_ context.Context, _ p2pmsg.Message) ([]p2pmsg.Message, error) {
201+
func (h *DecryptionKeysHandler) HandleMessage(ctx context.Context, msg p2pmsg.Message) ([]p2pmsg.Message, error) {
202+
keys := msg.(*p2pmsg.DecryptionKeys)
203+
extra := keys.Extra.(*p2pmsg.DecryptionKeys_Gnosis).Gnosis
204+
gnosisDB := database.New(h.dbpool)
205+
// the first key is the block key, only the rest are tx keys, so subtract 1
206+
newTxPointer := int64(extra.TxPointer) + int64(len(keys.Keys)) - 1
207+
log.Debug().
208+
Uint64("eon", keys.Eon).
209+
Uint64("block", extra.Slot).
210+
Uint64("tx-pointer-msg", extra.TxPointer).
211+
Int("num-keys", len(keys.Keys)).
212+
Int64("tx-pointer-updated", newTxPointer).
213+
Msg("updating tx pointer")
214+
err := gnosisDB.SetTxPointer(ctx, database.SetTxPointerParams{
215+
Eon: int64(keys.Eon),
216+
Block: int64(extra.Slot),
217+
Value: newTxPointer,
218+
})
219+
if err != nil {
220+
return []p2pmsg.Message{}, errors.Wrap(err, "failed to set tx pointer")
221+
}
47222
return []p2pmsg.Message{}, nil
48223
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package gnosis
2+
3+
import (
4+
"github.com/ethereum/go-ethereum/crypto"
5+
6+
"github.com/shutter-network/rolling-shutter/rolling-shutter/medley/identitypreimage"
7+
"github.com/shutter-network/rolling-shutter/rolling-shutter/p2pmsg"
8+
)
9+
10+
func computeIdentitiesHash(identityPreimages []identitypreimage.IdentityPreimage) []byte {
11+
identityPreimagesAsBytes := [][]byte{}
12+
for _, preimage := range identityPreimages {
13+
identityPreimagesAsBytes = append(identityPreimagesAsBytes, preimage)
14+
}
15+
return crypto.Keccak256(identityPreimagesAsBytes...)
16+
}
17+
18+
func computeIdentitiesHashFromShares(shares []*p2pmsg.KeyShare) []byte {
19+
identityPreimges := []identitypreimage.IdentityPreimage{}
20+
for _, share := range shares {
21+
identityPreimges = append(identityPreimges, identitypreimage.IdentityPreimage(share.EpochID))
22+
}
23+
return computeIdentitiesHash(identityPreimges)
24+
}

0 commit comments

Comments
 (0)