referrerpolicy=no-referrer-when-downgrade

sc_consensus_beefy/
keystore.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18use codec::Decode;
19use log::warn;
20
21use sp_application_crypto::{key_types::BEEFY as BEEFY_KEY_TYPE, AppCrypto, RuntimeAppPublic};
22#[cfg(feature = "bls-experimental")]
23use sp_core::ecdsa_bls381;
24use sp_core::{ecdsa, keccak_256};
25
26use sp_keystore::KeystorePtr;
27use std::marker::PhantomData;
28
29use sp_consensus_beefy::{AuthorityIdBound, BeefyAuthorityId, BeefySignatureHasher};
30
31use crate::{error, LOG_TARGET};
32
33/// A BEEFY specific keystore implemented as a `Newtype`. This is basically a
34/// wrapper around [`sp_keystore::Keystore`] and allows to customize
35/// common cryptographic functionality.
36pub(crate) struct BeefyKeystore<AuthorityId: AuthorityIdBound>(
37	Option<KeystorePtr>,
38	PhantomData<fn() -> AuthorityId>,
39);
40
41impl<AuthorityId: AuthorityIdBound> BeefyKeystore<AuthorityId> {
42	/// Check if the keystore contains a private key for one of the public keys
43	/// contained in `keys`. A public key with a matching private key is known
44	/// as a local authority id.
45	///
46	/// Return the public key for which we also do have a private key. If no
47	/// matching private key is found, `None` will be returned.
48	pub fn authority_id(&self, keys: &[AuthorityId]) -> Option<AuthorityId> {
49		let store = self.0.clone()?;
50
51		// we do check for multiple private keys as a key store sanity check.
52		let public: Vec<AuthorityId> = keys
53			.iter()
54			.filter(|k| {
55				store
56					.has_keys(&[(<AuthorityId as RuntimeAppPublic>::to_raw_vec(k), BEEFY_KEY_TYPE)])
57			})
58			.cloned()
59			.collect();
60
61		if public.len() > 1 {
62			warn!(
63				target: LOG_TARGET,
64				"🥩 Multiple private keys found for: {:?} ({})",
65				public,
66				public.len()
67			);
68		}
69
70		public.get(0).cloned()
71	}
72
73	/// Sign `message` with the `public` key.
74	///
75	/// Note that `message` usually will be pre-hashed before being signed.
76	///
77	/// Return the message signature or an error in case of failure.
78	pub fn sign(
79		&self,
80		public: &AuthorityId,
81		message: &[u8],
82	) -> Result<<AuthorityId as RuntimeAppPublic>::Signature, error::Error> {
83		let store = self.0.clone().ok_or_else(|| error::Error::Keystore("no Keystore".into()))?;
84
85		// ECDSA should use ecdsa_sign_prehashed since it needs to be hashed by keccak_256 instead
86		// of blake2. As such we need to deal with producing the signatures case-by-case
87		let signature_byte_array: Vec<u8> = match <AuthorityId as AppCrypto>::CRYPTO_ID {
88			ecdsa::CRYPTO_ID => {
89				let msg_hash = keccak_256(message);
90				let public: ecdsa::Public = ecdsa::Public::try_from(public.as_slice()).unwrap();
91
92				let sig = store
93					.ecdsa_sign_prehashed(BEEFY_KEY_TYPE, &public, &msg_hash)
94					.map_err(|e| error::Error::Keystore(e.to_string()))?
95					.ok_or_else(|| {
96						error::Error::Signature("ecdsa_sign_prehashed() failed".to_string())
97					})?;
98				let sig_ref: &[u8] = sig.as_ref();
99				sig_ref.to_vec()
100			},
101
102			#[cfg(feature = "bls-experimental")]
103			ecdsa_bls381::CRYPTO_ID => {
104				let public: ecdsa_bls381::Public =
105					ecdsa_bls381::Public::try_from(public.as_slice()).unwrap();
106				let sig = store
107					.ecdsa_bls381_sign_with_keccak256(BEEFY_KEY_TYPE, &public, &message)
108					.map_err(|e| error::Error::Keystore(e.to_string()))?
109					.ok_or_else(|| error::Error::Signature("bls381_sign()  failed".to_string()))?;
110				let sig_ref: &[u8] = sig.as_ref();
111				sig_ref.to_vec()
112			},
113
114			_ => Err(error::Error::Keystore("key type is not supported by BEEFY Keystore".into()))?,
115		};
116
117		//check that `sig` has the expected result type
118		let signature = <AuthorityId as RuntimeAppPublic>::Signature::decode(
119			&mut signature_byte_array.as_slice(),
120		)
121		.map_err(|_| {
122			error::Error::Signature(format!(
123				"invalid signature {:?} for key {:?}",
124				signature_byte_array, public
125			))
126		})?;
127
128		Ok(signature)
129	}
130
131	/// Returns a vector of [`sp_consensus_beefy::crypto::Public`] keys which are currently
132	/// supported (i.e. found in the keystore).
133	pub fn public_keys(&self) -> Result<Vec<AuthorityId>, error::Error> {
134		let store = self.0.clone().ok_or_else(|| error::Error::Keystore("no Keystore".into()))?;
135
136		let pk = match <AuthorityId as AppCrypto>::CRYPTO_ID {
137			ecdsa::CRYPTO_ID => store
138				.ecdsa_public_keys(BEEFY_KEY_TYPE)
139				.drain(..)
140				.map(|pk| AuthorityId::try_from(pk.as_ref()))
141				.collect::<Result<Vec<_>, _>>()
142				.or_else(|_| {
143					Err(error::Error::Keystore(
144						"unable to convert public key into authority id".into(),
145					))
146				}),
147
148			#[cfg(feature = "bls-experimental")]
149			ecdsa_bls381::CRYPTO_ID => store
150				.ecdsa_bls381_public_keys(BEEFY_KEY_TYPE)
151				.drain(..)
152				.map(|pk| AuthorityId::try_from(pk.as_ref()))
153				.collect::<Result<Vec<_>, _>>()
154				.or_else(|_| {
155					Err(error::Error::Keystore(
156						"unable to convert public key into authority id".into(),
157					))
158				}),
159
160			_ => Err(error::Error::Keystore("key type is not supported by BEEFY Keystore".into())),
161		};
162
163		pk
164	}
165
166	/// Use the `public` key to verify that `sig` is a valid signature for `message`.
167	///
168	/// Return `true` if the signature is authentic, `false` otherwise.
169	pub fn verify(
170		public: &AuthorityId,
171		sig: &<AuthorityId as RuntimeAppPublic>::Signature,
172		message: &[u8],
173	) -> bool {
174		BeefyAuthorityId::<BeefySignatureHasher>::verify(public, sig, message)
175	}
176}
177
178impl<AuthorityId: AuthorityIdBound> From<Option<KeystorePtr>> for BeefyKeystore<AuthorityId> {
179	fn from(store: Option<KeystorePtr>) -> BeefyKeystore<AuthorityId> {
180		BeefyKeystore(store, PhantomData)
181	}
182}
183
184#[cfg(test)]
185pub mod tests {
186	#[cfg(feature = "bls-experimental")]
187	use sp_consensus_beefy::ecdsa_bls_crypto;
188	use sp_consensus_beefy::{
189		ecdsa_crypto,
190		test_utils::{BeefySignerAuthority, Keyring},
191	};
192	use sp_core::Pair as PairT;
193	use sp_keystore::{testing::MemoryKeystore, Keystore};
194
195	use super::*;
196	use crate::error::Error;
197
198	fn keystore() -> KeystorePtr {
199		MemoryKeystore::new().into()
200	}
201
202	fn pair_verify_should_work<
203		AuthorityId: AuthorityIdBound + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Public>,
204	>()
205	where
206		<AuthorityId as sp_runtime::RuntimeAppPublic>::Signature:
207			Send + Sync + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Signature>,
208		<AuthorityId as AppCrypto>::Pair: BeefySignerAuthority<sp_runtime::traits::Keccak256>,
209	{
210		let msg = b"I am Alice!";
211		let sig = Keyring::<AuthorityId>::Alice.sign(b"I am Alice!");
212
213		assert!(<AuthorityId as BeefyAuthorityId<BeefySignatureHasher>>::verify(
214			&Keyring::Alice.public(),
215			&sig,
216			&msg.as_slice(),
217		));
218
219		// different public key -> fail
220		assert!(!<AuthorityId as BeefyAuthorityId<BeefySignatureHasher>>::verify(
221			&Keyring::Bob.public(),
222			&sig,
223			&msg.as_slice(),
224		));
225
226		let msg = b"I am not Alice!";
227
228		// different msg -> fail
229		assert!(!<AuthorityId as BeefyAuthorityId<BeefySignatureHasher>>::verify(
230			&Keyring::Alice.public(),
231			&sig,
232			&msg.as_slice(),
233		));
234	}
235
236	/// Generate key pair in the given store using the provided seed
237	fn generate_in_store<AuthorityId>(
238		store: KeystorePtr,
239		key_type: sp_application_crypto::KeyTypeId,
240		owner: Option<Keyring<AuthorityId>>,
241	) -> AuthorityId
242	where
243		AuthorityId:
244			AuthorityIdBound + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Public>,
245		<AuthorityId as AppCrypto>::Pair: BeefySignerAuthority<BeefySignatureHasher>,
246		<AuthorityId as RuntimeAppPublic>::Signature:
247			Send + Sync + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Signature>,
248	{
249		let optional_seed: Option<String> = owner.map(|owner| owner.to_seed());
250
251		match <AuthorityId as AppCrypto>::CRYPTO_ID {
252			ecdsa::CRYPTO_ID => {
253				let pk = store.ecdsa_generate_new(key_type, optional_seed.as_deref()).ok().unwrap();
254				AuthorityId::decode(&mut pk.as_ref()).unwrap()
255			},
256			#[cfg(feature = "bls-experimental")]
257			ecdsa_bls381::CRYPTO_ID => {
258				let pk = store
259					.ecdsa_bls381_generate_new(key_type, optional_seed.as_deref())
260					.ok()
261					.unwrap();
262				AuthorityId::decode(&mut pk.as_ref()).unwrap()
263			},
264			_ => panic!("Requested CRYPTO_ID is not supported by the BEEFY Keyring"),
265		}
266	}
267
268	#[test]
269	fn pair_verify_should_work_ecdsa() {
270		pair_verify_should_work::<ecdsa_crypto::AuthorityId>();
271	}
272
273	#[cfg(feature = "bls-experimental")]
274	#[test]
275	fn pair_verify_should_work_ecdsa_n_bls() {
276		pair_verify_should_work::<ecdsa_bls_crypto::AuthorityId>();
277	}
278
279	fn pair_works<
280		AuthorityId: AuthorityIdBound + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Public>,
281	>()
282	where
283		<AuthorityId as sp_runtime::RuntimeAppPublic>::Signature:
284			Send + Sync + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Signature>,
285		<AuthorityId as AppCrypto>::Pair: BeefySignerAuthority<sp_runtime::traits::Keccak256>,
286	{
287		let want = <AuthorityId as AppCrypto>::Pair::from_string("//Alice", None)
288			.expect("Pair failed")
289			.to_raw_vec();
290		let got = Keyring::<AuthorityId>::Alice.pair().to_raw_vec();
291		assert_eq!(want, got);
292
293		let want = <AuthorityId as AppCrypto>::Pair::from_string("//Bob", None)
294			.expect("Pair failed")
295			.to_raw_vec();
296		let got = Keyring::<AuthorityId>::Bob.pair().to_raw_vec();
297		assert_eq!(want, got);
298
299		let want = <AuthorityId as AppCrypto>::Pair::from_string("//Charlie", None)
300			.expect("Pair failed")
301			.to_raw_vec();
302		let got = Keyring::<AuthorityId>::Charlie.pair().to_raw_vec();
303		assert_eq!(want, got);
304
305		let want = <AuthorityId as AppCrypto>::Pair::from_string("//Dave", None)
306			.expect("Pair failed")
307			.to_raw_vec();
308		let got = Keyring::<AuthorityId>::Dave.pair().to_raw_vec();
309		assert_eq!(want, got);
310
311		let want = <AuthorityId as AppCrypto>::Pair::from_string("//Eve", None)
312			.expect("Pair failed")
313			.to_raw_vec();
314		let got = Keyring::<AuthorityId>::Eve.pair().to_raw_vec();
315		assert_eq!(want, got);
316
317		let want = <AuthorityId as AppCrypto>::Pair::from_string("//Ferdie", None)
318			.expect("Pair failed")
319			.to_raw_vec();
320		let got = Keyring::<AuthorityId>::Ferdie.pair().to_raw_vec();
321		assert_eq!(want, got);
322
323		let want = <AuthorityId as AppCrypto>::Pair::from_string("//One", None)
324			.expect("Pair failed")
325			.to_raw_vec();
326		let got = Keyring::<AuthorityId>::One.pair().to_raw_vec();
327		assert_eq!(want, got);
328
329		let want = <AuthorityId as AppCrypto>::Pair::from_string("//Two", None)
330			.expect("Pair failed")
331			.to_raw_vec();
332		let got = Keyring::<AuthorityId>::Two.pair().to_raw_vec();
333		assert_eq!(want, got);
334	}
335
336	#[test]
337	fn ecdsa_pair_works() {
338		pair_works::<ecdsa_crypto::AuthorityId>();
339	}
340
341	#[cfg(feature = "bls-experimental")]
342	#[test]
343	fn ecdsa_n_bls_pair_works() {
344		pair_works::<ecdsa_bls_crypto::AuthorityId>();
345	}
346
347	fn authority_id_works<
348		AuthorityId: AuthorityIdBound + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Public>,
349	>()
350	where
351		<AuthorityId as sp_runtime::RuntimeAppPublic>::Signature:
352			Send + Sync + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Signature>,
353		<AuthorityId as AppCrypto>::Pair: BeefySignerAuthority<sp_runtime::traits::Keccak256>,
354	{
355		let store = keystore();
356
357		generate_in_store::<AuthorityId>(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Alice));
358
359		let alice = Keyring::<AuthorityId>::Alice.public();
360
361		let bob = Keyring::Bob.public();
362		let charlie = Keyring::Charlie.public();
363
364		let beefy_store: BeefyKeystore<AuthorityId> = Some(store).into();
365
366		let mut keys = vec![bob, charlie];
367
368		let id = beefy_store.authority_id(keys.as_slice());
369		assert!(id.is_none());
370
371		keys.push(alice.clone());
372
373		let id = beefy_store.authority_id(keys.as_slice()).unwrap();
374		assert_eq!(id, alice);
375	}
376
377	#[test]
378	fn authority_id_works_for_ecdsa() {
379		authority_id_works::<ecdsa_crypto::AuthorityId>();
380	}
381
382	#[cfg(feature = "bls-experimental")]
383	#[test]
384	fn authority_id_works_for_ecdsa_n_bls() {
385		authority_id_works::<ecdsa_bls_crypto::AuthorityId>();
386	}
387
388	fn sign_works<
389		AuthorityId: AuthorityIdBound + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Public>,
390	>()
391	where
392		<AuthorityId as sp_runtime::RuntimeAppPublic>::Signature:
393			Send + Sync + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Signature>,
394		<AuthorityId as AppCrypto>::Pair: BeefySignerAuthority<sp_runtime::traits::Keccak256>,
395	{
396		let store = keystore();
397
398		generate_in_store::<AuthorityId>(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Alice));
399
400		let alice = Keyring::Alice.public();
401
402		let store: BeefyKeystore<AuthorityId> = Some(store).into();
403
404		let msg = b"are you involved or committed?";
405
406		let sig1 = store.sign(&alice, msg).unwrap();
407		let sig2 = Keyring::<AuthorityId>::Alice.sign(msg);
408
409		assert_eq!(sig1, sig2);
410	}
411
412	#[test]
413	fn sign_works_for_ecdsa() {
414		sign_works::<ecdsa_crypto::AuthorityId>();
415	}
416
417	#[cfg(feature = "bls-experimental")]
418	#[test]
419	fn sign_works_for_ecdsa_n_bls() {
420		sign_works::<ecdsa_bls_crypto::AuthorityId>();
421	}
422
423	fn sign_error<
424		AuthorityId: AuthorityIdBound + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Public>,
425	>(
426		expected_error_message: &str,
427	) where
428		<AuthorityId as sp_runtime::RuntimeAppPublic>::Signature:
429			Send + Sync + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Signature>,
430		<AuthorityId as AppCrypto>::Pair: BeefySignerAuthority<sp_runtime::traits::Keccak256>,
431	{
432		let store = keystore();
433
434		generate_in_store::<AuthorityId>(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Bob));
435
436		let store: BeefyKeystore<AuthorityId> = Some(store).into();
437
438		let alice = Keyring::Alice.public();
439
440		let msg = b"are you involved or committed?";
441		let sig = store.sign(&alice, msg).err().unwrap();
442		let err = Error::Signature(expected_error_message.to_string());
443
444		assert_eq!(sig, err);
445	}
446
447	#[test]
448	fn sign_error_for_ecdsa() {
449		sign_error::<ecdsa_crypto::AuthorityId>("ecdsa_sign_prehashed() failed");
450	}
451
452	#[cfg(feature = "bls-experimental")]
453	#[test]
454	fn sign_error_for_ecdsa_n_bls() {
455		sign_error::<ecdsa_bls_crypto::AuthorityId>("bls381_sign()  failed");
456	}
457
458	#[test]
459	fn sign_no_keystore() {
460		let store: BeefyKeystore<ecdsa_crypto::Public> = None.into();
461
462		let alice = Keyring::Alice.public();
463		let msg = b"are you involved or committed";
464
465		let sig = store.sign(&alice, msg).err().unwrap();
466		let err = Error::Keystore("no Keystore".to_string());
467		assert_eq!(sig, err);
468	}
469
470	fn verify_works<
471		AuthorityId: AuthorityIdBound + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Public>,
472	>()
473	where
474		<AuthorityId as sp_runtime::RuntimeAppPublic>::Signature:
475			Send + Sync + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Signature>,
476		<AuthorityId as AppCrypto>::Pair: BeefySignerAuthority<sp_runtime::traits::Keccak256>,
477	{
478		let store = keystore();
479
480		generate_in_store::<AuthorityId>(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Alice));
481
482		let store: BeefyKeystore<AuthorityId> = Some(store).into();
483
484		let alice = Keyring::Alice.public();
485
486		// `msg` and `sig` match
487		let msg = b"are you involved or committed?";
488		let sig = store.sign(&alice, msg).unwrap();
489		assert!(BeefyKeystore::verify(&alice, &sig, msg));
490
491		// `msg and `sig` don't match
492		let msg = b"you are just involved";
493		assert!(!BeefyKeystore::verify(&alice, &sig, msg));
494	}
495
496	#[test]
497	fn verify_works_for_ecdsa() {
498		verify_works::<ecdsa_crypto::AuthorityId>();
499	}
500
501	#[cfg(feature = "bls-experimental")]
502	#[test]
503
504	fn verify_works_for_ecdsa_n_bls() {
505		verify_works::<ecdsa_bls_crypto::AuthorityId>();
506	}
507
508	// Note that we use keys with and without a seed for this test.
509	fn public_keys_works<
510		AuthorityId: AuthorityIdBound + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Public>,
511	>()
512	where
513		<AuthorityId as sp_runtime::RuntimeAppPublic>::Signature:
514			Send + Sync + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Signature>,
515		<AuthorityId as AppCrypto>::Pair: BeefySignerAuthority<sp_runtime::traits::Keccak256>,
516	{
517		const TEST_TYPE: sp_application_crypto::KeyTypeId =
518			sp_application_crypto::KeyTypeId(*b"test");
519
520		let store = keystore();
521
522		// test keys
523		let _ = generate_in_store::<AuthorityId>(store.clone(), TEST_TYPE, Some(Keyring::Alice));
524		let _ = generate_in_store::<AuthorityId>(store.clone(), TEST_TYPE, Some(Keyring::Bob));
525
526		// BEEFY keys
527		let _ =
528			generate_in_store::<AuthorityId>(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Dave));
529		let _ = generate_in_store::<AuthorityId>(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Eve));
530
531		let _ = generate_in_store::<AuthorityId>(store.clone(), TEST_TYPE, None);
532		let _ = generate_in_store::<AuthorityId>(store.clone(), TEST_TYPE, None);
533
534		let key1 = generate_in_store::<AuthorityId>(store.clone(), BEEFY_KEY_TYPE, None);
535		let key2 = generate_in_store::<AuthorityId>(store.clone(), BEEFY_KEY_TYPE, None);
536
537		let store: BeefyKeystore<AuthorityId> = Some(store).into();
538
539		let keys = store.public_keys().ok().unwrap();
540
541		assert!(keys.len() == 4);
542		assert!(keys.contains(&Keyring::Dave.public()));
543		assert!(keys.contains(&Keyring::Eve.public()));
544		assert!(keys.contains(&key1));
545		assert!(keys.contains(&key2));
546	}
547
548	#[test]
549	fn public_keys_works_for_ecdsa_keystore() {
550		public_keys_works::<ecdsa_crypto::AuthorityId>();
551	}
552
553	#[cfg(feature = "bls-experimental")]
554	#[test]
555
556	fn public_keys_works_for_ecdsa_n_bls() {
557		public_keys_works::<ecdsa_bls_crypto::AuthorityId>();
558	}
559}