Skip to main content

jam_std_common/
keyset.rs

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/// Decoded `OpaqueValidatorMetadata`.
11///
12/// Converting from `OpaqueValidatorMetadata` to `ValidatorMetadata` is infallible. Converting back
13/// always yields an `OpaqueValidatorMetadata` which is identical to the original.
14// Care must be taken here to ensure these guarantees hold. In particular, types with a
15// variable-length encoding or with fallible decode should be wrapped in InjectiveDecode.
16#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen)]
17pub struct ValidatorMetadata {
18	pub addr: PeerAddr,
19	/// ECDSA P-256 identity, for use in particular with WebTransport. This may be set to
20	/// `P256PeerId::ZERO` if the validator does not have a P-256 key.
21	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
76/// BLS Placeholder
77pub 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	// Items order of ValKeyset and OpaqueValKeyset must match
201	#[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		// Test vectors from the JIP-5 spec
218
219		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}