sui_types/
signature_verification.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use nonempty::NonEmpty;
5use shared_crypto::intent::Intent;
6
7use crate::base_types::SuiAddress;
8use crate::committee::EpochId;
9use crate::digests::ZKLoginInputsDigest;
10use crate::error::{SuiErrorKind, SuiResult};
11use crate::signature::VerifyParams;
12use crate::transaction::{SenderSignedData, TransactionDataAPI};
13use lru::LruCache;
14use parking_lot::RwLock;
15use prometheus::IntCounter;
16use std::hash::Hash;
17use std::sync::Arc;
18
19// Cache up to this many verified certs. We will need to tune this number in the future - a decent
20// guess to start with is that it should be 10-20 times larger than peak transactions per second,
21// on the assumption that we should see most certs twice within about 10-20 seconds at most:
22// Once via RPC, once via consensus.
23const VERIFIED_CERTIFICATE_CACHE_SIZE: usize = 100_000;
24
25pub struct VerifiedDigestCache<D, V = ()> {
26    inner: RwLock<LruCache<D, V>>,
27    cache_hits_counter: IntCounter,
28    cache_misses_counter: IntCounter,
29    cache_evictions_counter: IntCounter,
30}
31
32impl<D: Hash + Eq + Copy, V: Clone> VerifiedDigestCache<D, V> {
33    pub fn new(
34        cache_hits_counter: IntCounter,
35        cache_misses_counter: IntCounter,
36        cache_evictions_counter: IntCounter,
37    ) -> Self {
38        Self {
39            inner: RwLock::new(LruCache::new(
40                std::num::NonZeroUsize::new(VERIFIED_CERTIFICATE_CACHE_SIZE).unwrap(),
41            )),
42            cache_hits_counter,
43            cache_misses_counter,
44            cache_evictions_counter,
45        }
46    }
47
48    pub fn is_cached(&self, digest: &D) -> bool {
49        let inner = self.inner.read();
50        if inner.contains(digest) {
51            self.cache_hits_counter.inc();
52            true
53        } else {
54            self.cache_misses_counter.inc();
55            false
56        }
57    }
58
59    /// Returns the cached value for the given digest, if present.
60    pub fn get_cached(&self, digest: &D) -> Option<V> {
61        let inner = self.inner.read();
62        if let Some(value) = inner.peek(digest) {
63            self.cache_hits_counter.inc();
64            Some(value.clone())
65        } else {
66            self.cache_misses_counter.inc();
67            None
68        }
69    }
70
71    pub fn cache_with_value(&self, digest: D, value: V) {
72        let mut inner = self.inner.write();
73        if let Some(old) = inner.push(digest, value)
74            && old.0 != digest
75        {
76            self.cache_evictions_counter.inc();
77        }
78    }
79
80    pub fn clear(&self) {
81        let mut inner = self.inner.write();
82        inner.clear();
83    }
84
85    // Initialize an empty cache when the cache is not needed (in testing scenarios, graphql and rosetta initialization).
86    pub fn new_empty() -> Self {
87        Self::new(
88            IntCounter::new("test_cache_hits", "test cache hits").unwrap(),
89            IntCounter::new("test_cache_misses", "test cache misses").unwrap(),
90            IntCounter::new("test_cache_evictions", "test cache evictions").unwrap(),
91        )
92    }
93}
94
95impl<D: Hash + Eq + Copy> VerifiedDigestCache<D, ()> {
96    pub fn cache_digest(&self, digest: D) {
97        self.cache_with_value(digest, ())
98    }
99
100    pub fn cache_digests(&self, digests: Vec<D>) {
101        let mut inner = self.inner.write();
102        digests.into_iter().for_each(|d| {
103            if let Some(old) = inner.push(d, ())
104                && old.0 != d
105            {
106                self.cache_evictions_counter.inc();
107            }
108        });
109    }
110
111    pub fn is_verified<F, G>(&self, digest: D, verify_callback: F, uncached_checks: G) -> SuiResult
112    where
113        F: FnOnce() -> SuiResult,
114        G: FnOnce() -> SuiResult,
115    {
116        if !self.is_cached(&digest) {
117            verify_callback()?;
118            self.cache_digest(digest);
119        } else {
120            // Checks that are required to be performed outside the cache.
121            uncached_checks()?;
122        }
123        Ok(())
124    }
125}
126
127/// Does crypto validation for a transaction which may be user-provided, or may be from a checkpoint.
128/// Returns the signature index (into `tx_signatures`) used to verify each required signer,
129/// in the same order as `required_signers`.
130pub fn verify_sender_signed_data_message_signatures(
131    txn: &SenderSignedData,
132    current_epoch: EpochId,
133    verify_params: &VerifyParams,
134    zklogin_inputs_cache: Arc<VerifiedDigestCache<ZKLoginInputsDigest>>,
135    aliased_addresses: Vec<(SuiAddress, NonEmpty<SuiAddress>)>,
136) -> SuiResult<Vec<u8>> {
137    let intent_message = txn.intent_message();
138    assert_eq!(intent_message.intent, Intent::sui_transaction());
139
140    // 1. One signature per signer is required.
141    let required_signers = txn.intent_message().value.required_signers();
142    fp_ensure!(
143        txn.inner().tx_signatures.len() == required_signers.len(),
144        SuiErrorKind::SignerSignatureNumberMismatch {
145            actual: txn.inner().tx_signatures.len(),
146            expected: required_signers.len()
147        }
148        .into()
149    );
150
151    // 2. System transactions do not require valid signatures. User-submitted transactions are
152    // verified not to be system transactions before this point.
153    if intent_message.value.is_system_tx() {
154        // System tx are defined to use all of the dummy signatures provided.
155        return Ok((0..required_signers.len() as u8).collect());
156    }
157
158    // 3. Each signer must provide a signature from one of the set of allowed aliases.
159    // Use index mapping to track which signature index satisfies each required signer.
160    let sig_mapping = txn.get_signer_sig_mapping(verify_params.verify_legacy_zklogin_address)?;
161
162    let mut signer_to_sig_index = Vec::with_capacity(required_signers.len());
163    for signer in required_signers.iter() {
164        let alias_set = aliased_addresses
165            .iter()
166            .find(|(addr, _)| *addr == *signer)
167            .map(|(_, aliases)| aliases.clone())
168            .unwrap_or(NonEmpty::new(*signer));
169
170        // Find the signature that matches any alias for this signer.
171        let Some(sig_index) = alias_set
172            .iter()
173            .find_map(|alias| sig_mapping.get(alias).map(|(idx, _)| *idx))
174        else {
175            return Err(SuiErrorKind::SignerSignatureAbsent {
176                expected: alias_set
177                    .iter()
178                    .map(|s| s.to_string())
179                    .collect::<Vec<_>>()
180                    .join(" or "),
181                actual: sig_mapping.keys().map(|s| s.to_string()).collect(),
182            }
183            .into());
184        };
185        signer_to_sig_index.push(sig_index);
186    }
187
188    // 4. Every signature must be valid.
189    for (signer, (_, signature)) in sig_mapping {
190        signature.verify_authenticator(
191            intent_message,
192            signer,
193            current_epoch,
194            verify_params,
195            zklogin_inputs_cache.clone(),
196        )?;
197    }
198    Ok(signer_to_sig_index)
199}