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
//! key rotation.
use crate::errors::*;
use crate::identity::*;
use crate::types::*;
use crate::verify::ok;
use zeroize::Zeroize;
// Key Rotation
// ============================================================================
/// Identity with key rotation support.
///
/// Maintains a current keypair and up to `max_generations` previous keys
/// (for graceful transition). Callers should verify signatures against
/// all retained keys during the rotation window.
#[cfg(feature = "std")]
pub struct RotatingIdentity {
current: Identity,
previous: Vec<(Identity, u64)>,
max_generations: usize,
}
#[cfg(feature = "std")]
impl RotatingIdentity {
/// Default maximum number of previous key generations to retain.
pub const DEFAULT_MAX_GENERATIONS: usize = 2;
/// Create a new rotating identity from a fresh keypair.
/// # Errors
/// Returns `AuthError::E5` if the platform RNG fails.
pub fn gen() -> Result<Self> {
Ok(Self {
current: Identity::gen()?,
previous: Vec::new(),
max_generations: Self::DEFAULT_MAX_GENERATIONS,
})
}
/// Create from an existing identity.
#[must_use]
pub const fn from_identity(id: Identity) -> Self {
Self {
current: id,
previous: Vec::new(),
max_generations: Self::DEFAULT_MAX_GENERATIONS,
}
}
/// Create with a custom maximum number of previous generations.
#[must_use]
pub fn with_max_generations(id: Identity, max: usize) -> Self {
Self {
current: id,
previous: Vec::new(),
max_generations: max.max(1),
}
}
/// Rotate to a new keypair. The old key is archived with a timestamp.
/// Oldest generations beyond `max_generations` are evicted (and zeroized).
/// Returns the new public `AliceId`.
/// # Errors
/// Returns `AuthError::E5` if the platform RNG fails.
pub fn rotate(&mut self, now_ms: u64) -> Result<AliceId> {
let mut old_seed = self.current.seed();
let old = Identity::from_seed(&old_seed);
old_seed.zeroize();
let new = Identity::gen()?;
self.previous.push((old, now_ms));
// Evict oldest generations beyond limit (dropped Identity runs Drop → zeroize)
while self.previous.len() > self.max_generations {
self.previous.remove(0);
}
self.current = new;
Ok(self.current.id())
}
/// Get the current public identity.
#[inline]
#[must_use]
pub fn id(&self) -> AliceId {
self.current.id()
}
/// Get the most recent previous public identity (if rotation has occurred).
#[must_use]
pub fn previous_id(&self) -> Option<(AliceId, u64)> {
self.previous.last().map(|(id, ts)| (id.id(), *ts))
}
/// Get all previous public identities (oldest first).
#[must_use]
pub fn previous_ids(&self) -> Vec<(AliceId, u64)> {
self.previous
.iter()
.map(|(id, ts)| (id.id(), *ts))
.collect()
}
/// Sign with the current key.
#[inline]
#[must_use]
pub fn sign(&self, m: &[u8]) -> AliceSig {
self.current.sign(m)
}
/// Verify a signature against the current key, falling back to all
/// previous keys. Returns true if any key matches.
#[must_use]
pub fn verify_any(&self, id: &AliceId, m: &[u8], s: &AliceSig) -> bool {
if ok(id, m, s) {
return true;
}
for (prev, _) in &self.previous {
if ok(&prev.id(), m, s) {
return true;
}
}
false
}
/// Clear all previous keys (after rotation window expires).
pub fn clear_previous(&mut self) {
self.previous.clear();
}
/// Check if rotation has occurred and previous keys exist.
#[must_use]
pub const fn has_previous(&self) -> bool {
!self.previous.is_empty()
}
/// Number of retained previous generations.
#[must_use]
pub const fn generation_count(&self) -> usize {
self.previous.len()
}
}