-
Notifications
You must be signed in to change notification settings - Fork 105
/
keygen.go
243 lines (193 loc) · 6.76 KB
/
keygen.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
package types
import (
fmt "fmt"
time "time"
sdk "github.com/cosmos/cosmos-sdk/types"
"golang.org/x/exp/maps"
"github.com/axelarnetwork/axelar-core/utils"
"github.com/axelarnetwork/axelar-core/x/multisig/exported"
snapshot "github.com/axelarnetwork/axelar-core/x/snapshot/exported"
"github.com/axelarnetwork/utils/funcs"
"github.com/axelarnetwork/utils/slices"
)
// NewKeygenSession is the contructor for keygen session
func NewKeygenSession(id exported.KeyID, keygenThreshold utils.Threshold, signingThreshold utils.Threshold, snapshot snapshot.Snapshot, expiresAt int64, gracePeriod int64) KeygenSession {
return KeygenSession{
Key: Key{
ID: id,
Snapshot: snapshot,
SigningThreshold: signingThreshold,
},
State: exported.Pending,
KeygenThreshold: keygenThreshold,
ExpiresAt: expiresAt,
GracePeriod: gracePeriod,
}
}
// ValidateBasic returns an error if the given keygen session is invalid; nil otherwise
func (m KeygenSession) ValidateBasic() error {
if m.KeygenThreshold.LT(m.Key.SigningThreshold) {
return fmt.Errorf("keygen threshold must be >=signing threshold")
}
if m.ExpiresAt <= 0 {
return fmt.Errorf("expires at must be >0")
}
if m.CompletedAt >= m.ExpiresAt {
return fmt.Errorf("completed at must be < expires at")
}
if m.GracePeriod < 0 {
return fmt.Errorf("grace period must be >=0")
}
switch m.GetState() {
case exported.Pending:
if m.CompletedAt != 0 {
return fmt.Errorf("pending keygen session must not have completed at set")
}
if err := validateBasicPendingKey(m.Key); err != nil {
return err
}
case exported.Completed:
if m.CompletedAt <= 0 {
return fmt.Errorf("completed keygen session must have completed at set")
}
if err := m.Key.ValidateBasic(); err != nil {
return err
}
default:
return fmt.Errorf("unexpected state %s", m.GetState())
}
return nil
}
// GetKeyID returns the key ID of the given keygen session
func (m KeygenSession) GetKeyID() exported.KeyID {
return m.Key.ID
}
// AddKey adds a new public key for the given participant into the keygen session
func (m *KeygenSession) AddKey(blockHeight int64, participant sdk.ValAddress, pubKey exported.PublicKey) error {
if m.Key.PubKeys == nil {
m.Key.PubKeys = make(map[string]exported.PublicKey)
m.IsPubKeyReceived = make(map[string]bool)
}
if m.isExpired(blockHeight) {
return fmt.Errorf("keygen session %s has expired", m.GetKeyID())
}
if m.Key.Snapshot.GetParticipantWeight(participant).IsZero() {
return fmt.Errorf("%s is not a participant of keygen %s", participant.String(), m.GetKeyID())
}
if _, ok := m.Key.PubKeys[participant.String()]; ok {
return fmt.Errorf("participant %s already submitted its public key for keygen %s", participant.String(), m.GetKeyID())
}
if m.IsPubKeyReceived[pubKey.String()] {
return fmt.Errorf("duplicate public key received")
}
if m.State == exported.Completed && !m.isWithinGracePeriod(blockHeight) {
return fmt.Errorf("keygen session %s has closed", m.GetKeyID())
}
m.addKey(participant, pubKey)
if m.State != exported.Completed && m.Key.GetParticipantsWeight().GTE(m.Key.Snapshot.CalculateMinPassingWeight(m.KeygenThreshold)) {
m.CompletedAt = blockHeight
m.State = exported.Completed
}
return nil
}
// GetMissingParticipants returns all participants who failed to submit their public keys
func (m KeygenSession) GetMissingParticipants() []sdk.ValAddress {
participants := m.Key.Snapshot.GetParticipantAddresses()
return slices.Filter(participants, func(p sdk.ValAddress) bool {
_, ok := m.Key.PubKeys[p.String()]
return !ok
})
}
// Result returns the generated key if the session is completed and the key is valid
func (m KeygenSession) Result() (Key, error) {
if m.GetState() != exported.Completed {
return Key{}, fmt.Errorf("keygen %s is not completed yet", m.GetKeyID())
}
funcs.MustNoErr(m.Key.ValidateBasic())
return m.Key, nil
}
func (m KeygenSession) isWithinGracePeriod(blockHeight int64) bool {
return blockHeight <= m.CompletedAt+m.GracePeriod
}
func (m KeygenSession) isExpired(blockHeight int64) bool {
return blockHeight >= m.ExpiresAt
}
func (m *KeygenSession) addKey(participant sdk.ValAddress, pubKey exported.PublicKey) {
m.Key.PubKeys[participant.String()] = pubKey
m.IsPubKeyReceived[pubKey.String()] = true
}
// GetParticipants returns the participants of the given key
func (m Key) GetParticipants() []sdk.ValAddress {
return sortAddresses(
slices.Map(maps.Keys(m.PubKeys), func(a string) sdk.ValAddress { return funcs.Must(sdk.ValAddressFromBech32(a)) }),
)
}
// GetParticipantsWeight returns the total weight of all participants who have submitted their public keys
func (m Key) GetParticipantsWeight() sdk.Uint {
return slices.Reduce(m.GetParticipants(), sdk.ZeroUint(), func(total sdk.Uint, p sdk.ValAddress) sdk.Uint {
return total.Add(m.Snapshot.GetParticipantWeight(p))
})
}
// GetMinPassingWeight returns the minimum amount of weights required for the
// key to sign
func (m Key) GetMinPassingWeight() sdk.Uint {
return m.Snapshot.CalculateMinPassingWeight(m.SigningThreshold)
}
// GetPubKey returns the public key of the given participant
func (m Key) GetPubKey(p sdk.ValAddress) (exported.PublicKey, bool) {
pubKey, ok := m.PubKeys[p.String()]
return pubKey, ok
}
// GetWeight returns the weight of the given participant
func (m Key) GetWeight(p sdk.ValAddress) sdk.Uint {
return m.Snapshot.GetParticipantWeight(p)
}
// GetHeight returns the height of the key snapshot
func (m Key) GetHeight() int64 {
return m.Snapshot.Height
}
// GetTimestamp returns the timestamp of the key snapshot
func (m Key) GetTimestamp() time.Time {
return m.Snapshot.Timestamp
}
// GetBondedWeight returns the bonded weight of the key snapshot
func (m Key) GetBondedWeight() sdk.Uint {
return m.Snapshot.BondedWeight
}
// ValidateBasic returns an error if the given key is invalid; nil otherwise
func (m Key) ValidateBasic() error {
if err := validateBasicPendingKey(m); err != nil {
return err
}
if m.GetParticipantsWeight().LT(m.GetMinPassingWeight()) {
return fmt.Errorf("invalid signing threshold")
}
return nil
}
func validateBasicPendingKey(key Key) error {
if err := key.ID.ValidateBasic(); err != nil {
return err
}
if err := key.Snapshot.ValidateBasic(); err != nil {
return err
}
pubKeySeen := make(map[string]bool, len(key.PubKeys))
for address, pubkey := range key.PubKeys {
pubkeyStr := pubkey.String()
if pubKeySeen[pubkeyStr] {
return fmt.Errorf("duplicate public key seen")
}
pubKeySeen[pubkeyStr] = true
p, err := sdk.ValAddressFromBech32(address)
if err != nil {
return err
}
if err := pubkey.ValidateBasic(); err != nil {
return err
}
if key.Snapshot.GetParticipantWeight(p).IsZero() {
return fmt.Errorf("invalid participant with public key submitted")
}
}
return nil
}