use alloc::collections::BTreeSet;
use alloc::vec::Vec;
use keetanetwork_crypto::hash::hash_default;
use crate::error::BlockError;
use super::{AdjustMethod, BlockOperation, OperationContext, OperationType};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CertificateDer(Vec<u8>);
impl CertificateDer {
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn hash(&self) -> [u8; 32] {
hash_default(&self.0)
}
pub fn to_certificate(&self) -> Result<keetanetwork_x509::certificates::Certificate, BlockError> {
Ok(keetanetwork_x509::certificates::Certificate::try_from(self.0.as_slice())?)
}
}
impl From<Vec<u8>> for CertificateDer {
fn from(bytes: Vec<u8>) -> Self {
Self(bytes)
}
}
impl TryFrom<&keetanetwork_x509::certificates::Certificate> for CertificateDer {
type Error = BlockError;
fn try_from(certificate: &keetanetwork_x509::certificates::Certificate) -> Result<Self, Self::Error> {
Ok(Self(certificate.to_der()?))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CertificateOrHash {
Certificate(CertificateDer),
Hash([u8; 32]),
}
impl CertificateOrHash {
pub fn hash(&self) -> [u8; 32] {
match self {
CertificateOrHash::Certificate(certificate) => certificate.hash(),
CertificateOrHash::Hash(hash) => *hash,
}
}
}
impl From<CertificateDer> for CertificateOrHash {
fn from(certificate: CertificateDer) -> Self {
CertificateOrHash::Certificate(certificate)
}
}
impl From<[u8; 32]> for CertificateOrHash {
fn from(hash: [u8; 32]) -> Self {
CertificateOrHash::Hash(hash)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IntermediateCertificates {
None,
Bundle(Vec<CertificateDer>),
}
#[derive(Debug, Clone)]
pub struct ManageCertificate {
pub method: AdjustMethod,
pub certificate_or_hash: CertificateOrHash,
pub intermediate_certificates: Option<IntermediateCertificates>,
}
impl ManageCertificate {
pub fn canonical_certificate_or_hash(&self) -> CertificateOrHash {
match (self.method, &self.certificate_or_hash) {
(AdjustMethod::Subtract, CertificateOrHash::Certificate(certificate)) => {
CertificateOrHash::Hash(certificate.hash())
}
(_, certificate_or_hash) => certificate_or_hash.clone(),
}
}
}
impl BlockOperation for ManageCertificate {
const TYPE: OperationType = OperationType::ManageCertificate;
fn validate(&self, ctx: &OperationContext<'_>) -> Result<(), BlockError> {
if self.method == AdjustMethod::Set {
return Err(BlockError::AdjustMethodSetForbidden);
}
if self.intermediate_certificates.is_none() == (self.method == AdjustMethod::Add) {
return Err(BlockError::IntermediateCertificatesOnlyAdd);
}
if self.method == AdjustMethod::Add {
let CertificateOrHash::Certificate(certificate) = &self.certificate_or_hash else {
return Err(BlockError::InvalidCertificateValue);
};
let parsed = certificate.to_certificate()?;
let subject_key = &parsed
.tbs_certificate
.subject_public_key_info
.subject_public_key;
let account_bytes = ctx.account.to_public_key_with_type();
if subject_key.raw_bytes() != &account_bytes[1..] {
return Err(BlockError::CertificateSubjectMismatch);
}
if let Some(IntermediateCertificates::Bundle(bundle)) = &self.intermediate_certificates {
let mut graph = BTreeSet::new();
for intermediate in bundle {
graph.insert(intermediate.to_certificate()?);
}
parsed.assert_can_construct_valid_graph(&graph)?;
}
}
let mut seen: BTreeSet<[u8; 32]> = BTreeSet::new();
for other in ctx.iter_type::<ManageCertificate>() {
if !seen.insert(other.certificate_or_hash.hash()) {
return Err(BlockError::DuplicateCertificateOperation);
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use alloc::vec;
use super::*;
use crate::operation::harness::{assert_validation, manage_certificate_subtract, Harness};
use crate::operation::Operation;
use crate::testing::generate_ed25519_ref;
#[test]
fn test_certificate_or_hash_from() {
let certificate = CertificateDer::from(Vec::from([1u8, 2, 3]));
assert!(matches!(CertificateOrHash::from(certificate), CertificateOrHash::Certificate(_)));
assert!(matches!(CertificateOrHash::from([7u8; 32]), CertificateOrHash::Hash(_)));
}
#[test]
fn test_canonical_certificate_or_hash_normalizes_subtract() {
let certificate = CertificateDer::from(Vec::from([1u8, 2, 3]));
let expected = certificate.hash();
let subtract = ManageCertificate {
method: AdjustMethod::Subtract,
certificate_or_hash: CertificateOrHash::Certificate(certificate.clone()),
intermediate_certificates: None,
};
assert!(matches!(subtract.canonical_certificate_or_hash(), CertificateOrHash::Hash(hash) if hash == expected));
let add = ManageCertificate {
method: AdjustMethod::Add,
certificate_or_hash: CertificateOrHash::Certificate(certificate),
intermediate_certificates: Some(IntermediateCertificates::None),
};
assert!(matches!(add.canonical_certificate_or_hash(), CertificateOrHash::Certificate(_)));
}
#[test]
fn test_manage_certificate_validation() {
assert_validation! {
"rejects_set_method": {
let mut operation = manage_certificate_subtract(7);
operation.method = AdjustMethod::Set;
(Harness::new(generate_ed25519_ref(1)), operation.into())
} => Err(BlockError::AdjustMethodSetForbidden),
"rejects_intermediates_on_subtract": {
let mut operation = manage_certificate_subtract(7);
operation.intermediate_certificates = Some(IntermediateCertificates::None);
(Harness::new(generate_ed25519_ref(1)), operation.into())
} => Err(BlockError::IntermediateCertificatesOnlyAdd),
"rejects_hash_on_add": {
let mut operation = manage_certificate_subtract(7);
operation.method = AdjustMethod::Add;
operation.intermediate_certificates = Some(IntermediateCertificates::None);
(Harness::new(generate_ed25519_ref(1)), operation.into())
} => Err(BlockError::InvalidCertificateValue),
"rejects_duplicate_certificate": {
let mut harness = Harness::new(generate_ed25519_ref(1));
let operation: Operation = manage_certificate_subtract(7).into();
harness.operations = vec![operation.clone(), operation.clone()];
(harness, operation)
} => Err(BlockError::DuplicateCertificateOperation),
}
}
}