#![allow(clippy::useless_format)]
#![cfg(feature = "alloc")]
use super::PreimageHash;
use super::SlicePage;
use super::SlicePageError;
use super::V0SliceContentPage;
use super::V0SliceHashPage;
use super::MAX_PAGE_SIZE;
use tezos_crypto_rs::hash::BlsSignature;
#[cfg(feature = "bls")]
use tezos_crypto_rs::hash::PublicKeyBls;
use tezos_crypto_rs::CryptoError;
use tezos_data_encoding::enc::BinWriter;
use tezos_data_encoding::encoding::HasEncoding;
use tezos_data_encoding::nom::NomReader;
use tezos_data_encoding::types::Zarith;
use tezos_smart_rollup_core::PREIMAGE_HASH_SIZE;
use tezos_smart_rollup_host::path::Path;
use tezos_smart_rollup_host::runtime::Runtime;
use tezos_smart_rollup_host::runtime::RuntimeError;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum CertificateError {
#[error(
"Insufficient number of signatures - threshold: {threshold}, actual: {actual}"
)]
InsufficientNumberOfSignatures {
threshold: usize,
actual: usize,
},
#[error("Error propagated by cryptographic primitives while verifying the aggregate signature: {0}")]
SignatureVerificationFailed(CryptoError),
#[error("Verification of aggregate signature failed")]
InvalidAggregateSignature,
#[error("Failed to write certificate contents to {0}:{1}")]
StorageError(String, RuntimeError),
#[error("Could not reveal content of {0:?}:{1}")]
RevealError(PreimageHash, RuntimeError),
#[error("Revealed page is invalid: {0:?}")]
PageError(SlicePageError),
#[error("Payload too large")]
PayloadTooLarge,
}
impl From<SlicePageError> for CertificateError {
fn from(e: SlicePageError) -> Self {
Self::PageError(e)
}
}
#[derive(Debug, HasEncoding, NomReader, BinWriter)]
#[encoding(tags = "u8")]
pub enum Certificate {
#[encoding(tag = 0)]
V0(V0Certificate),
}
#[derive(Debug, HasEncoding, NomReader, BinWriter)]
pub struct V0Certificate {
pub root_hash: PreimageHash,
pub aggregated_signature: BlsSignature,
pub witnesses: Zarith,
}
impl Certificate {
#[cfg(feature = "bls")]
pub fn verify(
&self,
committee_members_pks: &[PublicKeyBls],
threshold: u8,
) -> Result<(), CertificateError> {
match self {
Certificate::V0(V0Certificate {
root_hash,
aggregated_signature,
witnesses,
}) => {
let root_hash = root_hash.as_ref();
let root_hash_with_signing_committee_members: Vec<(
&[u8],
&PublicKeyBls,
)> = committee_members_pks
.iter()
.enumerate()
.filter_map(|(i, member)| {
if witnesses.0.bit(i as u64) {
Some((root_hash.as_slice(), member))
} else {
None
}
})
.collect();
let num_of_signatures = root_hash_with_signing_committee_members.len();
if num_of_signatures < threshold.into() {
return Err(CertificateError::InsufficientNumberOfSignatures {
threshold: threshold.into(),
actual: num_of_signatures,
});
}
let is_valid_signature = aggregated_signature
.aggregate_verify(
&mut root_hash_with_signing_committee_members.into_iter(),
)
.map_err(CertificateError::SignatureVerificationFailed)?;
if is_valid_signature {
Ok(())
} else {
Err(CertificateError::InvalidAggregateSignature)
}
}
}
}
pub fn reveal_to_store<Host: Runtime>(
&self,
host: &mut Host,
path: &impl Path,
) -> Result<usize, CertificateError> {
const MAX_TOP_LEVEL_HASHES: usize = 20;
const MAX_REVEALS: usize = 1
+ MAX_TOP_LEVEL_HASHES
+ V0SliceHashPage::MAX_HASHES_PER_PAGE * MAX_TOP_LEVEL_HASHES;
host.store_delete_value(path)
.map_err(|error| CertificateError::StorageError(path.to_string(), error))?;
let buffer = &mut [0u8; MAX_PAGE_SIZE];
let mut written = 0;
let mut save = |host: &mut Host, content: V0SliceContentPage| {
let content = content.as_ref();
host.store_write(path, content, written)
.map_err(|e| CertificateError::StorageError(path.to_string(), e))
.map(|()| {
written += content.len();
written
})
};
let Self::V0(V0Certificate { root_hash, .. }) = self;
let mut revealed = 0;
let mut hashes = Vec::with_capacity(MAX_REVEALS);
hashes.push(*root_hash.as_ref());
while let Some(hash) = hashes.get(revealed) {
let (page, _) = fetch_page(host, hash, buffer)?;
revealed += 1;
match page {
SlicePage::V0HashPage(page) => {
let num_allowed = MAX_REVEALS - hashes.len();
if page.inner.len() > num_allowed * PREIMAGE_HASH_SIZE {
return Err(CertificateError::PayloadTooLarge);
}
for hash in page.hashes() {
hashes.push(*hash);
}
}
SlicePage::V0ContentPage(page) => {
save(host, page)?;
}
}
}
Ok(written)
}
}
fn fetch_page<'a>(
host: &impl Runtime,
hash: &[u8; PREIMAGE_HASH_SIZE],
buffer: &'a mut [u8],
) -> Result<(SlicePage<'a>, &'a mut [u8]), CertificateError> {
super::fetch_page_raw(host, hash, buffer)
.map_err(|err| CertificateError::RevealError(hash.into(), err))
.and_then(|(page, buffer)| Ok((SlicePage::try_from(page)?, buffer)))
}
#[cfg(test)]
mod tests {
use super::*;
use tezos_data_encoding::enc::BinWriter;
use tezos_data_encoding::nom::NomReader;
const EXAMPLE_CERTIFICATE: &[u8] = &[
0, 0, 91, 85, 250, 59, 178, 127, 163, 100, 79, 170, 123, 209, 228, 206, 121, 49,
154, 65, 255, 53, 163, 194, 18, 128, 137, 34, 78, 47, 191, 145, 129, 67, 130,
182, 229, 184, 224, 94, 136, 21, 243, 179, 240, 183, 241, 10, 232, 158, 214, 59,
15, 133, 100, 251, 67, 218, 154, 230, 151, 140, 184, 73, 49, 113, 11, 82, 243,
76, 154, 144, 156, 200, 188, 66, 24, 25, 43, 143, 115, 199, 11, 121, 55, 113, 90,
110, 66, 245, 66, 38, 36, 56, 169, 135, 207, 146, 121, 205, 25, 89, 34, 12, 160,
6, 64, 8, 169, 87, 137, 69, 56, 134, 18, 251, 240, 113, 214, 158, 98, 122, 80,
34, 80, 223, 83, 14, 126, 42, 3,
];
const COMMITTEE_MEMBER_0_B58_PK: &str =
"BLpk1tsVzqCokL6dZEiCQgEvwqQp4btiHYm3A1HoEUxKUwq5jCNZMJQ7bU71QE969KioUWCKtK9F";
const COMMITTEE_MEMBER_1_B58_PK: &str =
"BLpk1xQMdGocMdiiuU2pGvNMeu8vP91nNfrKk5tCssvPzP4z9EY7k5bbEisrqN3pT9vaoN2dsSiW";
const COMMITTEE_MEMBER_2_B58_PK: &str =
"BLpk1xeM4fERgfDR13qxjRgT9DCtqL9qUo7PHxncNmo8NEQgW93QyJm4ySvYbwc4YwJxj6d9Jd8t";
fn to_public_key(b58_pk: &str) -> PublicKeyBls {
PublicKeyBls::from_base58_check(b58_pk).unwrap()
}
#[test]
fn encode_decode_certificate() {
let (_, certificate) = Certificate::nom_read(EXAMPLE_CERTIFICATE)
.expect("Deserialization should work");
let mut buffer = Vec::new();
certificate
.bin_write(&mut buffer)
.expect("Serialization should work");
assert_eq!(buffer.as_slice(), EXAMPLE_CERTIFICATE);
}
#[test]
fn verify_valid_certificate_signed_by_all_committee_members() {
let committee = vec![
to_public_key(COMMITTEE_MEMBER_0_B58_PK),
to_public_key(COMMITTEE_MEMBER_1_B58_PK),
];
let (_, certificate) = Certificate::nom_read(EXAMPLE_CERTIFICATE).unwrap();
assert!(matches!(certificate.verify(&committee, 2), Ok(())))
}
#[test]
fn verify_valid_certificate_signed_by_enough_committee_members() {
let committee = vec![
to_public_key(COMMITTEE_MEMBER_0_B58_PK),
to_public_key(COMMITTEE_MEMBER_1_B58_PK),
to_public_key(COMMITTEE_MEMBER_2_B58_PK),
];
let (_, certificate) = Certificate::nom_read(EXAMPLE_CERTIFICATE).unwrap();
assert!(matches!(certificate.verify(&committee, 2), Ok(())))
}
#[test]
fn verify_invalid_certificate_insufficient_number_of_signatures() {
let committee = vec![
to_public_key(COMMITTEE_MEMBER_0_B58_PK),
to_public_key(COMMITTEE_MEMBER_1_B58_PK),
];
let (_, certificate) = Certificate::nom_read(EXAMPLE_CERTIFICATE).unwrap();
assert!(matches!(
certificate.verify(&committee, 3),
Err(CertificateError::InsufficientNumberOfSignatures {
threshold: 3,
actual: 2
})
))
}
#[test]
fn verify_invalid_certificate_invalid_aggregate_signature() {
let committee = vec![
to_public_key(COMMITTEE_MEMBER_0_B58_PK),
to_public_key(COMMITTEE_MEMBER_2_B58_PK),
];
let (_, certificate) = Certificate::nom_read(EXAMPLE_CERTIFICATE).unwrap();
assert!(matches!(
certificate.verify(&committee, 2),
Err(CertificateError::InvalidAggregateSignature),
))
}
#[test]
fn verify_invalid_certificate_committee_members_out_of_order() {
let committee = vec![
to_public_key(COMMITTEE_MEMBER_2_B58_PK),
to_public_key(COMMITTEE_MEMBER_0_B58_PK),
to_public_key(COMMITTEE_MEMBER_1_B58_PK),
];
let (_, certificate) = Certificate::nom_read(EXAMPLE_CERTIFICATE).unwrap();
assert!(matches!(
certificate.verify(&committee, 2),
Err(CertificateError::InvalidAggregateSignature),
));
}
}