1use codec::{ConstEncodedLen, Decode, Encode, MaxEncodedLen};
2use jam_types::{BoundedVec, OpaqueValidatorMetadata};
3use rand_chacha_for_p256::{ChaCha12Rng, rand_core::SeedableRng};
4
5use crate::{
6 InjectiveDecode, P256PeerId, PeerAddr, PeerDetails, PeerId, bandersnatch, ed25519,
7 hash_raw_concat,
8};
9
10#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen)]
17pub struct ValidatorMetadata {
18 pub addr: PeerAddr,
19 pub p256_id: InjectiveDecode<P256PeerId, 33>,
22 pub rest1: [u8; 13],
23 pub rest2: [u32; 16],
24}
25impl ConstEncodedLen for ValidatorMetadata {}
26
27impl Default for ValidatorMetadata {
28 fn default() -> Self {
29 Self {
30 addr: PeerAddr { ip: Default::default(), port: 0 },
31 p256_id: InjectiveDecode::Valid(P256PeerId::ZERO),
32 rest1: Default::default(),
33 rest2: Default::default(),
34 }
35 }
36}
37
38impl std::fmt::Debug for ValidatorMetadata {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 let mut debug_struct = f.debug_struct("ValidatorMetadata");
41 debug_struct.field("addr", &self.addr);
42 if let InjectiveDecode::Valid(p256_id) = &self.p256_id {
43 debug_struct.field("p256_id", p256_id);
44 }
45 debug_struct.finish()
46 }
47}
48
49impl From<OpaqueValidatorMetadata> for ValidatorMetadata {
50 fn from(opaque: OpaqueValidatorMetadata) -> Self {
51 Self::decode(&mut &opaque[..])
52 .expect("ValidatorMetadata is carefully specified such that decode is infallible")
53 }
54}
55
56impl From<ValidatorMetadata> for OpaqueValidatorMetadata {
57 fn from(metadata: ValidatorMetadata) -> Self {
58 let mut opaque = OpaqueValidatorMetadata::default();
59 metadata.encode_to(&mut opaque.as_mut_slice());
60 opaque
61 }
62}
63
64#[cfg(any(test, feature = "rand"))]
65impl rand::distr::Distribution<ValidatorMetadata> for rand::distr::StandardUniform {
66 fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> ValidatorMetadata {
67 ValidatorMetadata {
68 addr: rng.random(),
69 p256_id: InjectiveDecode::Valid(rng.random()),
70 rest1: rng.random(),
71 rest2: rng.random(),
72 }
73 }
74}
75
76pub mod bls {
78 use codec::{Decode, Encode, MaxEncodedLen};
79 use jam_types::OpaqueBlsPublic;
80
81 pub const BLS_PUBLIC_SERIALIZED_LEN: usize = 144;
82
83 #[derive(Copy, Clone, Encode, Decode, Debug, Eq, PartialEq, MaxEncodedLen)]
84 pub struct Public(pub [u8; BLS_PUBLIC_SERIALIZED_LEN]);
85
86 impl Default for Public {
87 fn default() -> Self {
88 Public([0u8; BLS_PUBLIC_SERIALIZED_LEN])
89 }
90 }
91
92 impl From<OpaqueBlsPublic> for Public {
93 fn from(opaque: OpaqueBlsPublic) -> Self {
94 Self(opaque.0)
95 }
96 }
97
98 impl From<Public> for OpaqueBlsPublic {
99 fn from(public: Public) -> Self {
100 Self(public.0)
101 }
102 }
103}
104
105#[derive(Copy, Clone, Encode, Decode, Debug, Eq, PartialEq, MaxEncodedLen, Default)]
106pub struct ValKeyset {
107 pub bandersnatch: bandersnatch::Public,
108 pub ed25519: ed25519::Public,
109 pub bls: bls::Public,
110 pub metadata: ValidatorMetadata,
111}
112
113impl From<&ValKeyset> for PeerDetails {
114 fn from(keyset: &ValKeyset) -> PeerDetails {
115 PeerDetails { id: PeerId(keyset.ed25519), addr: keyset.metadata.addr }
116 }
117}
118
119pub type ValKeysets = BoundedVec<ValKeyset, jam_types::MaxValCount>;
120pub type EdKeys = BoundedVec<ed25519::Public, jam_types::MaxValCount>;
121
122fn p256_secret_from_seed(seed: [u8; 32]) -> p256::ecdsa::SigningKey {
123 let mut rng = ChaCha12Rng::from_seed(seed);
124 p256::ecdsa::SigningKey::random(&mut rng)
125}
126
127pub struct SecretKeyset {
128 pub bandersnatch: bandersnatch::Secret,
129 pub ed25519: ed25519::Secret,
130 pub p256: p256::ecdsa::SigningKey,
131}
132
133impl SecretKeyset {
134 pub fn from_seed(seed: &[u8; 32]) -> Self {
135 Self {
136 bandersnatch: bandersnatch::Secret::from_seed(hash_raw_concat([
137 b"jam_val_key_bandersnatch".as_slice(),
138 seed.as_slice(),
139 ])),
140 ed25519: ed25519::Secret::from_seed(hash_raw_concat([
141 b"jam_val_key_ed25519".as_slice(),
142 seed.as_slice(),
143 ])),
144 p256: p256_secret_from_seed(hash_raw_concat([
145 b"jam_val_key_p256".as_slice(),
146 seed.as_slice(),
147 ])),
148 }
149 }
150
151 pub fn trivial_seed(id: u32) -> [u8; 32] {
152 let mut seed = [0; 32];
153 for chunk in seed.chunks_mut(4) {
154 chunk.copy_from_slice(&id.to_le_bytes());
155 }
156 seed
157 }
158
159 pub fn trivial(id: u32) -> Self {
160 Self::from_seed(&Self::trivial_seed(id))
161 }
162
163 pub fn public(&self, mut metadata: ValidatorMetadata) -> ValKeyset {
164 metadata.p256_id = InjectiveDecode::Valid(self.p256.verifying_key().into());
165 ValKeyset {
166 ed25519: self.ed25519.public(),
167 bandersnatch: self.bandersnatch.public(),
168 bls: bls::Public::default(),
169 metadata,
170 }
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177 use jam_types::hex::from_hex;
178 use rand::{Rng, RngCore};
179
180 #[test]
181 fn metadata_round_trip() {
182 assert_eq!(ValidatorMetadata::max_encoded_len(), OpaqueValidatorMetadata::LEN);
183
184 let mut rng = rand::rng();
185
186 for _ in 0..20 {
187 let mut opaque = OpaqueValidatorMetadata([0; 128]);
188 rng.fill_bytes(&mut opaque.0);
189 let metadata = ValidatorMetadata::from(opaque);
190 assert_eq!(OpaqueValidatorMetadata::from(metadata), opaque);
191 }
192
193 for _ in 0..20 {
194 let metadata: ValidatorMetadata = rand::rng().random();
195 let opaque = OpaqueValidatorMetadata::from(metadata);
196 assert_eq!(ValidatorMetadata::from(opaque), metadata);
197 }
198 }
199
200 #[test]
202 fn validator_keyset_order_assumptions() {
203 let keyset = SecretKeyset::trivial(0).public(Default::default());
204 let buf = keyset.encode();
205 let opaque = jam_types::OpaqueValKeyset::decode(&mut &buf[..]).unwrap();
206 assert_eq!(keyset.bandersnatch.encode(), opaque.bandersnatch[..]);
207 assert_eq!(keyset.ed25519.encode(), opaque.ed25519[..]);
208 assert_eq!(keyset.bls.encode(), opaque.bls[..]);
209 assert_eq!(keyset.metadata.encode(), opaque.metadata[..]);
210 let buf = opaque.encode();
211 let keyset2 = ValKeyset::decode(&mut &buf[..]).unwrap();
212 assert_eq!(keyset, keyset2);
213 }
214
215 #[test]
216 fn from_seed() {
217 let keyset = SecretKeyset::trivial(0).public(Default::default());
220 assert_eq!(
221 keyset.ed25519.as_bytes(),
222 from_hex("4418fb8c85bb3985394a8c2756d3643457ce614546202a2f50b093d762499ace")
223 .unwrap()
224 .as_slice()
225 );
226 assert_eq!(
227 &keyset.bandersnatch.0,
228 from_hex("ff71c6c03ff88adb5ed52c9681de1629a54e702fc14729f6b50d2f0a76f185b3")
229 .unwrap()
230 .as_slice()
231 );
232 assert_eq!(
233 keyset.metadata.p256_id.valid().unwrap().to_text().as_str(),
234 "oqov2a57d7etnpzb6aerv64y5j622ejkkvqjencdrwln4qhnoqvqb"
235 );
236
237 let keyset = SecretKeyset::trivial(1).public(Default::default());
238 assert_eq!(
239 keyset.ed25519.as_bytes(),
240 from_hex("ad93247bd01307550ec7acd757ce6fb805fcf73db364063265b30a949e90d933")
241 .unwrap()
242 .as_slice()
243 );
244 assert_eq!(
245 &keyset.bandersnatch.0,
246 from_hex("dee6d555b82024f1ccf8a1e37e60fa60fd40b1958c4bb3006af78647950e1b91")
247 .unwrap()
248 .as_slice()
249 );
250 assert_eq!(
251 keyset.metadata.p256_id.valid().unwrap().to_text().as_str(),
252 "ordkiwj4rcxzhxrh3xbfj6dyt3utrhrxukgy6xfi3fpubv4ioerzb"
253 );
254
255 let keyset = SecretKeyset::trivial(2).public(Default::default());
256 assert_eq!(
257 keyset.ed25519.as_bytes(),
258 from_hex("cab2b9ff25c2410fbe9b8a717abb298c716a03983c98ceb4def2087500b8e341")
259 .unwrap()
260 .as_slice()
261 );
262 assert_eq!(
263 &keyset.bandersnatch.0,
264 from_hex("9326edb21e5541717fde24ec085000b28709847b8aab1ac51f84e94b37ca1b66")
265 .unwrap()
266 .as_slice()
267 );
268 assert_eq!(
269 keyset.metadata.p256_id.valid().unwrap().to_text().as_str(),
270 "vnj7urb4lb75ronubjofyj4tuy4upjypgcnuktthjfm6ntoxb7qga"
271 );
272
273 let keyset = SecretKeyset::trivial(3).public(Default::default());
274 assert_eq!(
275 keyset.ed25519.as_bytes(),
276 from_hex("f30aa5444688b3cab47697b37d5cac5707bb3289e986b19b17db437206931a8d")
277 .unwrap()
278 .as_slice()
279 );
280 assert_eq!(
281 &keyset.bandersnatch.0,
282 from_hex("0746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981cc")
283 .unwrap()
284 .as_slice()
285 );
286 assert_eq!(
287 keyset.metadata.p256_id.valid().unwrap().to_text().as_str(),
288 "o3cucx6gy2ughphhb6iiy33cchj6hbwoc7kvp63r52yi4w22dbcnb"
289 );
290
291 let keyset = SecretKeyset::trivial(4).public(Default::default());
292 assert_eq!(
293 keyset.ed25519.as_bytes(),
294 from_hex("8b8c5d436f92ecf605421e873a99ec528761eb52a88a2f9a057b3b3003e6f32a")
295 .unwrap()
296 .as_slice()
297 );
298 assert_eq!(
299 &keyset.bandersnatch.0,
300 from_hex("151e5c8fe2b9d8a606966a79edd2f9e5db47e83947ce368ccba53bf6ba20a40b")
301 .unwrap()
302 .as_slice()
303 );
304 assert_eq!(
305 keyset.metadata.p256_id.valid().unwrap().to_text().as_str(),
306 "ozk7khb6idjpt3wuaikstki2ehgsi4ikne2fpsamdzimohihp4uyb"
307 );
308
309 let keyset = SecretKeyset::trivial(5).public(Default::default());
310 assert_eq!(
311 keyset.ed25519.as_bytes(),
312 from_hex("ab0084d01534b31c1dd87c81645fd762482a90027754041ca1b56133d0466c06")
313 .unwrap()
314 .as_slice()
315 );
316 assert_eq!(
317 &keyset.bandersnatch.0,
318 from_hex("2105650944fcd101621fd5bb3124c9fd191d114b7ad936c1d79d734f9f21392e")
319 .unwrap()
320 .as_slice()
321 );
322 assert_eq!(
323 keyset.metadata.p256_id.valid().unwrap().to_text().as_str(),
324 "vgyzaaduuhipky3cuvwee4wr3eptbmsrg7zn7dlmrqxjmjlwwrgrb"
325 );
326
327 let keyset = SecretKeyset::from_seed(
328 from_hex("f92d680ea3f0ac06307795490d8a03c5c0d4572b5e0a8cffec87e1294855d9d1")
329 .unwrap()
330 .as_slice()
331 .try_into()
332 .unwrap(),
333 )
334 .public(Default::default());
335 assert_eq!(
336 keyset.ed25519.as_bytes(),
337 from_hex("11a695f674de95ff3daaff9a5b88c18448b10156bf88bc04200e48d5155c7243")
338 .unwrap()
339 .as_slice()
340 );
341 assert_eq!(
342 &keyset.bandersnatch.0,
343 from_hex("299bdfd8d615aadd9e6c58718f9893a5144d60e897bc9da1f3d73c935715c650")
344 .unwrap()
345 .as_slice()
346 );
347 assert_eq!(
348 keyset.metadata.p256_id.valid().unwrap().to_text().as_str(),
349 "vhoj24pkzgs4sszvzvpw6f76eeftvfmz2haepu7m7wlazqc6viwpa"
350 );
351 }
352}