iota_types/
signature_verification.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{hash::Hash, sync::Arc};
6
7use lru::LruCache;
8use nonempty::NonEmpty;
9use parking_lot::RwLock;
10use prometheus::IntCounter;
11use shared_crypto::intent::Intent;
12
13use crate::{
14    committee::EpochId,
15    digests::ZKLoginInputsDigest,
16    error::{IotaError, IotaResult},
17    signature::VerifyParams,
18    transaction::{SenderSignedData, TransactionDataAPI},
19};
20
21// Cache up to 20000 verified certs. We will need to tune this number in the
22// future - a decent guess to start with is that it should be 10-20 times larger
23// than peak transactions per second, on the assumption that we should see most
24// certs twice within about 10-20 seconds at most: Once via RPC, once via
25// consensus.
26const VERIFIED_CERTIFICATE_CACHE_SIZE: usize = 20000;
27
28pub struct VerifiedDigestCache<D> {
29    inner: RwLock<LruCache<D, ()>>,
30    cache_hits_counter: IntCounter,
31    cache_misses_counter: IntCounter,
32    cache_evictions_counter: IntCounter,
33}
34
35impl<D: Hash + Eq + Copy> VerifiedDigestCache<D> {
36    pub fn new(
37        cache_hits_counter: IntCounter,
38        cache_misses_counter: IntCounter,
39        cache_evictions_counter: IntCounter,
40    ) -> Self {
41        Self {
42            inner: RwLock::new(LruCache::new(
43                std::num::NonZeroUsize::new(VERIFIED_CERTIFICATE_CACHE_SIZE).unwrap(),
44            )),
45            cache_hits_counter,
46            cache_misses_counter,
47            cache_evictions_counter,
48        }
49    }
50
51    pub fn is_cached(&self, digest: &D) -> bool {
52        let inner = self.inner.read();
53        if inner.contains(digest) {
54            self.cache_hits_counter.inc();
55            true
56        } else {
57            self.cache_misses_counter.inc();
58            false
59        }
60    }
61
62    pub fn cache_digest(&self, digest: D) {
63        let mut inner = self.inner.write();
64        if let Some(old) = inner.push(digest, ()) {
65            if old.0 != digest {
66                self.cache_evictions_counter.inc();
67            }
68        }
69    }
70
71    pub fn cache_digests(&self, digests: Vec<D>) {
72        let mut inner = self.inner.write();
73        digests.into_iter().for_each(|d| {
74            if let Some(old) = inner.push(d, ()) {
75                if old.0 != d {
76                    self.cache_evictions_counter.inc();
77                }
78            }
79        });
80    }
81
82    pub fn is_verified<F, G>(&self, digest: D, verify_callback: F, uncached_checks: G) -> IotaResult
83    where
84        F: FnOnce() -> IotaResult,
85        G: FnOnce() -> IotaResult,
86    {
87        if !self.is_cached(&digest) {
88            verify_callback()?;
89            self.cache_digest(digest);
90        } else {
91            // Checks that are required to be performed outside the cache.
92            uncached_checks()?;
93        }
94        Ok(())
95    }
96
97    pub fn clear(&self) {
98        let mut inner = self.inner.write();
99        inner.clear();
100    }
101
102    // Initialize an empty cache when the cache is not needed (in testing scenarios,
103    // graphql and rosetta initialization).
104    pub fn new_empty() -> Self {
105        Self::new(
106            IntCounter::new("test_cache_hits", "test cache hits").unwrap(),
107            IntCounter::new("test_cache_misses", "test cache misses").unwrap(),
108            IntCounter::new("test_cache_evictions", "test cache evictions").unwrap(),
109        )
110    }
111}
112
113/// Does crypto validation for a transaction which may be user-provided, or may
114/// be from a checkpoint.
115pub fn verify_sender_signed_data_message_signatures(
116    txn: &SenderSignedData,
117    current_epoch: EpochId,
118    verify_params: &VerifyParams,
119    zklogin_inputs_cache: Arc<VerifiedDigestCache<ZKLoginInputsDigest>>,
120) -> IotaResult {
121    let intent_message = txn.intent_message();
122    assert_eq!(intent_message.intent, Intent::iota_transaction());
123
124    // 1. System transactions do not require signatures. User-submitted transactions
125    //    are verified not to
126    // be system transactions before this point
127    if intent_message.value.is_system_tx() {
128        return Ok(());
129    }
130
131    // 2. One signature per signer is required.
132    let signers: NonEmpty<_> = txn.intent_message().value.signers();
133    fp_ensure!(
134        txn.inner().tx_signatures.len() == signers.len(),
135        IotaError::SignerSignatureNumberMismatch {
136            actual: txn.inner().tx_signatures.len(),
137            expected: signers.len()
138        }
139    );
140
141    // 3. Each signer must provide a signature.
142    let present_sigs = txn.get_signer_sig_mapping()?;
143    for s in signers {
144        if !present_sigs.contains_key(&s) {
145            return Err(IotaError::SignerSignatureAbsent {
146                expected: s.to_string(),
147                actual: present_sigs.keys().map(|s| s.to_string()).collect(),
148            });
149        }
150    }
151
152    // 4. Every signature must be valid.
153    for (signer, signature) in present_sigs {
154        signature.verify_authenticator(
155            intent_message,
156            signer,
157            current_epoch,
158            verify_params,
159            zklogin_inputs_cache.clone(),
160        )?;
161    }
162    Ok(())
163}