referrerpolicy=no-referrer-when-downgrade

sp_consensus_beefy/
commitment.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18use alloc::{vec, vec::Vec};
19use codec::{Decode, DecodeWithMemTracking, Encode, Error, Input};
20use core::cmp;
21use scale_info::TypeInfo;
22use sp_application_crypto::RuntimeAppPublic;
23
24use crate::{BeefyAuthorityId, Payload, ValidatorSet, ValidatorSetId};
25
26/// A commitment signature, accompanied by the id of the validator that it belongs to.
27#[derive(Debug)]
28pub struct KnownSignature<TAuthorityId, TSignature> {
29	/// The signing validator.
30	pub validator_id: TAuthorityId,
31	/// The signature.
32	pub signature: TSignature,
33}
34
35impl<TAuthorityId: Clone, TSignature: Clone> KnownSignature<&TAuthorityId, &TSignature> {
36	/// Creates a `KnownSignature<TAuthorityId, TSignature>` from an
37	/// `KnownSignature<&TAuthorityId, &TSignature>`.
38	pub fn to_owned(&self) -> KnownSignature<TAuthorityId, TSignature> {
39		KnownSignature {
40			validator_id: self.validator_id.clone(),
41			signature: self.signature.clone(),
42		}
43	}
44}
45
46/// A commitment signed by GRANDPA validators as part of BEEFY protocol.
47///
48/// The commitment contains a [payload](Commitment::payload) extracted from the finalized block at
49/// height [block_number](Commitment::block_number).
50/// GRANDPA validators collect signatures on commitments and a stream of such signed commitments
51/// (see [SignedCommitment]) forms the BEEFY protocol.
52#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, DecodeWithMemTracking, TypeInfo)]
53pub struct Commitment<TBlockNumber> {
54	///  A collection of payloads to be signed, see [`Payload`] for details.
55	///
56	/// One of the payloads should be some form of cumulative representation of the chain (think
57	/// MMR root hash). Additionally one of the payloads should also contain some details that
58	/// allow the light client to verify next validator set. The protocol does not enforce any
59	/// particular format of this data, nor how often it should be present in commitments, however
60	/// the light client has to be provided with full validator set whenever it performs the
61	/// transition (i.e. importing first block with
62	/// [validator_set_id](Commitment::validator_set_id) incremented).
63	pub payload: Payload,
64
65	/// Finalized block number this commitment is for.
66	///
67	/// GRANDPA validators agree on a block they create a commitment for and start collecting
68	/// signatures. This process is called a round.
69	/// There might be multiple rounds in progress (depending on the block choice rule), however
70	/// since the payload is supposed to be cumulative, it is not required to import all
71	/// commitments.
72	/// BEEFY light client is expected to import at least one commitment per epoch,
73	/// but is free to import as many as it requires.
74	pub block_number: TBlockNumber,
75
76	/// BEEFY validator set supposed to sign this commitment.
77	///
78	/// Validator set is changing once per epoch. The Light Client must be provided by details
79	/// about the validator set whenever it's importing first commitment with a new
80	/// `validator_set_id`. Validator set data MUST be verifiable, for instance using
81	/// [payload](Commitment::payload) information.
82	pub validator_set_id: ValidatorSetId,
83}
84
85impl<TBlockNumber> cmp::PartialOrd for Commitment<TBlockNumber>
86where
87	TBlockNumber: cmp::Ord,
88{
89	fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
90		Some(self.cmp(other))
91	}
92}
93
94impl<TBlockNumber> cmp::Ord for Commitment<TBlockNumber>
95where
96	TBlockNumber: cmp::Ord,
97{
98	fn cmp(&self, other: &Self) -> cmp::Ordering {
99		self.validator_set_id
100			.cmp(&other.validator_set_id)
101			.then_with(|| self.block_number.cmp(&other.block_number))
102			.then_with(|| self.payload.cmp(&other.payload))
103	}
104}
105
106/// A commitment with matching GRANDPA validators' signatures.
107///
108/// Note that SCALE-encoding of the structure is optimized for size efficiency over the wire,
109/// please take a look at custom [`Encode`] and [`Decode`] implementations and
110/// `CompactSignedCommitment` struct.
111#[derive(Clone, Debug, PartialEq, Eq, TypeInfo)]
112pub struct SignedCommitment<TBlockNumber, TSignature> {
113	/// The commitment signatures are collected for.
114	pub commitment: Commitment<TBlockNumber>,
115	/// GRANDPA validators' signatures for the commitment.
116	///
117	/// The length of this `Vec` must match number of validators in the current set (see
118	/// [Commitment::validator_set_id]).
119	pub signatures: Vec<Option<TSignature>>,
120}
121
122impl<TBlockNumber: core::fmt::Debug, TSignature> core::fmt::Display
123	for SignedCommitment<TBlockNumber, TSignature>
124{
125	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
126		let signatures_count = self.signatures.iter().filter(|s| s.is_some()).count();
127		write!(
128			f,
129			"SignedCommitment(commitment: {:?}, signatures_count: {})",
130			self.commitment, signatures_count
131		)
132	}
133}
134
135impl<TBlockNumber, TSignature> SignedCommitment<TBlockNumber, TSignature> {
136	/// Return the number of collected signatures.
137	pub fn signature_count(&self) -> usize {
138		self.signatures.iter().filter(|x| x.is_some()).count()
139	}
140
141	/// Verify all the commitment signatures against the validator set that was active
142	/// at the block where the commitment was generated.
143	///
144	/// Returns the valid validator-signature pairs if the commitment can be verified.
145	pub fn verify_signatures<'a, TAuthorityId>(
146		&'a self,
147		target_number: TBlockNumber,
148		validator_set: &'a ValidatorSet<TAuthorityId>,
149	) -> Result<Vec<KnownSignature<&'a TAuthorityId, &'a TSignature>>, u32>
150	where
151		TBlockNumber: Clone + Encode + PartialEq,
152		TAuthorityId: RuntimeAppPublic<Signature = TSignature> + BeefyAuthorityId,
153	{
154		if self.signatures.len() != validator_set.len() ||
155			self.commitment.validator_set_id != validator_set.id() ||
156			self.commitment.block_number != target_number
157		{
158			return Err(0);
159		}
160
161		// Arrangement of signatures in the commitment should be in the same order
162		// as validators for that set.
163		let encoded_commitment = self.commitment.encode();
164		let signatories: Vec<_> = validator_set
165			.validators()
166			.into_iter()
167			.zip(self.signatures.iter())
168			.filter_map(|(id, maybe_signature)| {
169				let signature = maybe_signature.as_ref()?;
170				match BeefyAuthorityId::verify(id, signature, &encoded_commitment) {
171					true => Some(KnownSignature { validator_id: id, signature }),
172					false => None,
173				}
174			})
175			.collect();
176
177		Ok(signatories)
178	}
179}
180
181/// Type to be used to denote placement of signatures
182type BitField = Vec<u8>;
183/// Compress 8 bit values into a single u8 Byte
184const CONTAINER_BIT_SIZE: usize = 8;
185
186/// Compressed representation of [`SignedCommitment`], used for encoding efficiency.
187#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
188struct CompactSignedCommitment<TBlockNumber, TSignature> {
189	/// The commitment, unchanged compared to regular [`SignedCommitment`].
190	commitment: Commitment<TBlockNumber>,
191	/// A bitfield representing presence of a signature coming from a validator at some index.
192	///
193	/// The bit at index `0` is set to `1` in case we have a signature coming from a validator at
194	/// index `0` in in the original validator set. In case the [`SignedCommitment`] does not
195	/// contain that signature the `bit` will be set to `0`. Bits are packed into `Vec<u8>`
196	signatures_from: BitField,
197	/// Number of validators in the Validator Set and hence number of significant bits in the
198	/// [`signatures_from`] collection.
199	///
200	/// Note this might be smaller than the size of `signatures_compact` in case some signatures
201	/// are missing.
202	validator_set_len: u32,
203	/// A `Vec` containing all `Signature`s present in the original [`SignedCommitment`].
204	///
205	/// Note that in order to associate a `Signature` from this `Vec` with a validator, one needs
206	/// to look at the `signatures_from` bitfield, since some validators might have not produced a
207	/// signature.
208	signatures_compact: Vec<TSignature>,
209}
210
211impl<'a, TBlockNumber: Clone, TSignature> CompactSignedCommitment<TBlockNumber, &'a TSignature> {
212	/// Packs a `SignedCommitment` into the compressed `CompactSignedCommitment` format for
213	/// efficient network transport.
214	fn pack(signed_commitment: &'a SignedCommitment<TBlockNumber, TSignature>) -> Self {
215		let SignedCommitment { commitment, signatures } = signed_commitment;
216		let validator_set_len = signatures.len() as u32;
217
218		let signatures_compact: Vec<&'a TSignature> =
219			signatures.iter().filter_map(|x| x.as_ref()).collect();
220		let bits = {
221			let mut bits: Vec<u8> =
222				signatures.iter().map(|x| if x.is_some() { 1 } else { 0 }).collect();
223			// Resize with excess bits for placement purposes
224			let excess_bits_len =
225				CONTAINER_BIT_SIZE - (validator_set_len as usize % CONTAINER_BIT_SIZE);
226			bits.resize(bits.len() + excess_bits_len, 0);
227			bits
228		};
229
230		let mut signatures_from: BitField = vec![];
231		let chunks = bits.chunks(CONTAINER_BIT_SIZE);
232		for chunk in chunks {
233			let mut iter = chunk.iter().copied();
234			let mut v = iter.next().unwrap() as u8;
235
236			for bit in iter {
237				v <<= 1;
238				v |= bit as u8;
239			}
240
241			signatures_from.push(v);
242		}
243
244		Self {
245			commitment: commitment.clone(),
246			signatures_from,
247			validator_set_len,
248			signatures_compact,
249		}
250	}
251
252	/// Unpacks a `CompactSignedCommitment` into the uncompressed `SignedCommitment` form.
253	fn unpack(
254		temporary_signatures: CompactSignedCommitment<TBlockNumber, TSignature>,
255	) -> SignedCommitment<TBlockNumber, TSignature> {
256		let CompactSignedCommitment {
257			commitment,
258			signatures_from,
259			validator_set_len,
260			signatures_compact,
261		} = temporary_signatures;
262		let mut bits: Vec<u8> = vec![];
263
264		for block in signatures_from {
265			for bit in 0..CONTAINER_BIT_SIZE {
266				bits.push((block >> (CONTAINER_BIT_SIZE - bit - 1)) & 1);
267			}
268		}
269
270		bits.truncate(validator_set_len as usize);
271
272		let mut next_signature = signatures_compact.into_iter();
273		let signatures: Vec<Option<TSignature>> = bits
274			.iter()
275			.map(|&x| if x == 1 { next_signature.next() } else { None })
276			.collect();
277
278		SignedCommitment { commitment, signatures }
279	}
280}
281
282impl<TBlockNumber, TSignature> Encode for SignedCommitment<TBlockNumber, TSignature>
283where
284	TBlockNumber: Encode + Clone,
285	TSignature: Encode,
286{
287	fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
288		let temp = CompactSignedCommitment::pack(self);
289		temp.using_encoded(f)
290	}
291}
292
293impl<TBlockNumber, TSignature> Decode for SignedCommitment<TBlockNumber, TSignature>
294where
295	TBlockNumber: Decode + Clone,
296	TSignature: Decode,
297{
298	fn decode<I: Input>(input: &mut I) -> Result<Self, Error> {
299		let temp = CompactSignedCommitment::decode(input)?;
300		Ok(CompactSignedCommitment::unpack(temp))
301	}
302}
303
304/// A [SignedCommitment] with a version number.
305///
306/// This variant will be appended to the block justifications for the block
307/// for which the signed commitment has been generated.
308///
309/// Note that this enum is subject to change in the future with introduction
310/// of additional cryptographic primitives to BEEFY.
311#[derive(Clone, Debug, PartialEq, codec::Encode, codec::Decode)]
312pub enum VersionedFinalityProof<N, S> {
313	#[codec(index = 1)]
314	/// Current active version
315	V1(SignedCommitment<N, S>),
316}
317
318impl<N: core::fmt::Debug, S> core::fmt::Display for VersionedFinalityProof<N, S> {
319	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
320		match self {
321			VersionedFinalityProof::V1(sc) => write!(f, "VersionedFinalityProof::V1({})", sc),
322		}
323	}
324}
325
326impl<N, S> From<SignedCommitment<N, S>> for VersionedFinalityProof<N, S> {
327	fn from(commitment: SignedCommitment<N, S>) -> Self {
328		VersionedFinalityProof::V1(commitment)
329	}
330}
331
332#[cfg(test)]
333mod tests {
334
335	use super::*;
336	use crate::{ecdsa_crypto::Signature as EcdsaSignature, known_payloads};
337	use codec::Decode;
338	use sp_core::Pair;
339	use sp_crypto_hashing::keccak_256;
340
341	#[cfg(feature = "bls-experimental")]
342	use crate::bls_crypto::Signature as BlsSignature;
343
344	type TestCommitment = Commitment<u128>;
345
346	const LARGE_RAW_COMMITMENT: &[u8] = include_bytes!("../test-res/large-raw-commitment");
347
348	// Types for bls-less commitment
349	type TestEcdsaSignedCommitment = SignedCommitment<u128, EcdsaSignature>;
350	type TestVersionedFinalityProof = VersionedFinalityProof<u128, EcdsaSignature>;
351
352	#[cfg(feature = "bls-experimental")]
353	#[derive(Clone, Debug, PartialEq, codec::Encode, codec::Decode)]
354	struct EcdsaBlsSignaturePair(EcdsaSignature, BlsSignature);
355
356	#[cfg(feature = "bls-experimental")]
357	type TestBlsSignedCommitment = SignedCommitment<u128, EcdsaBlsSignaturePair>;
358
359	// Generates mock aggregatable ecdsa signature for generating test commitment
360	// BLS signatures
361	fn mock_ecdsa_signatures() -> (EcdsaSignature, EcdsaSignature) {
362		let alice = sp_core::ecdsa::Pair::from_string("//Alice", None).unwrap();
363
364		let msg = keccak_256(b"This is the first message");
365		let sig1 = alice.sign_prehashed(&msg);
366
367		let msg = keccak_256(b"This is the second message");
368		let sig2 = alice.sign_prehashed(&msg);
369
370		(sig1.into(), sig2.into())
371	}
372
373	// Generates mock aggregatable bls signature for generating test commitment
374	// BLS signatures
375	#[cfg(feature = "bls-experimental")]
376	fn mock_bls_signatures() -> (BlsSignature, BlsSignature) {
377		let alice = sp_core::bls::Pair::from_string("//Alice", None).unwrap();
378
379		let msg = b"This is the first message";
380		let sig1 = alice.sign(msg);
381
382		let msg = b"This is the second message";
383		let sig2 = alice.sign(msg);
384
385		(sig1.into(), sig2.into())
386	}
387
388	#[test]
389	fn commitment_encode_decode() {
390		// given
391		let payload =
392			Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode());
393		let commitment: TestCommitment =
394			Commitment { payload, block_number: 5, validator_set_id: 0 };
395
396		// when
397		let encoded = codec::Encode::encode(&commitment);
398		let decoded = TestCommitment::decode(&mut &*encoded);
399
400		// then
401		assert_eq!(decoded, Ok(commitment));
402		assert_eq!(
403			encoded,
404			array_bytes::hex2bytes_unchecked(
405				"046d68343048656c6c6f20576f726c6421050000000000000000000000000000000000000000000000"
406			)
407		);
408	}
409
410	#[test]
411	fn signed_commitment_encode_decode_ecdsa() {
412		// given
413		let payload =
414			Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode());
415		let commitment: TestCommitment =
416			Commitment { payload, block_number: 5, validator_set_id: 0 };
417
418		let ecdsa_sigs = mock_ecdsa_signatures();
419
420		let ecdsa_signed = SignedCommitment {
421			commitment: commitment.clone(),
422			signatures: vec![None, None, Some(ecdsa_sigs.0.clone()), Some(ecdsa_sigs.1.clone())],
423		};
424
425		// when
426		let encoded = codec::Encode::encode(&ecdsa_signed);
427		let decoded = TestEcdsaSignedCommitment::decode(&mut &*encoded);
428
429		// then
430		assert_eq!(decoded, Ok(ecdsa_signed));
431		assert_eq!(
432			encoded,
433			array_bytes::hex2bytes_unchecked(
434				"\
435				046d68343048656c6c6f20576f726c64210500000000000000000000000000000000000000000000000\
436				4300400000008558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c746c\
437				c321f2319a5e99a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba012d6e1f8105c337a86cdd9aa\
438				acdc496577f3db8c55ef9e6fd48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487bca2324b6\
439				a0046395a71681be3d0c2a00\
440			"
441			)
442		);
443	}
444
445	#[test]
446	#[cfg(feature = "bls-experimental")]
447	fn signed_commitment_encode_decode_ecdsa_n_bls() {
448		// given
449		let payload =
450			Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode());
451		let commitment: TestCommitment =
452			Commitment { payload, block_number: 5, validator_set_id: 0 };
453
454		let ecdsa_sigs = mock_ecdsa_signatures();
455
456		// including bls signature
457		let bls_signed_msgs = mock_bls_signatures();
458
459		let ecdsa_and_bls_signed = SignedCommitment {
460			commitment,
461			signatures: vec![
462				None,
463				None,
464				Some(EcdsaBlsSignaturePair(ecdsa_sigs.0, bls_signed_msgs.0)),
465				Some(EcdsaBlsSignaturePair(ecdsa_sigs.1, bls_signed_msgs.1)),
466			],
467		};
468
469		// when
470		let encoded = codec::Encode::encode(&ecdsa_and_bls_signed);
471		let decoded = TestBlsSignedCommitment::decode(&mut &*encoded);
472
473		// then
474		assert_eq!(decoded, Ok(ecdsa_and_bls_signed));
475		assert_eq!(
476			encoded,
477			array_bytes::hex2bytes_unchecked(
478				"046d68343048656c6c6f20576f726c642105000000000000000000000000000000000000000000000004300400000008558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c746cc321f2319a5e99a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba0182022df4689ef25499205f7154a1a62eb2d6d5c4a3657efed321e2c277998130d1b01a264c928afb79534cb0fa9dcf79f67ed4e6bf2de576bb936146f2fa60fa56b8651677cc764ea4fe317c62294c2a0c5966e439653eed0572fded5e2461c888518e0769718dcce9f3ff612fb89d262d6e1f8105c337a86cdd9aaacdc496577f3db8c55ef9e6fd48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487bca2324b6a0046395a71681be3d0c2a00a90973bea76fac3a4e2d76a25ec3926d6a5a20aacee15ec0756cd268088ed5612b67b4a49349cee70bc1185078d17c7f7df9d944e8be30022d9680d0437c4ba4600d74050692e8ee9b96e37df2a39d1cb4b4af4b6a058342dd9e8c7481a3a0b8975ad8614c953e950253aa327698d842"
479			)
480		);
481	}
482
483	#[test]
484	fn signed_commitment_count_signatures() {
485		// given
486		let payload =
487			Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode());
488		let commitment: TestCommitment =
489			Commitment { payload, block_number: 5, validator_set_id: 0 };
490
491		let sigs = mock_ecdsa_signatures();
492
493		let mut signed = SignedCommitment {
494			commitment,
495			signatures: vec![None, None, Some(sigs.0), Some(sigs.1)],
496		};
497		assert_eq!(signed.signature_count(), 2);
498
499		// when
500		signed.signatures[2] = None;
501
502		// then
503		assert_eq!(signed.signature_count(), 1);
504	}
505
506	#[test]
507	fn commitment_ordering() {
508		fn commitment(
509			block_number: u128,
510			validator_set_id: crate::ValidatorSetId,
511		) -> TestCommitment {
512			let payload =
513				Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode());
514			Commitment { payload, block_number, validator_set_id }
515		}
516
517		// given
518		let a = commitment(1, 0);
519		let b = commitment(2, 1);
520		let c = commitment(10, 0);
521		let d = commitment(10, 1);
522
523		// then
524		assert!(a < b);
525		assert!(a < c);
526		assert!(c < b);
527		assert!(c < d);
528		assert!(b < d);
529	}
530
531	#[test]
532	fn versioned_commitment_encode_decode() {
533		let payload =
534			Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode());
535		let commitment: TestCommitment =
536			Commitment { payload, block_number: 5, validator_set_id: 0 };
537
538		let sigs = mock_ecdsa_signatures();
539
540		let signed = SignedCommitment {
541			commitment,
542			signatures: vec![None, None, Some(sigs.0), Some(sigs.1)],
543		};
544
545		let versioned = TestVersionedFinalityProof::V1(signed.clone());
546
547		let encoded = codec::Encode::encode(&versioned);
548
549		assert_eq!(1, encoded[0]);
550		assert_eq!(encoded[1..], codec::Encode::encode(&signed));
551
552		let decoded = TestVersionedFinalityProof::decode(&mut &*encoded);
553
554		assert_eq!(decoded, Ok(versioned));
555	}
556
557	#[test]
558	fn large_signed_commitment_encode_decode() {
559		// given
560		let payload =
561			Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode());
562		let commitment: TestCommitment =
563			Commitment { payload, block_number: 5, validator_set_id: 0 };
564
565		let sigs = mock_ecdsa_signatures();
566
567		let signatures: Vec<Option<_>> = (0..1024)
568			.into_iter()
569			.map(|x| if x < 340 { None } else { Some(sigs.0.clone()) })
570			.collect();
571		let signed = SignedCommitment { commitment, signatures };
572
573		// when
574		let encoded = codec::Encode::encode(&signed);
575		let decoded = TestEcdsaSignedCommitment::decode(&mut &*encoded);
576
577		// then
578		assert_eq!(decoded, Ok(signed));
579		assert_eq!(encoded, LARGE_RAW_COMMITMENT);
580	}
581}