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