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