referrerpolicy=no-referrer-when-downgrade

sc_consensus_beefy/
justification.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/>.
18
19use codec::DecodeAll;
20use sp_application_crypto::RuntimeAppPublic;
21use sp_consensus::Error as ConsensusError;
22use sp_consensus_beefy::{
23	AuthorityIdBound, BeefySignatureHasher, KnownSignature, ValidatorSet, ValidatorSetId,
24	VersionedFinalityProof,
25};
26use sp_runtime::traits::{Block as BlockT, NumberFor};
27
28/// A finality proof with matching BEEFY authorities' signatures.
29pub type BeefyVersionedFinalityProof<Block, AuthorityId> =
30	VersionedFinalityProof<NumberFor<Block>, <AuthorityId as RuntimeAppPublic>::Signature>;
31
32pub(crate) fn proof_block_num_and_set_id<Block: BlockT, AuthorityId: AuthorityIdBound>(
33	proof: &BeefyVersionedFinalityProof<Block, AuthorityId>,
34) -> (NumberFor<Block>, ValidatorSetId) {
35	match proof {
36		VersionedFinalityProof::V1(sc) =>
37			(sc.commitment.block_number, sc.commitment.validator_set_id),
38	}
39}
40
41/// Decode and verify a Beefy FinalityProof.
42pub(crate) fn decode_and_verify_finality_proof<Block: BlockT, AuthorityId: AuthorityIdBound>(
43	encoded: &[u8],
44	target_number: NumberFor<Block>,
45	validator_set: &ValidatorSet<AuthorityId>,
46) -> Result<BeefyVersionedFinalityProof<Block, AuthorityId>, (ConsensusError, u32)> {
47	let proof = <BeefyVersionedFinalityProof<Block, AuthorityId>>::decode_all(&mut &*encoded)
48		.map_err(|_| (ConsensusError::InvalidJustification, 0))?;
49	verify_with_validator_set::<Block, AuthorityId>(target_number, validator_set, &proof)?;
50	Ok(proof)
51}
52
53/// Verify the Beefy finality proof against the validator set at the block it was generated.
54pub(crate) fn verify_with_validator_set<'a, Block: BlockT, AuthorityId: AuthorityIdBound>(
55	target_number: NumberFor<Block>,
56	validator_set: &'a ValidatorSet<AuthorityId>,
57	proof: &'a BeefyVersionedFinalityProof<Block, AuthorityId>,
58) -> Result<
59	Vec<KnownSignature<&'a AuthorityId, &'a <AuthorityId as RuntimeAppPublic>::Signature>>,
60	(ConsensusError, u32),
61> {
62	match proof {
63		VersionedFinalityProof::V1(signed_commitment) => {
64			let signatories = signed_commitment
65				.verify_signatures::<_, BeefySignatureHasher>(target_number, validator_set)
66				.map_err(|checked_signatures| {
67					(ConsensusError::InvalidJustification, checked_signatures)
68				})?;
69
70			if signatories.len() >= crate::round::threshold(validator_set.len()) {
71				Ok(signatories)
72			} else {
73				Err((
74					ConsensusError::InvalidJustification,
75					signed_commitment.signature_count() as u32,
76				))
77			}
78		},
79	}
80}
81
82#[cfg(test)]
83pub(crate) mod tests {
84	use codec::Encode;
85	use sp_consensus_beefy::{
86		ecdsa_crypto, known_payloads, test_utils::Keyring, Commitment, Payload, SignedCommitment,
87		VersionedFinalityProof,
88	};
89	use substrate_test_runtime_client::runtime::Block;
90
91	use super::*;
92	use crate::tests::make_beefy_ids;
93
94	pub(crate) fn new_finality_proof(
95		block_num: NumberFor<Block>,
96		validator_set: &ValidatorSet<ecdsa_crypto::AuthorityId>,
97		keys: &[Keyring<ecdsa_crypto::AuthorityId>],
98	) -> BeefyVersionedFinalityProof<Block, ecdsa_crypto::AuthorityId> {
99		let commitment = Commitment {
100			payload: Payload::from_single_entry(known_payloads::MMR_ROOT_ID, vec![]),
101			block_number: block_num,
102			validator_set_id: validator_set.id(),
103		};
104		let message = commitment.encode();
105		let signatures = keys.iter().map(|key| Some(key.sign(&message))).collect();
106		VersionedFinalityProof::V1(SignedCommitment { commitment, signatures })
107	}
108
109	#[test]
110	fn should_verify_with_validator_set() {
111		let keys = &[Keyring::Alice, Keyring::Bob, Keyring::Charlie];
112		let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap();
113
114		// build valid justification
115		let block_num = 42;
116		let proof = new_finality_proof(block_num, &validator_set, keys);
117
118		let good_proof = proof.clone().into();
119		// should verify successfully
120		verify_with_validator_set::<Block, ecdsa_crypto::AuthorityId>(
121			block_num,
122			&validator_set,
123			&good_proof,
124		)
125		.unwrap();
126
127		// wrong block number -> should fail verification
128		let good_proof = proof.clone().into();
129		match verify_with_validator_set::<Block, ecdsa_crypto::AuthorityId>(
130			block_num + 1,
131			&validator_set,
132			&good_proof,
133		) {
134			Err((ConsensusError::InvalidJustification, 0)) => (),
135			e => assert!(false, "Got unexpected {:?}", e),
136		};
137
138		// wrong validator set id -> should fail verification
139		let good_proof = proof.clone().into();
140		let other = ValidatorSet::new(make_beefy_ids(keys), 1).unwrap();
141		match verify_with_validator_set::<Block, ecdsa_crypto::AuthorityId>(
142			block_num,
143			&other,
144			&good_proof,
145		) {
146			Err((ConsensusError::InvalidJustification, 0)) => (),
147			e => assert!(false, "Got unexpected {:?}", e),
148		};
149
150		// wrong signatures length -> should fail verification
151		let mut bad_proof = proof.clone();
152		// change length of signatures
153		let bad_signed_commitment = match bad_proof {
154			VersionedFinalityProof::V1(ref mut sc) => sc,
155		};
156		bad_signed_commitment.signatures.pop().flatten().unwrap();
157		match verify_with_validator_set::<Block, ecdsa_crypto::AuthorityId>(
158			block_num + 1,
159			&validator_set,
160			&bad_proof.into(),
161		) {
162			Err((ConsensusError::InvalidJustification, 0)) => (),
163			e => assert!(false, "Got unexpected {:?}", e),
164		};
165
166		// not enough signatures -> should fail verification
167		let mut bad_proof = proof.clone();
168		let bad_signed_commitment = match bad_proof {
169			VersionedFinalityProof::V1(ref mut sc) => sc,
170		};
171		// remove a signature (but same length)
172		*bad_signed_commitment.signatures.first_mut().unwrap() = None;
173		match verify_with_validator_set::<Block, ecdsa_crypto::AuthorityId>(
174			block_num,
175			&validator_set,
176			&bad_proof.into(),
177		) {
178			Err((ConsensusError::InvalidJustification, 2)) => (),
179			e => assert!(false, "Got unexpected {:?}", e),
180		};
181
182		// not enough _correct_ signatures -> should fail verification
183		let mut bad_proof = proof.clone();
184		let bad_signed_commitment = match bad_proof {
185			VersionedFinalityProof::V1(ref mut sc) => sc,
186		};
187		// change a signature to a different key
188		*bad_signed_commitment.signatures.first_mut().unwrap() = Some(
189			Keyring::<ecdsa_crypto::AuthorityId>::Dave
190				.sign(&bad_signed_commitment.commitment.encode()),
191		);
192		match verify_with_validator_set::<Block, ecdsa_crypto::AuthorityId>(
193			block_num,
194			&validator_set,
195			&bad_proof.into(),
196		) {
197			Err((ConsensusError::InvalidJustification, 3)) => (),
198			e => assert!(false, "Got unexpected {:?}", e),
199		};
200	}
201
202	#[test]
203	fn should_decode_and_verify_finality_proof() {
204		let keys = &[Keyring::Alice, Keyring::Bob];
205		let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap();
206		let block_num = 1;
207
208		// build valid justification
209		let proof = new_finality_proof(block_num, &validator_set, keys);
210		let versioned_proof: BeefyVersionedFinalityProof<Block, ecdsa_crypto::AuthorityId> =
211			proof.into();
212		let encoded = versioned_proof.encode();
213
214		// should successfully decode and verify
215		let verified = decode_and_verify_finality_proof::<Block, ecdsa_crypto::AuthorityId>(
216			&encoded,
217			block_num,
218			&validator_set,
219		)
220		.unwrap();
221		assert_eq!(verified, versioned_proof);
222	}
223}