@@ -80,14 +80,20 @@ module sui::authenticator_state {
80
80
}
81
81
}
82
82
83
- fun jwk_equal (a: &ActiveJwk , b: &ActiveJwk ): bool {
83
+ fun active_jwk_equal (a: &ActiveJwk , b: &ActiveJwk ): bool {
84
84
// note: epoch is ignored
85
- (&a.jwk.kty == &b.jwk.kty) &&
86
- (&a.jwk.e == &b.jwk.e) &&
87
- (&a.jwk.n == &b.jwk.n) &&
88
- (&a.jwk.alg == &b.jwk.alg) &&
89
- (&a.jwk_id.iss == &b.jwk_id.iss) &&
90
- (&a.jwk_id.kid == &b.jwk_id.kid)
85
+ jwk_equal (&a.jwk, &b.jwk) && jwk_id_equal (&a.jwk_id, &b.jwk_id)
86
+ }
87
+
88
+ fun jwk_equal (a: &JWK , b: &JWK ): bool {
89
+ (&a.kty == &b.kty) &&
90
+ (&a.e == &b.e) &&
91
+ (&a.n == &b.n) &&
92
+ (&a.alg == &b.alg)
93
+ }
94
+
95
+ fun jwk_id_equal (a: &JwkId , b: &JwkId ): bool {
96
+ (&a.iss == &b.iss) && (&a.kid == &b.kid)
91
97
}
92
98
93
99
// Compare the underlying byte arrays lexicographically. Since the strings may be utf8 this
@@ -213,6 +219,7 @@ module sui::authenticator_state {
213
219
assert !(tx_context::sender (ctx) == @0x0 , ENotSystemAddress );
214
220
215
221
check_sorted (&new_active_jwks);
222
+ let new_active_jwks = deduplicate (new_active_jwks);
216
223
217
224
let inner = load_inner_mut (self);
218
225
@@ -227,12 +234,19 @@ module sui::authenticator_state {
227
234
let new_jwk = vector ::borrow (&new_active_jwks, j);
228
235
229
236
// when they are equal, push only one, but use the max epoch of the two
230
- if (jwk_equal (old_jwk, new_jwk)) {
237
+ if (active_jwk_equal (old_jwk, new_jwk)) {
231
238
let jwk = *old_jwk;
232
239
jwk.epoch = math::max (old_jwk.epoch, new_jwk.epoch);
233
240
vector ::push_back (&mut res, jwk);
234
241
i = i + 1 ;
235
242
j = j + 1 ;
243
+ } else if (jwk_id_equal (&old_jwk.jwk_id, &new_jwk.jwk_id)) {
244
+ // if only jwk_id is equal, then the key has changed. Providers should not send
245
+ // JWKs like this, but if they do, we must ignore the new JWK to avoid having a
246
+ // liveness / forking issues
247
+ vector ::push_back (&mut res, *old_jwk);
248
+ i = i + 1 ;
249
+ j = j + 1 ;
236
250
} else if (jwk_lt (old_jwk, new_jwk)) {
237
251
vector ::push_back (&mut res, *old_jwk);
238
252
i = i + 1 ;
@@ -254,6 +268,27 @@ module sui::authenticator_state {
254
268
inner.active_jwks = res;
255
269
}
256
270
271
+ fun deduplicate (jwks: vector <ActiveJwk >): vector <ActiveJwk > {
272
+ let res = vector [];
273
+ let i = 0 ;
274
+ let prev: Option <JwkId > = option::none ();
275
+ while (i < vector ::length (&jwks)) {
276
+ let jwk = vector ::borrow (&jwks, i);
277
+ if (option::is_none (&prev)) {
278
+ option::fill (&mut prev, jwk.jwk_id);
279
+ } else if (jwk_id_equal (option::borrow (&prev), &jwk.jwk_id)) {
280
+ // skip duplicate jwks in input
281
+ i = i + 1 ;
282
+ continue ;
283
+ } else {
284
+ *option::borrow_mut (&mut prev) = jwk.jwk_id;
285
+ };
286
+ vector ::push_back (&mut res, *jwk);
287
+ i = i + 1 ;
288
+ };
289
+ res
290
+ }
291
+
257
292
#[allow(unused_function)]
258
293
// Called directly by rust when constructing the ChangeEpoch transaction.
259
294
fun expire_jwks (
0 commit comments