smoldot/finality/verify.rs
1// Smoldot
2// Copyright (C) 2023 Pierre Krieger
3// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
4
5// This program is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14
15// You should have received a copy of the GNU General Public License
16// along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18use crate::finality::decode;
19
20use alloc::vec::Vec;
21use core::{cmp, iter, mem};
22use rand_chacha::{
23 ChaCha20Rng,
24 rand_core::{RngCore as _, SeedableRng as _},
25};
26
27/// Configuration for a commit verification process.
28#[derive(Debug)]
29pub struct CommitVerifyConfig<C> {
30 /// SCALE-encoded commit to verify.
31 pub commit: C,
32
33 /// Number of bytes used for encoding the block number in the SCALE-encoded commit.
34 pub block_number_bytes: usize,
35
36 // TODO: document
37 pub expected_authorities_set_id: u64,
38
39 /// Number of authorities that are allowed to emit pre-commits. Used to calculate the
40 /// threshold of the number of required signatures.
41 pub num_authorities: u32,
42
43 /// Seed for a PRNG used for various purposes during the verification.
44 ///
45 /// > **Note**: The verification is nonetheless deterministic.
46 pub randomness_seed: [u8; 32],
47}
48
49/// Commit verification in progress.
50#[must_use]
51pub enum CommitVerify<C> {
52 /// See [`CommitVerifyIsAuthority`].
53 IsAuthority(CommitVerifyIsAuthority<C>),
54 /// See [`CommitVerifyIsParent`].
55 IsParent(CommitVerifyIsParent<C>),
56 /// Verification is finished. Contains an error if the commit message is invalid.
57 Finished(Result<(), CommitVerifyError>),
58 /// Verification is finished, but [`CommitVerifyIsParent::resume`] has been called with `None`,
59 /// meaning that some signatures couldn't be verified, and the commit message doesn't contain
60 /// enough signatures that are known to be valid.
61 ///
62 /// The commit must be verified again after more blocks are available.
63 FinishedUnknown,
64}
65
66/// Verifies that a commit is valid.
67pub fn verify_commit<C: AsRef<[u8]>>(config: CommitVerifyConfig<C>) -> CommitVerify<C> {
68 let decoded_commit =
69 match decode::decode_grandpa_commit(config.commit.as_ref(), config.block_number_bytes) {
70 Ok(c) => c,
71 Err(_) => return CommitVerify::Finished(Err(CommitVerifyError::InvalidFormat)),
72 };
73
74 if decoded_commit.set_id != config.expected_authorities_set_id {
75 return CommitVerify::Finished(Err(CommitVerifyError::BadSetId));
76 }
77
78 if decoded_commit.auth_data.len() != decoded_commit.precommits.len() {
79 return CommitVerify::Finished(Err(CommitVerifyError::InvalidFormat));
80 }
81
82 let mut randomness = ChaCha20Rng::from_seed(config.randomness_seed);
83
84 // Make sure that there is no duplicate authority public key.
85 {
86 let mut unique = hashbrown::HashSet::with_capacity_and_hasher(
87 decoded_commit.auth_data.len(),
88 crate::util::SipHasherBuild::new({
89 let mut seed = [0; 16];
90 randomness.fill_bytes(&mut seed);
91 seed
92 }),
93 );
94 if let Some((_, faulty_pub_key)) = decoded_commit
95 .auth_data
96 .iter()
97 .find(|(_, pubkey)| !unique.insert(pubkey))
98 {
99 return CommitVerify::Finished(Err(CommitVerifyError::DuplicateSignature {
100 authority_key: **faulty_pub_key,
101 }));
102 }
103 }
104
105 CommitVerification {
106 commit: config.commit,
107 block_number_bytes: config.block_number_bytes,
108 next_precommit_index: 0,
109 next_precommit_author_verified: false,
110 next_precommit_block_verified: false,
111 num_verified_signatures: 0,
112 num_authorities: config.num_authorities,
113 signatures_batch: ed25519_zebra::batch::Verifier::new(),
114 randomness,
115 }
116 .resume()
117}
118
119/// Must return whether a certain public key is in the list of authorities that are allowed to
120/// generate pre-commits.
121#[must_use]
122pub struct CommitVerifyIsAuthority<C> {
123 inner: CommitVerification<C>,
124}
125
126impl<C: AsRef<[u8]>> CommitVerifyIsAuthority<C> {
127 /// Public key to verify.
128 pub fn authority_public_key(&self) -> &[u8; 32] {
129 debug_assert!(!self.inner.next_precommit_author_verified);
130 let decoded_commit = decode::decode_grandpa_commit(
131 self.inner.commit.as_ref(),
132 self.inner.block_number_bytes,
133 )
134 .unwrap();
135 decoded_commit.auth_data[self.inner.next_precommit_index].1
136 }
137
138 /// Resumes the verification process.
139 ///
140 /// Must be passed `true` if the public key is indeed in the list of authorities.
141 /// Passing `false` always returns [`CommitVerify::Finished`] containing an error.
142 pub fn resume(mut self, is_authority: bool) -> CommitVerify<C> {
143 if !is_authority {
144 let key = *self.authority_public_key();
145 return CommitVerify::Finished(Err(CommitVerifyError::NotAuthority {
146 authority_key: key,
147 }));
148 }
149
150 self.inner.next_precommit_author_verified = true;
151 self.inner.resume()
152 }
153}
154
155/// Must return whether a certain block is a descendant of the target block.
156#[must_use]
157pub struct CommitVerifyIsParent<C> {
158 inner: CommitVerification<C>,
159 /// For performance reasons, the block number is copied here, but not the block hash. This
160 /// hasn't actually been benchmarked, so feel free to do so.
161 block_number: u64,
162}
163
164impl<C: AsRef<[u8]>> CommitVerifyIsParent<C> {
165 /// Height of the block to check.
166 pub fn block_number(&self) -> u64 {
167 self.block_number
168 }
169
170 /// Hash of the block to check.
171 pub fn block_hash(&self) -> &[u8; 32] {
172 debug_assert!(!self.inner.next_precommit_block_verified);
173 let decoded_commit = decode::decode_grandpa_commit(
174 self.inner.commit.as_ref(),
175 self.inner.block_number_bytes,
176 )
177 .unwrap();
178 decoded_commit.precommits[self.inner.next_precommit_index].target_hash
179 }
180
181 /// Height of the block that must be the ancestor of the block to check.
182 pub fn target_block_number(&self) -> u64 {
183 let decoded_commit = decode::decode_grandpa_commit(
184 self.inner.commit.as_ref(),
185 self.inner.block_number_bytes,
186 )
187 .unwrap();
188 decoded_commit.target_number
189 }
190
191 /// Hash of the block that must be the ancestor of the block to check.
192 pub fn target_block_hash(&self) -> &[u8; 32] {
193 let decoded_commit = decode::decode_grandpa_commit(
194 self.inner.commit.as_ref(),
195 self.inner.block_number_bytes,
196 )
197 .unwrap();
198 decoded_commit.target_hash
199 }
200
201 /// Resumes the verification process.
202 ///
203 /// Must be passed `Some(true)` if the block is known to be a descendant of the target block,
204 /// or `None` if it is unknown.
205 /// Passing `Some(false)` always returns [`CommitVerify::Finished`] containing an
206 /// error.
207 pub fn resume(mut self, is_parent: Option<bool>) -> CommitVerify<C> {
208 match is_parent {
209 None => {}
210 Some(true) => self.inner.num_verified_signatures += 1,
211 Some(false) => {
212 return CommitVerify::Finished(Err(CommitVerifyError::BadAncestry));
213 }
214 }
215
216 self.inner.next_precommit_block_verified = true;
217 self.inner.resume()
218 }
219}
220
221struct CommitVerification<C> {
222 /// Encoded commit message. Guaranteed to decode successfully.
223 commit: C,
224
225 /// See [`CommitVerifyConfig::block_number_bytes`].
226 block_number_bytes: usize,
227
228 /// Index of the next pre-commit to process within the commit.
229 next_precommit_index: usize,
230
231 /// Whether the precommit whose index is [`CommitVerification::next_precommit_index`] has been
232 /// verified as coming from the list of authorities.
233 next_precommit_author_verified: bool,
234
235 /// Whether the precommit whose index is [`CommitVerification::next_precommit_index`] has been
236 /// verified to be about a block that is a descendant of the target block.
237 next_precommit_block_verified: bool,
238
239 /// Number of signatures that have been pushed for verification. Needs to be above a certain
240 /// threshold for the commit to be valid.
241 num_verified_signatures: usize,
242
243 /// Number of authorities in the list. Used to calculate the threshold of the number of
244 /// required signatures.
245 num_authorities: u32,
246
247 /// Verifying all the signatures together brings better performances than verifying them one
248 /// by one.
249 /// Note that batched Ed25519 verification has some issues. The code below uses a special
250 /// flavor of Ed25519 where ambiguities are removed.
251 /// See <https://docs.rs/ed25519-zebra/2.2.0/ed25519_zebra/batch/index.html> and
252 /// <https://github.com/zcash/zips/blob/master/zip-0215.rst>
253 signatures_batch: ed25519_zebra::batch::Verifier,
254
255 /// Randomness generator used during the batch verification.
256 randomness: ChaCha20Rng,
257}
258
259impl<C: AsRef<[u8]>> CommitVerification<C> {
260 fn resume(mut self) -> CommitVerify<C> {
261 // The `verify` function that starts the verification performs the preliminary check that
262 // the commit has the correct format.
263 let decoded_commit =
264 decode::decode_grandpa_commit(self.commit.as_ref(), self.block_number_bytes).unwrap();
265
266 loop {
267 if let Some(precommit) = decoded_commit.precommits.get(self.next_precommit_index) {
268 if !self.next_precommit_author_verified {
269 return CommitVerify::IsAuthority(CommitVerifyIsAuthority { inner: self });
270 }
271
272 if !self.next_precommit_block_verified {
273 if precommit.target_hash == decoded_commit.target_hash
274 && precommit.target_number == decoded_commit.target_number
275 {
276 self.next_precommit_block_verified = true;
277 } else {
278 return CommitVerify::IsParent(CommitVerifyIsParent {
279 block_number: precommit.target_number,
280 inner: self,
281 });
282 }
283 }
284
285 let authority_public_key = decoded_commit.auth_data[self.next_precommit_index].1;
286 let signature = decoded_commit.auth_data[self.next_precommit_index].0;
287
288 let mut msg = Vec::with_capacity(1 + 32 + self.block_number_bytes + 8 + 8);
289 msg.push(1u8); // This `1` indicates which kind of message is being signed.
290 msg.extend_from_slice(&precommit.target_hash[..]);
291 // The message contains the little endian block number. While simple in concept,
292 // in reality it is more complicated because we don't know the number of bytes of
293 // this block number at compile time. We thus copy as many bytes as appropriate and
294 // pad with 0s if necessary.
295 msg.extend_from_slice(
296 &precommit.target_number.to_le_bytes()[..cmp::min(
297 mem::size_of_val(&precommit.target_number),
298 self.block_number_bytes,
299 )],
300 );
301 msg.extend(
302 iter::repeat(0).take(
303 self.block_number_bytes
304 .saturating_sub(mem::size_of_val(&precommit.target_number)),
305 ),
306 );
307 msg.extend_from_slice(&u64::to_le_bytes(decoded_commit.round_number)[..]);
308 msg.extend_from_slice(&u64::to_le_bytes(decoded_commit.set_id)[..]);
309 debug_assert_eq!(msg.len(), msg.capacity());
310
311 self.signatures_batch
312 .queue(ed25519_zebra::batch::Item::from((
313 ed25519_zebra::VerificationKeyBytes::from(*authority_public_key),
314 ed25519_zebra::Signature::from(*signature),
315 &msg,
316 )));
317
318 self.next_precommit_index += 1;
319 self.next_precommit_author_verified = false;
320 self.next_precommit_block_verified = false;
321 } else {
322 debug_assert!(!self.next_precommit_author_verified);
323 debug_assert!(!self.next_precommit_block_verified);
324
325 // Check that commit contains a number of signatures equal to at least 2/3rd of the
326 // number of authorities.
327 // Duplicate signatures are checked below.
328 // The logic of the check is `actual >= (expected * 2 / 3) + 1`.
329 if decoded_commit.precommits.len()
330 < (usize::try_from(self.num_authorities).unwrap() * 2 / 3) + 1
331 {
332 return CommitVerify::FinishedUnknown;
333 }
334
335 // Actual signatures verification performed here.
336 match self.signatures_batch.verify(&mut self.randomness) {
337 Ok(()) => {}
338 Err(_) => return CommitVerify::Finished(Err(CommitVerifyError::BadSignature)),
339 }
340
341 return CommitVerify::Finished(Ok(()));
342 }
343 }
344 }
345}
346
347/// Error that can happen while verifying a commit.
348#[derive(Debug, derive_more::Display, derive_more::Error)]
349pub enum CommitVerifyError {
350 /// Failed to decode the commit message.
351 InvalidFormat,
352 /// The authorities set id of the commit doesn't match the one that is expected.
353 BadSetId,
354 /// One of the public keys is invalid.
355 BadPublicKey,
356 /// One of the signatures can't be verified.
357 BadSignature,
358 /// One authority has produced two signatures.
359 #[display("One authority has produced two signatures")]
360 DuplicateSignature { authority_key: [u8; 32] },
361 /// One of the public keys isn't in the list of authorities.
362 #[display("One of the public keys isn't in the list of authorities")]
363 NotAuthority { authority_key: [u8; 32] },
364 /// Commit contains a vote for a block that isn't a descendant of the target block.
365 BadAncestry,
366}
367
368// TODO: tests
369
370/// Configuration for a justification verification process.
371#[derive(Debug)]
372pub struct JustificationVerifyConfig<J, I> {
373 /// Justification to verify.
374 pub justification: J,
375
376 pub block_number_bytes: usize,
377
378 // TODO: document
379 pub authorities_set_id: u64,
380
381 /// List of authorities that are allowed to emit pre-commits for the block referred to by
382 /// the justification. Must implement `Iterator<Item = &[u8]>`, where each item is
383 /// the public key of an authority.
384 pub authorities_list: I,
385
386 /// Seed for a PRNG used for various purposes during the verification.
387 ///
388 /// > **Note**: The verification is nonetheless deterministic.
389 pub randomness_seed: [u8; 32],
390}
391
392/// Verifies that a justification is valid.
393pub fn verify_justification<'a>(
394 config: JustificationVerifyConfig<impl AsRef<[u8]>, impl Iterator<Item = &'a [u8]>>,
395) -> Result<(), JustificationVerifyError> {
396 let decoded_justification = match decode::decode_grandpa_justification(
397 config.justification.as_ref(),
398 config.block_number_bytes,
399 ) {
400 Ok(c) => c,
401 Err(_) => return Err(JustificationVerifyError::InvalidFormat),
402 };
403
404 let num_precommits = decoded_justification.precommits.iter().count();
405
406 let mut randomness = ChaCha20Rng::from_seed(config.randomness_seed);
407
408 // Collect the authorities in a set in order to be able to determine with a low complexity
409 // whether a public key is an authority.
410 // For each authority, contains a boolean indicating whether the authority has been seen
411 // before in the list of pre-commits.
412 let mut authorities_list = {
413 let mut list = hashbrown::HashMap::<&[u8], _, _>::with_capacity_and_hasher(
414 0,
415 crate::util::SipHasherBuild::new({
416 let mut seed = [0; 16];
417 randomness.fill_bytes(&mut seed);
418 seed
419 }),
420 );
421 for authority in config.authorities_list {
422 list.insert(authority, false);
423 }
424 list
425 };
426
427 // Check that justification contains a number of signatures equal to at least 2/3rd of the
428 // number of authorities.
429 // Duplicate signatures are checked below.
430 // The logic of the check is `actual >= (expected * 2 / 3) + 1`.
431 if num_precommits < (authorities_list.len() * 2 / 3) + 1 {
432 return Err(JustificationVerifyError::NotEnoughSignatures);
433 }
434
435 // Verifying all the signatures together brings better performances than verifying them one
436 // by one.
437 // Note that batched ed25519 verification has some issues. The code below uses a special
438 // flavour of ed25519 where ambiguities are removed.
439 // See https://docs.rs/ed25519-zebra/2.2.0/ed25519_zebra/batch/index.html and
440 // https://github.com/zcash/zips/blob/master/zip-0215.rst
441 let mut batch = ed25519_zebra::batch::Verifier::new();
442
443 for precommit in decoded_justification.precommits.iter() {
444 match authorities_list.entry(precommit.authority_public_key) {
445 hashbrown::hash_map::Entry::Occupied(mut entry) => {
446 if entry.insert(true) {
447 return Err(JustificationVerifyError::DuplicateSignature {
448 authority_key: *precommit.authority_public_key,
449 });
450 }
451 }
452 hashbrown::hash_map::Entry::Vacant(_) => {
453 return Err(JustificationVerifyError::NotAuthority {
454 authority_key: *precommit.authority_public_key,
455 });
456 }
457 }
458
459 // TODO: must check signed block ancestry using `votes_ancestries`
460
461 let mut msg = Vec::with_capacity(1 + 32 + 4 + 8 + 8);
462 msg.push(1u8); // This `1` indicates which kind of message is being signed.
463 msg.extend_from_slice(&precommit.target_hash[..]);
464 // The message contains the little endian block number. While simple in concept,
465 // in reality it is more complicated because we don't know the number of bytes of
466 // this block number at compile time. We thus copy as many bytes as appropriate and
467 // pad with 0s if necessary.
468 msg.extend_from_slice(
469 &precommit.target_number.to_le_bytes()[..cmp::min(
470 mem::size_of_val(&precommit.target_number),
471 config.block_number_bytes,
472 )],
473 );
474 msg.extend(
475 iter::repeat(0).take(
476 config
477 .block_number_bytes
478 .saturating_sub(mem::size_of_val(&precommit.target_number)),
479 ),
480 );
481 msg.extend_from_slice(&u64::to_le_bytes(decoded_justification.round)[..]);
482 msg.extend_from_slice(&u64::to_le_bytes(config.authorities_set_id)[..]);
483 debug_assert_eq!(msg.len(), msg.capacity());
484
485 batch.queue(ed25519_zebra::batch::Item::from((
486 ed25519_zebra::VerificationKeyBytes::from(*precommit.authority_public_key),
487 ed25519_zebra::Signature::from(*precommit.signature),
488 &msg,
489 )));
490 }
491
492 // Actual signatures verification performed here.
493 batch
494 .verify(&mut randomness)
495 .map_err(|_| JustificationVerifyError::BadSignature)?;
496
497 // TODO: must check that votes_ancestries doesn't contain any unused entry
498 // TODO: there's also a "ghost" thing?
499
500 Ok(())
501}
502
503/// Error that can happen while verifying a justification.
504#[derive(Debug, derive_more::Display, derive_more::Error)]
505pub enum JustificationVerifyError {
506 /// Failed to decode the justification.
507 InvalidFormat,
508 /// One of the public keys is invalid.
509 BadPublicKey,
510 /// One of the signatures can't be verified.
511 BadSignature,
512 /// One authority has produced two signatures.
513 #[display("One authority has produced two signatures")]
514 DuplicateSignature { authority_key: [u8; 32] },
515 /// One of the public keys isn't in the list of authorities.
516 #[display("One of the public keys isn't in the list of authorities")]
517 NotAuthority { authority_key: [u8; 32] },
518 /// Justification doesn't contain enough authorities signatures to be valid.
519 NotEnoughSignatures,
520}