• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

input-output-hk / catalyst-libs / 14850909841

06 May 2025 03:19AM UTC coverage: 65.466% (-0.02%) from 65.484%
14850909841

push

github

web-flow
Fix cardano-chain-follower build (#306)

10815 of 16520 relevant lines covered (65.47%)

2694.68 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.17
/rust/signed_doc/src/lib.rs
1
//! Catalyst documents signing crate
2

3
mod builder;
4
mod content;
5
pub mod doc_types;
6
mod metadata;
7
pub mod providers;
8
mod signature;
9
pub mod validator;
10

11
use std::{
12
    convert::TryFrom,
13
    fmt::{Display, Formatter},
14
    sync::Arc,
15
};
16

17
use anyhow::Context;
18
pub use builder::Builder;
19
pub use catalyst_types::{
20
    problem_report::ProblemReport,
21
    uuid::{Uuid, UuidV4, UuidV7},
22
};
23
pub use content::Content;
24
use coset::{CborSerializable, Header, TaggedCborSerializable};
25
pub use metadata::{ContentEncoding, ContentType, DocumentRef, ExtraFields, Metadata, Section};
26
use minicbor::{decode, encode, Decode, Decoder, Encode};
27
pub use signature::{IdUri, Signatures};
28

29
/// A problem report content string
30
const PROBLEM_REPORT_CTX: &str = "Catalyst Signed Document";
31

32
/// Inner type that holds the Catalyst Signed Document with parsing errors.
33
#[derive(Debug)]
34
struct InnerCatalystSignedDocument {
35
    /// Document Metadata
36
    metadata: Metadata,
37
    /// Document Content
38
    content: Content,
39
    /// Signatures
40
    signatures: Signatures,
41
    /// A comprehensive problem report, which could include a decoding errors along with
42
    /// the other validation errors
43
    report: ProblemReport,
44
}
45

46
/// Keep all the contents private.
47
/// Better even to use a structure like this.  Wrapping in an Arc means we don't have to
48
/// manage the Arc anywhere else. These are likely to be large, best to have the Arc be
49
/// non-optional.
50
#[derive(Debug, Clone)]
51
pub struct CatalystSignedDocument {
52
    /// Catalyst Signed Document metadata, raw doc, with content errors.
53
    inner: Arc<InnerCatalystSignedDocument>,
54
}
55

56
impl Display for CatalystSignedDocument {
57
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
×
58
        writeln!(f, "{}", self.inner.metadata)?;
×
59
        writeln!(f, "Payload Size: {} bytes", self.inner.content.size())?;
×
60
        writeln!(f, "Signature Information")?;
×
61
        if self.inner.signatures.is_empty() {
×
62
            writeln!(f, "  This document is unsigned.")?;
×
63
        } else {
64
            for kid in &self.inner.signatures.kids() {
×
65
                writeln!(f, "  Signature Key ID: {kid}")?;
×
66
            }
67
        }
68
        Ok(())
×
69
    }
×
70
}
71

72
impl From<InnerCatalystSignedDocument> for CatalystSignedDocument {
73
    fn from(inner: InnerCatalystSignedDocument) -> Self {
103✔
74
        Self {
103✔
75
            inner: inner.into(),
103✔
76
        }
103✔
77
    }
103✔
78
}
79

80
impl CatalystSignedDocument {
81
    // A bunch of getters to access the contents, or reason through the document, such as.
82

83
    /// Return Document Type `UUIDv4`.
84
    ///
85
    /// # Errors
86
    /// - Missing 'type' field.
87
    pub fn doc_type(&self) -> anyhow::Result<UuidV4> {
42✔
88
        self.inner.metadata.doc_type()
42✔
89
    }
42✔
90

91
    /// Return Document ID `UUIDv7`.
92
    ///
93
    /// # Errors
94
    /// - Missing 'id' field.
95
    pub fn doc_id(&self) -> anyhow::Result<UuidV7> {
43✔
96
        self.inner.metadata.doc_id()
43✔
97
    }
43✔
98

99
    /// Return Document Version `UUIDv7`.
100
    ///
101
    /// # Errors
102
    /// - Missing 'ver' field.
103
    pub fn doc_ver(&self) -> anyhow::Result<UuidV7> {
16✔
104
        self.inner.metadata.doc_ver()
16✔
105
    }
16✔
106

107
    /// Return document `Content`.
108
    #[must_use]
109
    pub fn doc_content(&self) -> &Content {
39✔
110
        &self.inner.content
39✔
111
    }
39✔
112

113
    /// Return document `ContentType`.
114
    ///
115
    /// # Errors
116
    /// - Missing 'content-type' field.
117
    pub fn doc_content_type(&self) -> anyhow::Result<ContentType> {
26✔
118
        self.inner.metadata.content_type()
26✔
119
    }
26✔
120

121
    /// Return document `ContentEncoding`.
122
    #[must_use]
123
    pub fn doc_content_encoding(&self) -> Option<ContentEncoding> {
14✔
124
        self.inner.metadata.content_encoding()
14✔
125
    }
14✔
126

127
    /// Return document metadata content.
128
    #[must_use]
129
    pub fn doc_meta(&self) -> &ExtraFields {
113✔
130
        self.inner.metadata.extra()
113✔
131
    }
113✔
132

133
    /// Return a Document's signatures
134
    #[must_use]
135
    pub(crate) fn signatures(&self) -> &Signatures {
×
136
        &self.inner.signatures
×
137
    }
×
138

139
    /// Return a list of Document's Catalyst IDs.
140
    #[must_use]
141
    pub fn kids(&self) -> Vec<IdUri> {
13✔
142
        self.inner.signatures.kids()
13✔
143
    }
13✔
144

145
    /// Return a list of Document's author IDs (short form of Catalyst IDs).
146
    #[must_use]
147
    pub fn authors(&self) -> Vec<IdUri> {
×
148
        self.inner.signatures.authors()
×
149
    }
×
150

151
    /// Returns a collected problem report for the document.
152
    /// It accumulates all kind of errors, collected during the decoding, type based
153
    /// validation and signature verification.
154
    ///
155
    /// This is method is only for the public API usage, do not use it internally inside
156
    /// this crate.
157
    #[must_use]
158
    pub fn problem_report(&self) -> ProblemReport {
4✔
159
        self.report().clone()
4✔
160
    }
4✔
161

162
    /// Returns an internal problem report
163
    #[must_use]
164
    pub(crate) fn report(&self) -> &ProblemReport {
120✔
165
        &self.inner.report
120✔
166
    }
120✔
167

168
    /// Convert Catalyst Signed Document into `coset::CoseSign`
169
    ///
170
    /// # Errors
171
    /// Could fails if the `CatalystSignedDocument` object is not valid.
172
    pub(crate) fn as_cose_sign(&self) -> anyhow::Result<coset::CoseSign> {
7✔
173
        self.inner.as_cose_sign()
7✔
174
    }
7✔
175

176
    /// Returns a signed document `Builder` pre-loaded with the current signed document's
177
    /// data.
178
    #[must_use]
179
    pub fn into_builder(&self) -> Builder {
×
180
        self.into()
×
181
    }
×
182
}
183

184
impl InnerCatalystSignedDocument {
185
    /// Convert Catalyst Signed Document into `coset::CoseSign`
186
    ///
187
    /// # Errors
188
    /// Could fails if the `CatalystSignedDocument` object is not valid.
189
    fn as_cose_sign(&self) -> anyhow::Result<coset::CoseSign> {
25✔
190
        let protected_header =
25✔
191
            Header::try_from(&self.metadata).context("Failed to encode Document Metadata")?;
25✔
192

193
        let content = self
25✔
194
            .content
25✔
195
            .encoded_bytes(self.metadata.content_encoding())?;
25✔
196

197
        let mut builder = coset::CoseSignBuilder::new()
25✔
198
            .protected(protected_header)
25✔
199
            .payload(content);
25✔
200

201
        for signature in self.signatures.cose_signatures() {
25✔
202
            builder = builder.add_signature(signature);
18✔
203
        }
18✔
204
        Ok(builder.build())
25✔
205
    }
25✔
206
}
207

208
impl Decode<'_, ()> for CatalystSignedDocument {
209
    fn decode(d: &mut Decoder<'_>, _ctx: &mut ()) -> Result<Self, decode::Error> {
1✔
210
        let start = d.position();
1✔
211
        d.skip()?;
1✔
212
        let end = d.position();
1✔
213
        let cose_bytes = d
1✔
214
            .input()
1✔
215
            .get(start..end)
1✔
216
            .ok_or(minicbor::decode::Error::end_of_input())?;
1✔
217

218
        let cose_sign = coset::CoseSign::from_tagged_slice(cose_bytes)
1✔
219
            .or_else(|_| coset::CoseSign::from_slice(cose_bytes))
1✔
220
            .map_err(|e| {
1✔
221
                minicbor::decode::Error::message(format!("Invalid COSE Sign document: {e}"))
×
222
            })?;
1✔
223

224
        let report = ProblemReport::new(PROBLEM_REPORT_CTX);
1✔
225
        let metadata = Metadata::from_protected_header(&cose_sign.protected, &report);
1✔
226
        let signatures = Signatures::from_cose_sig_list(&cose_sign.signatures, &report);
1✔
227

228
        let content = if let Some(payload) = cose_sign.payload {
1✔
229
            Content::from_encoded(payload, metadata.content_encoding(), &report)
1✔
230
        } else {
231
            report.missing_field("COSE Sign Payload", "Missing document content (payload)");
×
232
            Content::default()
×
233
        };
234

235
        Ok(InnerCatalystSignedDocument {
1✔
236
            metadata,
1✔
237
            content,
1✔
238
            signatures,
1✔
239
            report,
1✔
240
        }
1✔
241
        .into())
1✔
242
    }
1✔
243
}
244

245
impl Encode<()> for CatalystSignedDocument {
246
    fn encode<W: minicbor::encode::Write>(
1✔
247
        &self, e: &mut encode::Encoder<W>, _ctx: &mut (),
1✔
248
    ) -> Result<(), encode::Error<W::Error>> {
1✔
249
        let cose_sign = self.as_cose_sign().map_err(encode::Error::message)?;
1✔
250

251
        let cose_bytes = cose_sign.to_tagged_vec().map_err(|e| {
1✔
252
            minicbor::encode::Error::message(format!("Failed to encode COSE Sign document: {e}"))
×
253
        })?;
1✔
254

255
        e.writer_mut()
1✔
256
            .write_all(&cose_bytes)
1✔
257
            .map_err(|_| minicbor::encode::Error::message("Failed to encode to CBOR"))
1✔
258
    }
1✔
259
}
260

261
impl TryFrom<&[u8]> for CatalystSignedDocument {
262
    type Error = anyhow::Error;
263

264
    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
1✔
265
        Ok(minicbor::decode(value)?)
1✔
266
    }
1✔
267
}
268

269
impl TryFrom<CatalystSignedDocument> for Vec<u8> {
270
    type Error = anyhow::Error;
271

272
    fn try_from(value: CatalystSignedDocument) -> Result<Self, Self::Error> {
1✔
273
        Ok(minicbor::to_vec(value)?)
1✔
274
    }
1✔
275
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc