Skip to main content

iota_core/
signature_verifier.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{collections::BTreeSet, sync::Arc};
6
7use either::Either;
8use fastcrypto::traits::{AggregateAuthenticator, ToFromBytes};
9use futures::pin_mut;
10use iota_metrics::monitored_scope;
11use iota_sdk_types::{
12    CertificateDigest, SenderSignedDataDigest, SenderSignedTransaction, crypto::Intent,
13};
14use iota_types::{
15    base_types::AuthorityName,
16    committee::Committee,
17    crypto::{AuthorityPublicKey, AuthoritySignInfoTrait, VerificationObligation},
18    error::{IotaError, IotaResult},
19    message_envelope::Message,
20    messages_checkpoint::{CheckpointSummaryExt, SignedCheckpointSummary},
21    messages_consensus::{AuthorityCapabilitiesDigest, SignedAuthorityCapabilitiesV1},
22    signature::VerifyParams,
23    signature_verification::{VerifiedDigestCache, verify_sender_signed_data_message_signatures},
24    transaction::{CertifiedTransaction, VerifiedCertificate},
25};
26use itertools::{Itertools as _, izip};
27use parking_lot::{Mutex, MutexGuard};
28use prometheus_filtered::{IntCounter, Registry, register_int_counter_with_registry};
29use tap::TapFallible;
30use tokio::{
31    runtime::Handle,
32    sync::oneshot,
33    time::{Duration, timeout},
34};
35use tracing::{Instrument, instrument, trace_span};
36// Maximum amount of time we wait for a batch to fill up before verifying a
37// partial batch.
38const BATCH_TIMEOUT_MS: Duration = Duration::from_millis(10);
39
40// Maximum size of batch to verify. Increasing this value will slightly improve
41// CPU utilization (batching starts to hit steeply diminishing marginal returns
42// around batch sizes of 16), at the cost of slightly increasing latency
43// (BATCH_TIMEOUT_MS will be hit more frequently if system is not heavily
44// loaded).
45const MAX_BATCH_SIZE: usize = 8;
46
47type Sender = oneshot::Sender<IotaResult<VerifiedCertificate>>;
48
49struct CertBuffer {
50    certs: Vec<CertifiedTransaction>,
51    senders: Vec<Sender>,
52    id: u64,
53}
54
55impl CertBuffer {
56    fn new(capacity: usize) -> Self {
57        Self {
58            certs: Vec::with_capacity(capacity),
59            senders: Vec::with_capacity(capacity),
60            id: 0,
61        }
62    }
63
64    // Function consumes MutexGuard, therefore releasing the lock after mem swap is
65    // done
66    fn take_and_replace(mut guard: MutexGuard<'_, Self>) -> Self {
67        let this = &mut *guard;
68        let mut new = CertBuffer::new(this.capacity());
69        new.id = this.id + 1;
70        std::mem::swap(&mut new, this);
71        new
72    }
73
74    fn capacity(&self) -> usize {
75        debug_assert_eq!(self.certs.capacity(), self.senders.capacity());
76        self.certs.capacity()
77    }
78
79    fn len(&self) -> usize {
80        debug_assert_eq!(self.certs.len(), self.senders.len());
81        self.certs.len()
82    }
83
84    fn push(&mut self, tx: Sender, cert: CertifiedTransaction) {
85        self.senders.push(tx);
86        self.certs.push(cert);
87    }
88}
89
90/// Verifies signatures in ways that faster than verifying each signature
91/// individually.
92/// - BLS signatures - caching and batch verification.
93/// - User signed data - caching.
94pub struct SignatureVerifier {
95    committee: Arc<Committee>,
96    non_committee_validators: BTreeSet<AuthorityName>,
97
98    certificate_cache: VerifiedDigestCache<CertificateDigest>,
99    signed_data_cache: VerifiedDigestCache<SenderSignedDataDigest>,
100    authority_capability_cache: VerifiedDigestCache<AuthorityCapabilitiesDigest>,
101
102    /// Params for signature verification.
103    verify_params: VerifyParams,
104
105    queue: Mutex<CertBuffer>,
106    pub metrics: Arc<SignatureVerifierMetrics>,
107}
108
109impl SignatureVerifier {
110    pub fn new_with_batch_size(
111        committee: Arc<Committee>,
112        non_committee_validators: BTreeSet<AuthorityName>,
113        batch_size: usize,
114        metrics: Arc<SignatureVerifierMetrics>,
115        accept_passkey_in_multisig: bool,
116        additional_multisig_checks: bool,
117    ) -> Self {
118        Self {
119            committee,
120            non_committee_validators,
121            certificate_cache: VerifiedDigestCache::new(
122                metrics.certificate_signatures_cache_hits.clone(),
123                metrics.certificate_signatures_cache_misses.clone(),
124                metrics.certificate_signatures_cache_evictions.clone(),
125            ),
126            signed_data_cache: VerifiedDigestCache::new(
127                metrics.signed_data_cache_hits.clone(),
128                metrics.signed_data_cache_misses.clone(),
129                metrics.signed_data_cache_evictions.clone(),
130            ),
131            authority_capability_cache: VerifiedDigestCache::new(
132                metrics.authority_capabilities_cache_hits.clone(),
133                metrics.authority_capabilities_cache_misses.clone(),
134                metrics.authority_capabilities_cache_evictions.clone(),
135            ),
136            queue: Mutex::new(CertBuffer::new(batch_size)),
137            metrics,
138            verify_params: VerifyParams::new(
139                accept_passkey_in_multisig,
140                additional_multisig_checks,
141            ),
142        }
143    }
144
145    pub fn new(
146        committee: Arc<Committee>,
147        non_committee_validators: BTreeSet<AuthorityName>,
148        metrics: Arc<SignatureVerifierMetrics>,
149        accept_passkey_in_multisig: bool,
150        additional_multisig_checks: bool,
151    ) -> Self {
152        Self::new_with_batch_size(
153            committee,
154            non_committee_validators,
155            MAX_BATCH_SIZE,
156            metrics,
157            accept_passkey_in_multisig,
158            additional_multisig_checks,
159        )
160    }
161
162    /// Verifies all certs, returns Ok only if all are valid.
163    #[instrument(level = "trace", skip_all)]
164    pub fn verify_certs_and_checkpoints(
165        &self,
166        certs: Vec<&CertifiedTransaction>,
167        checkpoints: Vec<&SignedCheckpointSummary>,
168        authority_capabilities: Vec<&SignedAuthorityCapabilitiesV1>,
169    ) -> IotaResult {
170        // Verify all user sigs, since caching is handled by the underlying
171        // implementation.
172        for cert in &certs {
173            self.verify_tx(cert.data())?;
174        }
175
176        // Verify authority capabilities signatures. Caching is handled inside to avoid
177        // checking the same message multiple times.
178        for cap in &authority_capabilities {
179            self.verify_authority_capabilities(cap)?;
180        }
181
182        batch_verify_all_certificates_and_checkpoints(&self.committee, &certs, &checkpoints)?;
183        Ok(())
184    }
185
186    /// Verifies one cert asynchronously, in a batch.
187    pub async fn verify_cert(&self, cert: CertifiedTransaction) -> IotaResult<VerifiedCertificate> {
188        let cert_digest = cert.certificate_digest();
189        if self.certificate_cache.is_cached(&cert_digest) {
190            return Ok(VerifiedCertificate::new_unchecked(cert));
191        }
192        self.verify_tx(cert.data())?;
193        self.verify_cert_skip_cache(cert)
194            .await
195            .tap_ok(|_| self.certificate_cache.cache_digest(cert_digest))
196    }
197
198    pub async fn multi_verify_certs(
199        &self,
200        certs: Vec<CertifiedTransaction>,
201    ) -> Vec<IotaResult<VerifiedCertificate>> {
202        // TODO: We could do better by pushing the all of `certs` into the verification
203        // queue at once, but that's significantly more complex.
204        let mut futures = Vec::with_capacity(certs.len());
205        for cert in certs {
206            futures.push(self.verify_cert(cert));
207        }
208        futures::future::join_all(futures).await
209    }
210
211    /// exposed as a public method for the benchmarks
212    pub async fn verify_cert_skip_cache(
213        &self,
214        cert: CertifiedTransaction,
215    ) -> IotaResult<VerifiedCertificate> {
216        // this is the only innocent error we are likely to encounter - filter it before
217        // we poison a whole batch.
218        if cert.auth_sig().epoch != self.committee.epoch() {
219            return Err(IotaError::WrongEpoch {
220                expected_epoch: self.committee.epoch(),
221                actual_epoch: cert.auth_sig().epoch,
222            });
223        }
224
225        self.verify_cert_inner(cert).await
226    }
227
228    async fn verify_cert_inner(
229        &self,
230        cert: CertifiedTransaction,
231    ) -> IotaResult<VerifiedCertificate> {
232        // Cancellation safety: we use parking_lot locks, which cannot be held across
233        // awaits. Therefore once the queue has been taken by a thread, it is
234        // guaranteed to process the queue and send all results before the
235        // future can be cancelled by the caller.
236        let (tx, rx) = oneshot::channel();
237        pin_mut!(rx);
238
239        let prev_id_or_buffer = {
240            let mut queue = self.queue.lock();
241            queue.push(tx, cert);
242            if queue.len() == queue.capacity() {
243                Either::Right(CertBuffer::take_and_replace(queue))
244            } else {
245                Either::Left(queue.id)
246            }
247        };
248        let prev_id = match prev_id_or_buffer {
249            Either::Left(prev_id) => prev_id,
250            Either::Right(buffer) => {
251                self.metrics.full_batches.inc();
252                self.process_queue(buffer)
253                    .instrument(trace_span!("SignatureVerifier::process_queue"))
254                    .await;
255                // unwrap ok - process_queue will have sent the result already
256                return rx.try_recv().unwrap();
257            }
258        };
259
260        if let Ok(res) = timeout(BATCH_TIMEOUT_MS, &mut rx).await {
261            // unwrap ok - tx cannot have been dropped without sending a result.
262            return res.unwrap();
263        }
264        self.metrics.timeouts.inc();
265
266        let buffer = {
267            let queue = self.queue.lock();
268            // check if another thread took the queue while we were re-acquiring lock.
269            if prev_id == queue.id {
270                debug_assert_ne!(queue.len(), queue.capacity());
271                Some(CertBuffer::take_and_replace(queue))
272            } else {
273                None
274            }
275        };
276
277        if let Some(buffer) = buffer {
278            self.metrics.partial_batches.inc();
279            self.process_queue(buffer).await;
280            // unwrap ok - process_queue will have sent the result already
281            return rx.try_recv().unwrap();
282        }
283
284        // unwrap ok - another thread took the queue while we were re-acquiring the lock
285        // and is guaranteed to process the queue immediately.
286        rx.await.unwrap()
287    }
288
289    async fn process_queue(&self, buffer: CertBuffer) {
290        let committee = self.committee.clone();
291        let metrics = self.metrics.clone();
292        Handle::current()
293            .spawn_blocking(move || Self::process_queue_sync(committee, metrics, buffer))
294            .await
295            .expect("Spawn blocking should not fail");
296    }
297
298    #[instrument(level = "trace", skip_all)]
299    fn process_queue_sync(
300        committee: Arc<Committee>,
301        metrics: Arc<SignatureVerifierMetrics>,
302        buffer: CertBuffer,
303    ) {
304        let _scope = monitored_scope("BatchCertificateVerifier::process_queue");
305
306        let results = batch_verify_certificates(&committee, &buffer.certs.iter().collect_vec());
307        izip!(
308            results.into_iter(),
309            buffer.certs.into_iter(),
310            buffer.senders.into_iter(),
311        )
312        .for_each(|(result, cert, tx)| {
313            tx.send(match result {
314                Ok(()) => {
315                    metrics.total_verified_certs.inc();
316                    Ok(VerifiedCertificate::new_unchecked(cert))
317                }
318                Err(e) => {
319                    metrics.total_failed_certs.inc();
320                    Err(e)
321                }
322            })
323            .ok();
324        });
325    }
326
327    #[instrument(level = "trace", skip_all, fields(tx_digest = ?signed_tx.digest()))]
328    pub fn verify_tx(&self, signed_tx: &SenderSignedTransaction) -> IotaResult {
329        self.signed_data_cache.is_verified(
330            signed_tx.full_message_digest(),
331            || verify_sender_signed_data_message_signatures(signed_tx, &self.verify_params),
332            || Ok(()),
333        )
334    }
335
336    #[instrument(level = "trace", skip_all)]
337    pub fn verify_authority_capabilities(
338        &self,
339        signed_authority_capabilities: &SignedAuthorityCapabilitiesV1,
340    ) -> IotaResult {
341        let epoch = self.committee.epoch();
342        self.authority_capability_cache.is_verified(
343            signed_authority_capabilities.cache_digest(epoch),
344            || {
345                // Check if authority exists in non-committee validators
346                let authority_name = signed_authority_capabilities.data().authority;
347                if !self.non_committee_validators.contains(&authority_name) {
348                    return Err(IotaError::IncorrectSigner {
349                        error: "Signer must be part of non-committee active validators".to_string(),
350                    });
351                }
352
353                // Create a verification obligation
354                let mut obligation = VerificationObligation::default();
355                let idx = obligation.add_message(
356                    signed_authority_capabilities.data(),
357                    epoch, /* epoch is shared between the committee and
358                            * non-committee validators */
359                    Intent::iota_app(signed_authority_capabilities.scope()),
360                );
361
362                // Add the signature and public key to the obligation
363                let authority_key = AuthorityPublicKey::from_bytes(authority_name.as_bytes())
364                    .map_err(|_| IotaError::IncorrectSigner {
365                        error: "Invalid authority public key bytes".to_string(),
366                    })?;
367                obligation
368                    .public_keys
369                    .get_mut(idx)
370                    .ok_or(IotaError::InvalidAuthenticator)?
371                    .push(&authority_key);
372
373                obligation
374                    .signatures
375                    .get_mut(idx)
376                    .ok_or(IotaError::InvalidAuthenticator)?
377                    .add_signature(signed_authority_capabilities.auth_sig().clone())
378                    .map_err(|_| IotaError::InvalidSignature {
379                        error: "Failed to add authority signature to obligation".to_string(),
380                    })?;
381
382                obligation.verify_all()
383            },
384            || Ok(()),
385        )
386    }
387
388    pub fn clear_signature_cache(&self) {
389        self.certificate_cache.clear();
390        self.authority_capability_cache.clear();
391        self.signed_data_cache.clear();
392    }
393}
394
395pub struct SignatureVerifierMetrics {
396    pub certificate_signatures_cache_hits: IntCounter,
397    pub certificate_signatures_cache_misses: IntCounter,
398    pub certificate_signatures_cache_evictions: IntCounter,
399    pub signed_data_cache_hits: IntCounter,
400    pub signed_data_cache_misses: IntCounter,
401    pub signed_data_cache_evictions: IntCounter,
402    pub authority_capabilities_cache_hits: IntCounter,
403    pub authority_capabilities_cache_misses: IntCounter,
404    pub authority_capabilities_cache_evictions: IntCounter,
405    timeouts: IntCounter,
406    full_batches: IntCounter,
407    partial_batches: IntCounter,
408    total_verified_certs: IntCounter,
409    total_failed_certs: IntCounter,
410}
411
412impl SignatureVerifierMetrics {
413    pub fn new(registry: &Registry) -> Arc<Self> {
414        Arc::new(Self {
415            certificate_signatures_cache_hits: register_int_counter_with_registry!(
416                "certificate_signatures_cache_hits",
417                "Number of certificates which were known to be verified because of signature cache.",
418                registry
419            )
420            .unwrap(),
421            certificate_signatures_cache_misses: register_int_counter_with_registry!(
422                "certificate_signatures_cache_misses",
423                "Number of certificates which missed the signature cache",
424                registry
425            )
426            .unwrap(),
427            certificate_signatures_cache_evictions: register_int_counter_with_registry!(
428                "certificate_signatures_cache_evictions",
429                "Number of times we evict a pre-existing key were known to be verified because of signature cache.",
430                registry
431            )
432            .unwrap(),
433            signed_data_cache_hits: register_int_counter_with_registry!(
434                "signed_data_cache_hits",
435                "Number of signed data which were known to be verified because of signature cache.",
436                registry
437            )
438            .unwrap(),
439            signed_data_cache_misses: register_int_counter_with_registry!(
440                "signed_data_cache_misses",
441                "Number of signed data which missed the signature cache.",
442                registry
443            )
444            .unwrap(),
445            signed_data_cache_evictions: register_int_counter_with_registry!(
446                "signed_data_cache_evictions",
447                "Number of times we evict a pre-existing signed data were known to be verified because of signature cache.",
448                registry
449            )
450            .unwrap(),
451            authority_capabilities_cache_hits: register_int_counter_with_registry!(
452                "authority_capabilities_cache_hits",
453                "Number of authority capabilities which were known to be verified because of capabilities cache.",
454                registry
455            )
456            .unwrap(),
457            authority_capabilities_cache_misses: register_int_counter_with_registry!(
458                "authority_capabilities_cache_misses",
459                "Number of authority capabilities which missed the capabilities cache.",
460                registry
461            )
462            .unwrap(),
463            authority_capabilities_cache_evictions: register_int_counter_with_registry!(
464                "authority_capabilities_cache_evictions",
465                "Number of times we evict a pre-existing authority capabilities that were known to be verified.",
466                registry
467            )
468            .unwrap(),
469            timeouts: register_int_counter_with_registry!(
470                "async_batch_verifier_timeouts",
471                "Number of times batch verifier times out and verifies a partial batch",
472                registry
473            )
474            .unwrap(),
475            full_batches: register_int_counter_with_registry!(
476                "async_batch_verifier_full_batches",
477                "Number of times batch verifier verifies a full batch",
478                registry
479            )
480            .unwrap(),
481            partial_batches: register_int_counter_with_registry!(
482                "async_batch_verifier_partial_batches",
483                "Number of times batch verifier verifies a partial batch",
484                registry
485            )
486            .unwrap(),
487            total_verified_certs: register_int_counter_with_registry!(
488                "async_batch_verifier_total_verified_certs",
489                "Total number of certs batch verifier has verified",
490                registry
491            )
492            .unwrap(),
493            total_failed_certs: register_int_counter_with_registry!(
494                "async_batch_verifier_total_failed_certs",
495                "Total number of certs batch verifier has rejected",
496                registry
497            )
498            .unwrap(),
499        })
500    }
501}
502
503/// Verifies all certificates - if any fail return error.
504#[instrument(level = "trace", skip_all)]
505pub fn batch_verify_all_certificates_and_checkpoints(
506    committee: &Committee,
507    certs: &[&CertifiedTransaction],
508    checkpoints: &[&SignedCheckpointSummary],
509) -> IotaResult {
510    // certs.data() is assumed to be verified already by the caller.
511
512    for ckpt in checkpoints {
513        ckpt.data().verify_epoch(committee.epoch())?;
514    }
515
516    batch_verify(committee, certs, checkpoints)
517}
518
519/// Verifies certificates in batch mode, but returns a separate result for each
520/// cert.
521#[instrument(level = "trace", skip_all)]
522pub fn batch_verify_certificates(
523    committee: &Committee,
524    certs: &[&CertifiedTransaction],
525) -> Vec<IotaResult> {
526    // certs.data() is assumed to be verified already by the caller.
527    let verify_params = VerifyParams::default();
528    match batch_verify(committee, certs, &[]) {
529        Ok(_) => vec![Ok(()); certs.len()],
530
531        // Verify one by one to find which certs were invalid.
532        Err(_) if certs.len() > 1 => certs
533            .iter()
534            // TODO: verify_signature currently checks the tx sig as well, which might be cached
535            // already.
536            .map(|c| c.verify_signatures_authenticated(committee, &verify_params))
537            .collect(),
538
539        Err(e) => vec![Err(e)],
540    }
541}
542
543fn batch_verify(
544    committee: &Committee,
545    certs: &[&CertifiedTransaction],
546    checkpoints: &[&SignedCheckpointSummary],
547) -> IotaResult {
548    let mut obligation = VerificationObligation::default();
549
550    for cert in certs {
551        let idx = obligation.add_message(cert.data(), cert.epoch(), Intent::iota_app(cert.scope()));
552        cert.auth_sig()
553            .add_to_verification_obligation(committee, &mut obligation, idx)?;
554    }
555
556    for ckpt in checkpoints {
557        let idx = obligation.add_message(ckpt.data(), ckpt.epoch(), Intent::iota_app(ckpt.scope()));
558        ckpt.auth_sig()
559            .add_to_verification_obligation(committee, &mut obligation, idx)?;
560    }
561
562    obligation.verify_all()
563}