nym_mixnet_contract_common/
key_rotation.rs

1// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::EpochId;
5use cosmwasm_schema::cw_serde;
6
7pub type KeyRotationId = u32;
8
9#[cw_serde]
10#[derive(Copy)]
11#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
12pub struct KeyRotationState {
13    /// Defines how long each key rotation is valid for (in terms of epochs)
14    pub validity_epochs: u32,
15
16    /// Records the initial epoch_id when the key rotation has been introduced (0 for fresh contracts).
17    /// It is used for determining when rotation is meant to advance.
18    #[cfg_attr(feature = "utoipa", schema(value_type = u32))]
19    pub initial_epoch_id: EpochId,
20}
21
22impl KeyRotationState {
23    pub fn key_rotation_id(&self, current_epoch_id: EpochId) -> KeyRotationId {
24        let diff = current_epoch_id.saturating_sub(self.initial_epoch_id);
25        diff / self.validity_epochs
26    }
27
28    pub fn next_rotation_starting_epoch_id(&self, current_epoch_id: EpochId) -> EpochId {
29        self.current_rotation_starting_epoch_id(current_epoch_id) + self.validity_epochs
30    }
31
32    pub fn current_rotation_starting_epoch_id(&self, current_epoch_id: EpochId) -> EpochId {
33        let current_rotation_id = self.key_rotation_id(current_epoch_id);
34
35        self.initial_epoch_id + self.validity_epochs * current_rotation_id
36    }
37}
38
39#[cw_serde]
40pub struct KeyRotationIdResponse {
41    pub rotation_id: KeyRotationId,
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    #[test]
49    fn key_rotation_id() {
50        let state = KeyRotationState {
51            validity_epochs: 24,
52            initial_epoch_id: 0,
53        };
54        assert_eq!(0, state.key_rotation_id(0));
55        assert_eq!(0, state.key_rotation_id(23));
56        assert_eq!(1, state.key_rotation_id(24));
57        assert_eq!(1, state.key_rotation_id(47));
58        assert_eq!(2, state.key_rotation_id(48));
59
60        let state = KeyRotationState {
61            validity_epochs: 12,
62            initial_epoch_id: 0,
63        };
64        assert_eq!(0, state.key_rotation_id(0));
65        assert_eq!(0, state.key_rotation_id(11));
66        assert_eq!(1, state.key_rotation_id(12));
67        assert_eq!(1, state.key_rotation_id(23));
68        assert_eq!(2, state.key_rotation_id(24));
69
70        let state = KeyRotationState {
71            validity_epochs: 24,
72            initial_epoch_id: 10000,
73        };
74        assert_eq!(0, state.key_rotation_id(123));
75        assert_eq!(0, state.key_rotation_id(10000));
76        assert_eq!(0, state.key_rotation_id(10001));
77        assert_eq!(0, state.key_rotation_id(10023));
78        assert_eq!(1, state.key_rotation_id(10024));
79        assert_eq!(1, state.key_rotation_id(10047));
80        assert_eq!(2, state.key_rotation_id(10048));
81        assert_eq!(2, state.key_rotation_id(10060));
82    }
83
84    #[test]
85    fn next_rotation_starting_epoch_id() {
86        let state = KeyRotationState {
87            validity_epochs: 24,
88            initial_epoch_id: 0,
89        };
90        assert_eq!(24, state.next_rotation_starting_epoch_id(0));
91        assert_eq!(24, state.next_rotation_starting_epoch_id(23));
92        assert_eq!(48, state.next_rotation_starting_epoch_id(24));
93        assert_eq!(48, state.next_rotation_starting_epoch_id(47));
94        assert_eq!(72, state.next_rotation_starting_epoch_id(48));
95
96        let state = KeyRotationState {
97            validity_epochs: 12,
98            initial_epoch_id: 0,
99        };
100        assert_eq!(12, state.next_rotation_starting_epoch_id(0));
101        assert_eq!(12, state.next_rotation_starting_epoch_id(11));
102        assert_eq!(24, state.next_rotation_starting_epoch_id(12));
103        assert_eq!(24, state.next_rotation_starting_epoch_id(23));
104        assert_eq!(36, state.next_rotation_starting_epoch_id(24));
105
106        let state = KeyRotationState {
107            validity_epochs: 24,
108            initial_epoch_id: 10000,
109        };
110        assert_eq!(10024, state.next_rotation_starting_epoch_id(123));
111        assert_eq!(10024, state.next_rotation_starting_epoch_id(10000));
112        assert_eq!(10024, state.next_rotation_starting_epoch_id(10001));
113        assert_eq!(10024, state.next_rotation_starting_epoch_id(10023));
114        assert_eq!(10048, state.next_rotation_starting_epoch_id(10024));
115        assert_eq!(10048, state.next_rotation_starting_epoch_id(10047));
116        assert_eq!(10072, state.next_rotation_starting_epoch_id(10048));
117        assert_eq!(10072, state.next_rotation_starting_epoch_id(10060));
118    }
119
120    #[test]
121    fn current_rotation_starting_epoch_id() {
122        let state = KeyRotationState {
123            validity_epochs: 24,
124            initial_epoch_id: 0,
125        };
126        assert_eq!(0, state.current_rotation_starting_epoch_id(0));
127        assert_eq!(0, state.current_rotation_starting_epoch_id(23));
128        assert_eq!(24, state.current_rotation_starting_epoch_id(24));
129        assert_eq!(24, state.current_rotation_starting_epoch_id(47));
130        assert_eq!(48, state.current_rotation_starting_epoch_id(48));
131
132        let state = KeyRotationState {
133            validity_epochs: 12,
134            initial_epoch_id: 0,
135        };
136        assert_eq!(0, state.current_rotation_starting_epoch_id(0));
137        assert_eq!(0, state.current_rotation_starting_epoch_id(11));
138        assert_eq!(12, state.current_rotation_starting_epoch_id(12));
139        assert_eq!(12, state.current_rotation_starting_epoch_id(23));
140        assert_eq!(24, state.current_rotation_starting_epoch_id(24));
141
142        let state = KeyRotationState {
143            validity_epochs: 24,
144            initial_epoch_id: 10000,
145        };
146        assert_eq!(10000, state.current_rotation_starting_epoch_id(123));
147        assert_eq!(10000, state.current_rotation_starting_epoch_id(10000));
148        assert_eq!(10000, state.current_rotation_starting_epoch_id(10001));
149        assert_eq!(10000, state.current_rotation_starting_epoch_id(10023));
150        assert_eq!(10024, state.current_rotation_starting_epoch_id(10024));
151        assert_eq!(10024, state.current_rotation_starting_epoch_id(10047));
152        assert_eq!(10048, state.current_rotation_starting_epoch_id(10048));
153        assert_eq!(10048, state.current_rotation_starting_epoch_id(10060));
154    }
155}