diff --git a/rust/signed_doc/src/doc_types/mod.rs b/rust/signed_doc/src/doc_types/mod.rs
new file mode 100644
index 0000000000..e07dbb00b2
--- /dev/null
+++ b/rust/signed_doc/src/doc_types/mod.rs
@@ -0,0 +1,116 @@
+//! An implementation of different defined document types
+//!
+
+mod proposal_document;
+
+use catalyst_types::uuid::UuidV4;
+pub use proposal_document::{ProposalDocument, PROPOSAL_DOCUMENT_UUID_TYPE};
+
+/// Represents different types of documents.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum DocumentType {
+ /// A proposal document containing proposal details.
+ ProposalDocument,
+ /// A template for proposal documents, defining the expected structure.
+ ProposalTemplate,
+ /// A document representing a comment on a proposal.
+ CommentDocument,
+ /// A template for comment documents, defining the expected structure.
+ CommentTemplate,
+ /// A review document containing feedback on a proposal.
+ ReviewDocument,
+ /// A template for review documents, defining the expected structure.
+ ReviewTemplate,
+ /// A document defining parameters for a specific category.
+ CategoryParametersDocument,
+ /// A template for category parameter documents, defining the expected structure.
+ CategoryParametersTemplate,
+ /// A document containing parameters for a specific campaign.
+ CampaignParametersDocument,
+ /// A template for campaign parameter documents, defining the expected structure.
+ CampaignParametersTemplate,
+ /// A document containing brand-related parameters.
+ BrandParametersDocument,
+ /// A template for brand parameter documents, defining the expected structure.
+ BrandParametersTemplate,
+ /// A document representing an action related to a proposal.
+ ProposalActionDocument,
+ /// A public voting transaction version 2.
+ PublicVoteTxV2,
+ /// A private voting transaction version 2.
+ PrivateVoteTxV2,
+ /// A block in the immutable ledger.
+ ImmutableLedgerBlock,
+}
+
+/// Proposal template `UuidV4` type.
+const PROPOSAL_TEMPLATE_UUID_TYPE: uuid::Uuid =
+ uuid::Uuid::from_u128(0x0CE8_AB38_9258_4FBC_A62E_7FAA_6E58_318F);
+/// Comment document `UuidV4` type.
+const COMMENT_DOCUMENT_UUID_TYPE: uuid::Uuid =
+ uuid::Uuid::from_u128(0xB679_DED3_0E7C_41BA_89F8_DA62_A178_98EA);
+/// Comment template `UuidV4` type.
+const COMMENT_TEMPLATE_UUID_TYPE: uuid::Uuid =
+ uuid::Uuid::from_u128(0x0B84_24D4_EBFD_46E3_9577_1775_A69D_290C);
+/// Review document `UuidV4` type.
+const REVIEW_DOCUMENT_UUID_TYPE: uuid::Uuid =
+ uuid::Uuid::from_u128(0xE4CA_F5F0_098B_45FD_94F3_0702_A457_3DB5);
+/// Review template `UuidV4` type.
+const REVIEW_TEMPLATE_UUID_TYPE: uuid::Uuid =
+ uuid::Uuid::from_u128(0xEBE5_D0BF_5D86_4577_AF4D_008F_DDBE_2EDC);
+/// Category parameters document `UuidV4` type.
+const CATEGORY_PARAMETERS_DOCUMENT_UUID_TYPE: uuid::Uuid =
+ uuid::Uuid::from_u128(0x48C2_0109_362A_4D32_9BBA_E0A9_CF8B_45BE);
+/// Category parameters template `UuidV4` type.
+const CATEGORY_PARAMETERS_TEMPLATE_UUID_TYPE: uuid::Uuid =
+ uuid::Uuid::from_u128(0x65B1_E8B0_51F1_46A5_9970_72CD_F268_84BE);
+/// Campaign parameters document `UuidV4` type.
+const CAMPAIGN_PARAMETERS_DOCUMENT_UUID_TYPE: uuid::Uuid =
+ uuid::Uuid::from_u128(0x0110_EA96_A555_47CE_8408_36EF_E6ED_6F7C);
+/// Campaign parameters template `UuidV4` type.
+const CAMPAIGN_PARAMETERS_TEMPLATE_UUID_TYPE: uuid::Uuid =
+ uuid::Uuid::from_u128(0x7E8F_5FA2_44CE_49C8_BFD5_02AF_42C1_79A3);
+/// Brand parameters document `UuidV4` type.
+const BRAND_PARAMETERS_DOCUMENT_UUID_TYPE: uuid::Uuid =
+ uuid::Uuid::from_u128(0x3E48_08CC_C86E_467B_9702_D60B_AA9D_1FCA);
+/// Brand parameters template `UuidV4` type.
+const BRAND_PARAMETERS_TEMPLATE_UUID_TYPE: uuid::Uuid =
+ uuid::Uuid::from_u128(0xFD3C_1735_80B1_4EEA_8D63_5F43_6D97_EA31);
+/// Proposal action document `UuidV4` type.
+const PROPOSAL_ACTION_DOCUMENT_UUID_TYPE: uuid::Uuid =
+ uuid::Uuid::from_u128(0x5E60_E623_AD02_4A1B_A1AC_406D_B978_EE48);
+/// Public vote transaction v2 `UuidV4` type.
+const PUBLIC_VOTE_TX_V2_UUID_TYPE: uuid::Uuid =
+ uuid::Uuid::from_u128(0x8DE5_586C_E998_4B95_8742_7BE3_C859_2803);
+/// Private vote transaction v2 `UuidV4` type.
+const PRIVATE_VOTE_TX_V2_UUID_TYPE: uuid::Uuid =
+ uuid::Uuid::from_u128(0xE78E_E18D_F380_44C1_A852_80AA_6ECB_07FE);
+/// Immutable ledger block `UuidV4` type.
+const IMMUTABLE_LEDGER_BLOCK_UUID_TYPE: uuid::Uuid =
+ uuid::Uuid::from_u128(0xD9E7_E6CE_2401_4D7D_9492_F4F7_C642_41C3);
+
+impl TryFrom for DocumentType {
+ type Error = anyhow::Error;
+
+ fn try_from(uuid: UuidV4) -> Result {
+ match uuid.uuid() {
+ PROPOSAL_DOCUMENT_UUID_TYPE => Ok(DocumentType::ProposalDocument),
+ PROPOSAL_TEMPLATE_UUID_TYPE => Ok(DocumentType::ProposalTemplate),
+ COMMENT_DOCUMENT_UUID_TYPE => Ok(DocumentType::CommentDocument),
+ COMMENT_TEMPLATE_UUID_TYPE => Ok(DocumentType::CommentTemplate),
+ REVIEW_DOCUMENT_UUID_TYPE => Ok(DocumentType::ReviewDocument),
+ REVIEW_TEMPLATE_UUID_TYPE => Ok(DocumentType::ReviewTemplate),
+ CATEGORY_PARAMETERS_DOCUMENT_UUID_TYPE => Ok(DocumentType::CategoryParametersDocument),
+ CATEGORY_PARAMETERS_TEMPLATE_UUID_TYPE => Ok(DocumentType::CategoryParametersTemplate),
+ CAMPAIGN_PARAMETERS_DOCUMENT_UUID_TYPE => Ok(DocumentType::CampaignParametersDocument),
+ CAMPAIGN_PARAMETERS_TEMPLATE_UUID_TYPE => Ok(DocumentType::CampaignParametersTemplate),
+ BRAND_PARAMETERS_DOCUMENT_UUID_TYPE => Ok(DocumentType::BrandParametersDocument),
+ BRAND_PARAMETERS_TEMPLATE_UUID_TYPE => Ok(DocumentType::BrandParametersTemplate),
+ PROPOSAL_ACTION_DOCUMENT_UUID_TYPE => Ok(DocumentType::ProposalActionDocument),
+ PUBLIC_VOTE_TX_V2_UUID_TYPE => Ok(DocumentType::PublicVoteTxV2),
+ PRIVATE_VOTE_TX_V2_UUID_TYPE => Ok(DocumentType::PrivateVoteTxV2),
+ IMMUTABLE_LEDGER_BLOCK_UUID_TYPE => Ok(DocumentType::ImmutableLedgerBlock),
+ _ => anyhow::bail!("Unsupported document type"),
+ }
+ }
+}
diff --git a/rust/signed_doc/src/doc_types/proposal_document.rs b/rust/signed_doc/src/doc_types/proposal_document.rs
new file mode 100644
index 0000000000..7fffae7f07
--- /dev/null
+++ b/rust/signed_doc/src/doc_types/proposal_document.rs
@@ -0,0 +1,68 @@
+//! Proposal Document object implementation
+//!
+
+use catalyst_types::problem_report::ProblemReport;
+
+use crate::{error::CatalystSignedDocError, CatalystSignedDocument};
+
+/// Proposal document `UuidV4` type.
+pub const PROPOSAL_DOCUMENT_UUID_TYPE: uuid::Uuid =
+ uuid::Uuid::from_u128(0x7808_D2BA_D511_40AF_84E8_C0D1_625F_DFDC);
+
+/// Proposal Document struct
+pub struct ProposalDocument {
+ /// Proposal document content data
+ /// TODO: change it to `serde_json::Value` type
+ #[allow(dead_code)]
+ content: Vec,
+}
+
+impl ProposalDocument {
+ /// Try to build `ProposalDocument` from `CatalystSignedDoc` doing all necessary
+ /// stateless verifications,
+ #[allow(dead_code)]
+ pub(crate) fn from_signed_doc(
+ doc: &CatalystSignedDocument, error_report: &ProblemReport,
+ ) -> anyhow::Result {
+ /// Context for error messages.
+ const CONTEXT: &str = "Catalyst Signed Document to Proposal Document";
+ let mut failed = false;
+
+ if doc.doc_type().uuid() != PROPOSAL_DOCUMENT_UUID_TYPE {
+ error_report.invalid_value(
+ "`type`",
+ &doc.doc_type().to_string(),
+ &format!("Proposal Document type UUID value is {PROPOSAL_DOCUMENT_UUID_TYPE}"),
+ CONTEXT,
+ );
+ failed = true;
+ }
+
+ // TODO add other validation
+
+ if failed {
+ anyhow::bail!("Failed to build `ProposalDocument` from `CatalystSignedDoc`");
+ }
+
+ let content = doc.doc_content().decoded_bytes().to_vec();
+ Ok(Self { content })
+ }
+
+ /// A comprehensive validation of the `ProposalDocument` content.
+ #[allow(clippy::unused_self)]
+ pub(crate) fn validate_with_report(&self, _doc_getter: F, _error_report: &ProblemReport)
+ where F: FnMut() -> Option {
+ // TODO: implement the rest of the validation
+ }
+}
+
+impl TryFrom for ProposalDocument {
+ type Error = CatalystSignedDocError;
+
+ fn try_from(doc: CatalystSignedDocument) -> Result {
+ let error_report = ProblemReport::new("Proposal Document");
+ let res = Self::from_signed_doc(&doc, &error_report)
+ .map_err(|e| CatalystSignedDocError::new(error_report, e))?;
+ Ok(res)
+ }
+}
diff --git a/rust/signed_doc/src/lib.rs b/rust/signed_doc/src/lib.rs
index ff2bf290f0..7e236937d8 100644
--- a/rust/signed_doc/src/lib.rs
+++ b/rust/signed_doc/src/lib.rs
@@ -2,10 +2,12 @@
mod builder;
mod content;
+pub mod doc_types;
pub mod error;
mod metadata;
mod signature;
mod utils;
+pub mod validator;
use std::{
convert::TryFrom,
@@ -15,11 +17,12 @@ use std::{
pub use builder::Builder;
use catalyst_types::problem_report::ProblemReport;
+pub use catalyst_types::uuid::{UuidV4, UuidV7};
pub use content::Content;
use coset::{CborSerializable, Header};
use ed25519_dalek::VerifyingKey;
use error::CatalystSignedDocError;
-pub use metadata::{DocumentRef, ExtraFields, Metadata, UuidV4, UuidV7};
+pub use metadata::{DocumentRef, ExtraFields, Metadata};
pub use minicbor::{decode, encode, Decode, Decoder, Encode};
pub use signature::{KidUri, Signatures};
use utils::context::DecodeSignDocCtx;
diff --git a/rust/signed_doc/src/metadata/document_id.rs b/rust/signed_doc/src/metadata/document_id.rs
deleted file mode 100644
index 931373322d..0000000000
--- a/rust/signed_doc/src/metadata/document_id.rs
+++ /dev/null
@@ -1,45 +0,0 @@
-//! Document ID.
-use std::fmt::{Display, Formatter};
-
-use coset::cbor::Value;
-
-use super::{encode_cbor_uuid, UuidV7};
-
-/// Catalyst Document ID.
-#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, serde::Deserialize)]
-#[serde(from = "UuidV7")]
-pub struct DocumentId(UuidV7);
-
-impl DocumentId {
- /// Returns the `uuid::Uuid` type.
- #[must_use]
- pub fn uuid(&self) -> uuid::Uuid {
- self.0.uuid()
- }
-}
-
-impl Display for DocumentId {
- fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
- write!(f, "{}", self.0)
- }
-}
-
-impl From for DocumentId {
- fn from(uuid: UuidV7) -> Self {
- Self(uuid)
- }
-}
-
-impl From for UuidV7 {
- fn from(value: DocumentId) -> Self {
- value.0
- }
-}
-
-impl TryFrom for Value {
- type Error = anyhow::Error;
-
- fn try_from(value: DocumentId) -> Result {
- encode_cbor_uuid(value.0)
- }
-}
diff --git a/rust/signed_doc/src/metadata/document_ref.rs b/rust/signed_doc/src/metadata/document_ref.rs
index 367e66a3c6..eaffaa58b2 100644
--- a/rust/signed_doc/src/metadata/document_ref.rs
+++ b/rust/signed_doc/src/metadata/document_ref.rs
@@ -13,6 +13,17 @@ pub struct DocumentRef {
pub ver: Option,
}
+impl DocumentRef {
+ /// Determine if internal `UUID`s are valid.
+ #[must_use]
+ pub fn is_valid(&self) -> bool {
+ match self.ver {
+ Some(ver) => self.id.is_valid() && ver.is_valid() && ver >= self.id,
+ None => self.id.is_valid(),
+ }
+ }
+}
+
impl TryFrom for Value {
type Error = anyhow::Error;
diff --git a/rust/signed_doc/src/metadata/document_type.rs b/rust/signed_doc/src/metadata/document_type.rs
deleted file mode 100644
index 6c0b355289..0000000000
--- a/rust/signed_doc/src/metadata/document_type.rs
+++ /dev/null
@@ -1,45 +0,0 @@
-//! Document Type.
-use std::fmt::{Display, Formatter};
-
-use coset::cbor::Value;
-
-use super::{encode_cbor_uuid, UuidV4};
-
-/// Catalyst Document Type.
-#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, serde::Deserialize)]
-#[serde(from = "UuidV4")]
-pub struct DocumentType(UuidV4);
-
-impl DocumentType {
- /// Returns the `uuid::Uuid` type.
- #[must_use]
- pub fn uuid(&self) -> uuid::Uuid {
- self.0.uuid()
- }
-}
-
-impl Display for DocumentType {
- fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
- write!(f, "{}", self.0)
- }
-}
-
-impl From for DocumentType {
- fn from(value: UuidV4) -> Self {
- Self(value)
- }
-}
-
-impl From for UuidV4 {
- fn from(value: DocumentType) -> Self {
- value.0
- }
-}
-
-impl TryFrom for Value {
- type Error = anyhow::Error;
-
- fn try_from(value: DocumentType) -> Result {
- encode_cbor_uuid(value.0)
- }
-}
diff --git a/rust/signed_doc/src/metadata/document_version.rs b/rust/signed_doc/src/metadata/document_version.rs
deleted file mode 100644
index 5aea2d0275..0000000000
--- a/rust/signed_doc/src/metadata/document_version.rs
+++ /dev/null
@@ -1,44 +0,0 @@
-//! Document Version.
-use std::fmt::{Display, Formatter};
-
-use coset::cbor::Value;
-
-use super::{encode_cbor_uuid, UuidV7};
-
-/// Catalyst Document Version.
-#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, serde::Deserialize)]
-pub struct DocumentVersion(UuidV7);
-
-impl DocumentVersion {
- /// Returns the `uuid::Uuid` type.
- #[must_use]
- pub fn uuid(&self) -> uuid::Uuid {
- self.0.uuid()
- }
-}
-
-impl Display for DocumentVersion {
- fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
- write!(f, "{}", self.0)
- }
-}
-
-impl From for DocumentVersion {
- fn from(value: UuidV7) -> Self {
- Self(value)
- }
-}
-
-impl From for UuidV7 {
- fn from(value: DocumentVersion) -> Self {
- value.0
- }
-}
-
-impl TryFrom for Value {
- type Error = anyhow::Error;
-
- fn try_from(value: DocumentVersion) -> Result {
- encode_cbor_uuid(value.0)
- }
-}
diff --git a/rust/signed_doc/src/metadata/mod.rs b/rust/signed_doc/src/metadata/mod.rs
index cc7619b788..bbc42be07d 100644
--- a/rust/signed_doc/src/metadata/mod.rs
+++ b/rust/signed_doc/src/metadata/mod.rs
@@ -4,23 +4,19 @@ use std::fmt::{Display, Formatter};
mod algorithm;
mod content_encoding;
mod content_type;
-mod document_id;
mod document_ref;
-mod document_type;
-mod document_version;
mod extra_fields;
use algorithm::Algorithm;
use anyhow::{anyhow, bail};
-use catalyst_types::problem_report::ProblemReport;
-pub use catalyst_types::uuid::{CborContext, UuidV4, UuidV7};
+use catalyst_types::{
+ problem_report::ProblemReport,
+ uuid::{CborContext, UuidV4, UuidV7},
+};
pub use content_encoding::ContentEncoding;
pub use content_type::ContentType;
use coset::{iana::CoapContentFormat, CborSerializable};
-pub use document_id::DocumentId;
pub use document_ref::DocumentRef;
-pub use document_type::DocumentType;
-pub use document_version::DocumentVersion;
pub use extra_fields::ExtraFields;
/// `content_encoding` field COSE key value
@@ -42,11 +38,11 @@ pub struct Metadata {
alg: Algorithm,
/// Document Type `UUIDv4`.
#[serde(rename = "type")]
- doc_type: DocumentType,
+ doc_type: UuidV4,
/// Document ID `UUIDv7`.
- id: DocumentId,
+ id: UuidV7,
/// Document Version `UUIDv7`.
- ver: DocumentVersion,
+ ver: UuidV7,
/// Document Payload Content Type.
#[serde(rename = "content-type")]
content_type: ContentType,
@@ -68,19 +64,19 @@ impl Metadata {
/// Return Document Type `UUIDv4`.
#[must_use]
pub fn doc_type(&self) -> UuidV4 {
- self.doc_type.into()
+ self.doc_type
}
/// Return Document ID `UUIDv7`.
#[must_use]
pub fn doc_id(&self) -> UuidV7 {
- self.id.into()
+ self.id
}
/// Return Document Version `UUIDv7`.
#[must_use]
pub fn doc_ver(&self) -> UuidV7 {
- self.ver.into()
+ self.ver
}
/// Returns the Document Content Type, if any.
@@ -244,9 +240,9 @@ impl Metadata {
}
Ok(Self {
- doc_type: doc_type.into(),
- id: id.into(),
- ver: ver.into(),
+ doc_type,
+ id,
+ ver,
alg: algorithm,
content_encoding,
content_type,
@@ -288,9 +284,9 @@ impl TryFrom<&Metadata> for coset::Header {
}
builder = builder
- .text_value(TYPE_KEY.to_string(), meta.doc_type.try_into()?)
- .text_value(ID_KEY.to_string(), meta.id.try_into()?)
- .text_value(VER_KEY.to_string(), meta.ver.try_into()?);
+ .text_value(TYPE_KEY.to_string(), encode_cbor_uuid(meta.doc_type)?)
+ .text_value(ID_KEY.to_string(), encode_cbor_uuid(meta.id)?)
+ .text_value(VER_KEY.to_string(), encode_cbor_uuid(meta.ver)?);
builder = meta.extra.fill_cose_header_fields(builder)?;
@@ -326,9 +322,7 @@ pub(crate) fn encode_cbor_uuid>(
/// Decode `From` type from `coset::cbor::Value`.
///
/// This is used to decode `UuidV4` and `UuidV7` types.
-pub(crate) fn decode_cbor_uuid<
- T: for<'a> minicbor::decode::Decode<'a, CborContext> + TryFrom,
->(
+pub(crate) fn decode_cbor_uuid minicbor::decode::Decode<'a, CborContext>>(
value: coset::cbor::Value,
) -> anyhow::Result {
match value.to_vec() {
diff --git a/rust/signed_doc/src/validator/mod.rs b/rust/signed_doc/src/validator/mod.rs
new file mode 100644
index 0000000000..7009e66198
--- /dev/null
+++ b/rust/signed_doc/src/validator/mod.rs
@@ -0,0 +1,71 @@
+//! Catalyst Signed Documents validation
+
+use catalyst_types::problem_report::ProblemReport;
+
+use crate::{
+ doc_types::{DocumentType, ProposalDocument},
+ error::CatalystSignedDocError,
+ CatalystSignedDocument,
+};
+
+/// A comprehensive validation of the `CatalystSignedDocument`,
+/// including a signature verification and document type based validation.
+///
+/// # Errors
+///
+/// Returns a report of validation failures and the source error.
+pub fn validate(
+ doc: &CatalystSignedDocument, doc_getter: F,
+) -> Result<(), CatalystSignedDocError>
+where F: FnMut() -> Option {
+ let error_report = ProblemReport::new("Catalyst Signed Document Validation");
+
+ let doc_type: DocumentType = match doc.doc_type().try_into() {
+ Ok(doc_type) => doc_type,
+ Err(e) => {
+ error_report.invalid_value(
+ "`type`",
+ &doc.doc_type().to_string(),
+ &e.to_string(),
+ "verifying document type",
+ );
+ return Err(CatalystSignedDocError::new(
+ error_report,
+ anyhow::anyhow!("Validation of the Catalyst Signed Document failed"),
+ ));
+ },
+ };
+
+ #[allow(clippy::match_same_arms)]
+ match doc_type {
+ DocumentType::ProposalDocument => {
+ if let Ok(proposal_doc) = ProposalDocument::from_signed_doc(doc, &error_report) {
+ proposal_doc.validate_with_report(doc_getter, &error_report);
+ }
+ },
+ DocumentType::ProposalTemplate => {},
+ DocumentType::CommentDocument => {},
+ DocumentType::CommentTemplate => {},
+ DocumentType::ReviewDocument => {},
+ DocumentType::ReviewTemplate => {},
+ DocumentType::CategoryParametersDocument => {},
+ DocumentType::CategoryParametersTemplate => {},
+ DocumentType::CampaignParametersDocument => {},
+ DocumentType::CampaignParametersTemplate => {},
+ DocumentType::BrandParametersDocument => {},
+ DocumentType::BrandParametersTemplate => {},
+ DocumentType::ProposalActionDocument => {},
+ DocumentType::PublicVoteTxV2 => {},
+ DocumentType::PrivateVoteTxV2 => {},
+ DocumentType::ImmutableLedgerBlock => {},
+ }
+
+ if error_report.is_problematic() {
+ return Err(CatalystSignedDocError::new(
+ error_report,
+ anyhow::anyhow!("Validation of the Catalyst Signed Document failed"),
+ ));
+ }
+
+ Ok(())
+}