1#![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#[derive(Debug, Error)]
37pub enum CertificateError {
38 #[error(
40 "Insufficient number of signatures - threshold: {threshold}, actual: {actual}"
41 )]
42 InsufficientNumberOfSignatures {
43 threshold: usize,
45 actual: usize,
47 },
48 #[error("Error propagated by cryptographic primitives while verifying the aggregate signature: {0}")]
50 SignatureVerificationFailed(CryptoError),
51 #[error("Verification of aggregate signature failed")]
53 InvalidAggregateSignature,
54 #[error("Failed to write certificate contents to {0}:{1}")]
56 StorageError(String, RuntimeError),
57 #[error("Could not reveal content of {0:?}:{1}")]
59 RevealError(PreimageHash, RuntimeError),
60 #[error("Revealed page is invalid: {0:?}")]
62 PageError(SlicePageError),
63 #[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#[derive(Debug, HasEncoding, NomReader, BinWriter)]
78#[encoding(tags = "u8")]
79pub enum Certificate {
80 #[encoding(tag = 0)]
82 V0(V0Certificate),
83}
84
85#[derive(Debug, HasEncoding, NomReader, BinWriter)]
87pub struct V0Certificate {
88 pub root_hash: PreimageHash,
92 pub aggregated_signature: BlsSignature,
94 pub witnesses: Zarith,
96}
97
98impl Certificate {
99 #[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 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 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 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 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}