tezos_smart_rollup_encoding/dac/
certificate.rs

1// SPDX-FileCopyrightText: 2022-2023 TriliTech <contact@trili.tech>
2// SPDX-FileCopyrightText: 2023 Marigold <contact@marigold.dev>
3//
4// SPDX-License-Identifier: MIT
5
6//! Encoding of Dac certificates. Dac certificates are versioned: the
7//! first byte in a serialized Dac certificate is indicative of the
8//! version of the certificate itself.
9
10// TODO: <https://github.com/trilitech/tezedge/issues/17>
11//       lint triggered by issue in encoding macro
12#![allow(clippy::useless_format)]
13#![cfg(feature = "alloc")]
14
15use super::PreimageHash;
16use super::SlicePage;
17use super::SlicePageError;
18use super::V0SliceContentPage;
19use super::V0SliceHashPage;
20use super::MAX_PAGE_SIZE;
21use tezos_crypto_rs::hash::BlsSignature;
22#[cfg(feature = "bls")]
23use tezos_crypto_rs::hash::PublicKeyBls;
24use tezos_crypto_rs::CryptoError;
25use tezos_data_encoding::enc::BinWriter;
26use tezos_data_encoding::encoding::HasEncoding;
27use tezos_data_encoding::nom::NomReader;
28use tezos_data_encoding::types::Zarith;
29use tezos_smart_rollup_core::PREIMAGE_HASH_SIZE;
30use tezos_smart_rollup_host::path::Path;
31use tezos_smart_rollup_host::runtime::Runtime;
32use tezos_smart_rollup_host::runtime::RuntimeError;
33use thiserror::Error;
34
35/// Errors that can be obtained when handling a certificate.
36#[derive(Debug, Error)]
37pub enum CertificateError {
38    /// Number of signatures is lower than the required threshold
39    #[error(
40        "Insufficient number of signatures - threshold: {threshold}, actual: {actual}"
41    )]
42    InsufficientNumberOfSignatures {
43        /// the threshold of signatures required for the certificate to be valid
44        threshold: usize,
45        /// the number of signatures provided by the certificate
46        actual: usize,
47    },
48    /// Cryptographic primitives result in error while verifying the signature
49    #[error("Error propagated by cryptographic primitives while verifying the aggregate signature: {0}")]
50    SignatureVerificationFailed(CryptoError),
51    /// Signature Verification failed
52    #[error("Verification of aggregate signature failed")]
53    InvalidAggregateSignature,
54    /// Storage error when revealing a certificate
55    #[error("Failed to write certificate contents to {0}:{1}")]
56    StorageError(String, RuntimeError),
57    /// Error occurred revealing page of content
58    #[error("Could not reveal content of {0:?}:{1}")]
59    RevealError(PreimageHash, RuntimeError),
60    /// Revealed page has invalid encoding
61    #[error("Revealed page is invalid: {0:?}")]
62    PageError(SlicePageError),
63    /// Revealed content is/would be larger than 10,063,860 bytes
64    #[error("Payload too large")]
65    PayloadTooLarge,
66}
67
68impl From<SlicePageError> for CertificateError {
69    fn from(e: SlicePageError) -> Self {
70        Self::PageError(e)
71    }
72}
73
74/// DAC certificates are signed by committee members to ensure validity.
75///
76/// Currently only one certificate type is supported, but more will be added in future.
77#[derive(Debug, HasEncoding, NomReader, BinWriter)]
78#[encoding(tags = "u8")]
79pub enum Certificate {
80    /// A V0 certificate - see [`V0Certificate`]
81    #[encoding(tag = 0)]
82    V0(V0Certificate),
83}
84
85/// A Dac V0 certificate.
86#[derive(Debug, HasEncoding, NomReader, BinWriter)]
87pub struct V0Certificate {
88    /// The preimage hash of the root [`V0HashPage`].
89    ///
90    /// [`V0HashPage`]: crate::dac::pages::V0HashPage
91    pub root_hash: PreimageHash,
92    /// Aggregated signature of the DAC committee.
93    pub aggregated_signature: BlsSignature,
94    /// Data_encoding.Bit_set.t is actually a Z.t
95    pub witnesses: Zarith,
96}
97
98impl Certificate {
99    /// Verifies that a certificate is valid.
100    ///
101    /// For a [`V0Certificate`], the aggregated signature is verified against the public keys
102    /// of the committee members that have signed the root hash.
103    /// This set is determined by the witness field of the certificate.
104    #[cfg(feature = "bls")]
105    pub fn verify(
106        &self,
107        committee_members_pks: &[PublicKeyBls],
108        threshold: u8,
109    ) -> Result<(), CertificateError> {
110        match self {
111            Certificate::V0(V0Certificate {
112                root_hash,
113                aggregated_signature,
114                witnesses,
115            }) => {
116                let root_hash = root_hash.as_ref();
117                let root_hash_with_signing_committee_members: Vec<(
118                    &[u8],
119                    &PublicKeyBls,
120                )> = committee_members_pks
121                    .iter()
122                    .enumerate()
123                    .filter_map(|(i, member)| {
124                        if witnesses.0.bit(i as u64) {
125                            Some((root_hash.as_slice(), member))
126                        } else {
127                            None
128                        }
129                    })
130                    .collect();
131                let num_of_signatures = root_hash_with_signing_committee_members.len();
132                if num_of_signatures < threshold.into() {
133                    return Err(CertificateError::InsufficientNumberOfSignatures {
134                        threshold: threshold.into(),
135                        actual: num_of_signatures,
136                    });
137                }
138                let is_valid_signature = aggregated_signature
139                    .aggregate_verify(
140                        &mut root_hash_with_signing_committee_members.into_iter(),
141                    )
142                    .map_err(CertificateError::SignatureVerificationFailed)?;
143                if is_valid_signature {
144                    Ok(())
145                } else {
146                    Err(CertificateError::InvalidAggregateSignature)
147                }
148            }
149        }
150    }
151
152    /// Reveal the contents of the certificate to a path in storage.
153    ///
154    /// Write the revealed content of the certificate to the given path. Any previous value is
155    /// overwritten.
156    ///
157    /// Up to ~10MB of content may be revealed in one go (`10,063,860 bytes`). The
158    /// content overwrites any pre-existing value in `path`. If an error occurs,
159    /// the content revealed up-to the error occuring will be written in `path`.
160    ///
161    /// **CAUTION**: this function will consume up to `~1.3 Billion` ticks, it is
162    /// recommended to limit any other computation done within the same `kernel_run`,
163    /// and to perform your own benchmarking.
164    ///
165    /// If `reveal_to_store` returns an error, any content succesfully revealed
166    /// up until the error occurred will remain in storage; use [Runtime::store_value_size] to
167    /// determine the size of this value.
168    pub fn reveal_to_store<Host: Runtime>(
169        &self,
170        host: &mut Host,
171        path: &impl Path,
172    ) -> Result<usize, CertificateError> {
173        const MAX_TOP_LEVEL_HASHES: usize = 20;
174
175        // We reveal the root_hash, followed by twenty top level hashes. Each of these
176        // hashes can reveal up to MAX_HASHES_PER_PAGE, which then all reveal to content
177        // pages.
178        //
179        // Therefore, we can reveal up to:
180        //   [1 * MAX_TOP_LEVEL_HASHES * MAX_HASHES_PER_PAGE * MAX_PAGE_SIZE] bytes
181        // = [1 * 20                   * 123                 * 4091         ] bytes
182        // = [10063860                                                      ] bytes
183        const MAX_REVEALS: usize = 1
184            + MAX_TOP_LEVEL_HASHES
185            + V0SliceHashPage::MAX_HASHES_PER_PAGE * MAX_TOP_LEVEL_HASHES;
186
187        host.store_delete_value(path)
188            .map_err(|error| CertificateError::StorageError(path.to_string(), error))?;
189
190        let buffer = &mut [0u8; MAX_PAGE_SIZE];
191        let mut written = 0;
192
193        let mut save = |host: &mut Host, content: V0SliceContentPage| {
194            let content = content.as_ref();
195            host.store_write(path, content, written)
196                .map_err(|e| CertificateError::StorageError(path.to_string(), e))
197                .map(|()| {
198                    written += content.len();
199                    written
200                })
201        };
202
203        let Self::V0(V0Certificate { root_hash, .. }) = self;
204
205        let mut revealed = 0;
206        let mut hashes = Vec::with_capacity(MAX_REVEALS);
207        hashes.push(*root_hash.as_ref());
208
209        while let Some(hash) = hashes.get(revealed) {
210            let (page, _) = fetch_page(host, hash, buffer)?;
211            revealed += 1;
212
213            match page {
214                SlicePage::V0HashPage(page) => {
215                    let num_allowed = MAX_REVEALS - hashes.len();
216
217                    if page.inner.len() > num_allowed * PREIMAGE_HASH_SIZE {
218                        return Err(CertificateError::PayloadTooLarge);
219                    }
220
221                    for hash in page.hashes() {
222                        hashes.push(*hash);
223                    }
224                }
225                SlicePage::V0ContentPage(page) => {
226                    save(host, page)?;
227                }
228            }
229        }
230
231        Ok(written)
232    }
233}
234
235fn fetch_page<'a>(
236    host: &impl Runtime,
237    hash: &[u8; PREIMAGE_HASH_SIZE],
238    buffer: &'a mut [u8],
239) -> Result<(SlicePage<'a>, &'a mut [u8]), CertificateError> {
240    super::fetch_page_raw(host, hash, buffer)
241        .map_err(|err| CertificateError::RevealError(hash.into(), err))
242        .and_then(|(page, buffer)| Ok((SlicePage::try_from(page)?, buffer)))
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248    use tezos_data_encoding::enc::BinWriter;
249    use tezos_data_encoding::nom::NomReader;
250
251    // taken from the output of octez-dac-client GET certificate
252    // Committee member 0 - public key hash: tz4Ate2Fj1QpVXBGLXioe57s3a1RUtavMS5P
253    // Committee member 0 - public key: BLpk1tsVzqCokL6dZEiCQgEvwqQp4btiHYm3A1HoEUxKUwq5jCNZMJQ7bU71QE969KioUWCKtK9F
254    // Committee member 1 - public key hash: tz4PA6aEFXbaSZXSmdTi933GQZPodn6VX8Q3
255    // Committee member 1 - public key: BLpk1xQMdGocMdiiuU2pGvNMeu8vP91nNfrKk5tCssvPzP4z9EY7k5bbEisrqN3pT9vaoN2dsSiW
256    // Hex payload - 0000000000000000
257    // Root hash - hex value: 005b55fa3bb27fa3644faa7bd1e4ce79319a41ff35a3c2128089224e2fbf918143
258    const EXAMPLE_CERTIFICATE: &[u8] = &[
259        0, 0, 91, 85, 250, 59, 178, 127, 163, 100, 79, 170, 123, 209, 228, 206, 121, 49,
260        154, 65, 255, 53, 163, 194, 18, 128, 137, 34, 78, 47, 191, 145, 129, 67, 130,
261        182, 229, 184, 224, 94, 136, 21, 243, 179, 240, 183, 241, 10, 232, 158, 214, 59,
262        15, 133, 100, 251, 67, 218, 154, 230, 151, 140, 184, 73, 49, 113, 11, 82, 243,
263        76, 154, 144, 156, 200, 188, 66, 24, 25, 43, 143, 115, 199, 11, 121, 55, 113, 90,
264        110, 66, 245, 66, 38, 36, 56, 169, 135, 207, 146, 121, 205, 25, 89, 34, 12, 160,
265        6, 64, 8, 169, 87, 137, 69, 56, 134, 18, 251, 240, 113, 214, 158, 98, 122, 80,
266        34, 80, 223, 83, 14, 126, 42, 3,
267    ];
268
269    const COMMITTEE_MEMBER_0_B58_PK: &str =
270        "BLpk1tsVzqCokL6dZEiCQgEvwqQp4btiHYm3A1HoEUxKUwq5jCNZMJQ7bU71QE969KioUWCKtK9F";
271
272    const COMMITTEE_MEMBER_1_B58_PK: &str =
273        "BLpk1xQMdGocMdiiuU2pGvNMeu8vP91nNfrKk5tCssvPzP4z9EY7k5bbEisrqN3pT9vaoN2dsSiW";
274
275    const COMMITTEE_MEMBER_2_B58_PK: &str =
276        "BLpk1xeM4fERgfDR13qxjRgT9DCtqL9qUo7PHxncNmo8NEQgW93QyJm4ySvYbwc4YwJxj6d9Jd8t";
277
278    fn to_public_key(b58_pk: &str) -> PublicKeyBls {
279        PublicKeyBls::from_base58_check(b58_pk).unwrap()
280    }
281
282    #[test]
283    fn encode_decode_certificate() {
284        let (_, certificate) = Certificate::nom_read(EXAMPLE_CERTIFICATE)
285            .expect("Deserialization should work");
286        let mut buffer = Vec::new();
287        certificate
288            .bin_write(&mut buffer)
289            .expect("Serialization should work");
290        assert_eq!(buffer.as_slice(), EXAMPLE_CERTIFICATE);
291    }
292
293    #[test]
294    fn verify_valid_certificate_signed_by_all_committee_members() {
295        let committee = vec![
296            to_public_key(COMMITTEE_MEMBER_0_B58_PK),
297            to_public_key(COMMITTEE_MEMBER_1_B58_PK),
298        ];
299        let (_, certificate) = Certificate::nom_read(EXAMPLE_CERTIFICATE).unwrap();
300        assert!(matches!(certificate.verify(&committee, 2), Ok(())))
301    }
302
303    #[test]
304    fn verify_valid_certificate_signed_by_enough_committee_members() {
305        let committee = vec![
306            to_public_key(COMMITTEE_MEMBER_0_B58_PK),
307            to_public_key(COMMITTEE_MEMBER_1_B58_PK),
308            to_public_key(COMMITTEE_MEMBER_2_B58_PK),
309        ];
310        let (_, certificate) = Certificate::nom_read(EXAMPLE_CERTIFICATE).unwrap();
311        assert!(matches!(certificate.verify(&committee, 2), Ok(())))
312    }
313
314    #[test]
315    fn verify_invalid_certificate_insufficient_number_of_signatures() {
316        let committee = vec![
317            to_public_key(COMMITTEE_MEMBER_0_B58_PK),
318            to_public_key(COMMITTEE_MEMBER_1_B58_PK),
319        ];
320        let (_, certificate) = Certificate::nom_read(EXAMPLE_CERTIFICATE).unwrap();
321        assert!(matches!(
322            certificate.verify(&committee, 3),
323            Err(CertificateError::InsufficientNumberOfSignatures {
324                threshold: 3,
325                actual: 2
326            })
327        ))
328    }
329
330    #[test]
331    fn verify_invalid_certificate_invalid_aggregate_signature() {
332        let committee = vec![
333            to_public_key(COMMITTEE_MEMBER_0_B58_PK),
334            to_public_key(COMMITTEE_MEMBER_2_B58_PK),
335        ];
336        let (_, certificate) = Certificate::nom_read(EXAMPLE_CERTIFICATE).unwrap();
337        assert!(matches!(
338            certificate.verify(&committee, 2),
339            Err(CertificateError::InvalidAggregateSignature),
340        ))
341    }
342
343    #[test]
344    fn verify_invalid_certificate_committee_members_out_of_order() {
345        // To check this scenario, we swap a committee member that signed the
346        // certificate with one that did not. If we swapped committee members
347        // that signed the certificate, then the set of committee members used
348        // to verify the signature will not change, and the verification will pass.
349        let committee = vec![
350            to_public_key(COMMITTEE_MEMBER_2_B58_PK),
351            to_public_key(COMMITTEE_MEMBER_0_B58_PK),
352            to_public_key(COMMITTEE_MEMBER_1_B58_PK),
353        ];
354        let (_, certificate) = Certificate::nom_read(EXAMPLE_CERTIFICATE).unwrap();
355        assert!(matches!(
356            certificate.verify(&committee, 2),
357            Err(CertificateError::InvalidAggregateSignature),
358        ));
359    }
360}