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

input-output-hk / catalyst-libs / 16646523141

31 Jul 2025 10:28AM UTC coverage: 67.341% (+3.8%) from 63.544%
16646523141

push

github

web-flow
feat(rust/signed-doc): Implement new Catalyst Signed Doc (#338)

* chore: add new line to open pr

Signed-off-by: bkioshn <bkioshn@gmail.com>

* chore: revert

Signed-off-by: bkioshn <bkioshn@gmail.com>

* feat(rust/signed-doc): add new type `DocType` (#339)

* feat(signed-doc): add new type DocType

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(signed-doc): add conversion policy

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(signed-doc): doc type

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(signed-doc): doc type error

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(signed-doc): seperate test

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(signed-doc): format

Signed-off-by: bkioshn <bkioshn@gmail.com>

---------

Signed-off-by: bkioshn <bkioshn@gmail.com>

* feat(rust/signed-doc): Add initial decoding tests for the Catalyst Signed Documents (#349)

* wip

* wip

* fix fmt

* fix spelling

* fix clippy

* fix(rust/signed-doc): Apply new `DocType` (#347)

* feat(signed-doc): add new type DocType

Signed-off-by: bkioshn <bkioshn@gmail.com>

* wip: apply doctype

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(signed-doc): add more function to DocType

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(signed-doc): map old doctype to new doctype

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(signed-doc): add eq to uuidv4

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(signed-doc): fix validator

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(signed-doc): minor fixes

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(catalyst-types): add hash to uuidv4

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(signed-doc): decoding test

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(signed-doc): doctype

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(signed-doc): minor fixes

Signed-off-by: bkioshn <bkioshn@gmail.com>

* chore(sign-doc): fix comment

Signed-off-by: bkioshn <bkioshn@gmail.com>

* fix(catalyst-types): add froms... (continued)

2453 of 2675 new or added lines in 38 files covered. (91.7%)

19 existing lines in 7 files now uncovered.

11312 of 16798 relevant lines covered (67.34%)

2525.16 hits per line

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

95.14
/rust/cbork-utils/src/array.rs
1
//! CBOR array (CBOR major type 4) structure with CBOR decoding and encoding
2
//! functionality. Supports deterministically encoded rules (RFC 8949 Section 4.2) if
3
//! corresponding option is enabled.
4

5
use std::{ops::Deref, vec::IntoIter};
6

7
use crate::{
8
    decode_context::DecodeCtx, decode_helper::get_bytes, deterministic_helper::CBOR_MAX_TINY_VALUE,
9
};
10

11
/// Represents a CBOR array, preserving original decoding order of values.
12
#[derive(Clone, Debug, PartialEq, Eq)]
13
pub struct Array(Vec<Vec<u8>>);
14

15
impl Deref for Array {
16
    type Target = Vec<Vec<u8>>;
17

18
    fn deref(&self) -> &Self::Target {
6✔
19
        &self.0
6✔
20
    }
6✔
21
}
22

23
impl IntoIterator for Array {
24
    type IntoIter = IntoIter<Vec<u8>>;
25
    type Item = Vec<u8>;
26

NEW
27
    fn into_iter(self) -> Self::IntoIter {
×
NEW
28
        self.0.into_iter()
×
NEW
29
    }
×
30
}
31

32
/// Major type indicator for CBOR arrays (major type 4: 100 in top 3 bits)
33
/// As per RFC 8949 Section 4.2, arrays in deterministic encoding must:
34
/// - Have lengths encoded minimally (Section 4.2.1)
35
/// - Use definite-length encoding only (Section 4.2.2)
36
/// - Have all elements themselves deterministically encoded
37
const CBOR_MAJOR_TYPE_ARRAY: u8 = 4 << 5;
38

39
/// Initial byte for a CBOR array whose length is encoded as an 8-bit unsigned integer
40
/// (uint8).
41
///
42
/// This value combines the array major type (4) with the additional information value
43
/// (24) that indicates a uint8 length follows. The resulting byte is:
44
/// - High 3 bits: 100 (major type 4 for array)
45
/// - Low 5 bits: 24 (indicates uint8 length follows)
46
///
47
/// Used when encoding CBOR arrays with lengths between 24 and 255 elements.
48
const CBOR_ARRAY_LENGTH_UINT8: u8 = CBOR_MAJOR_TYPE_ARRAY | 24; // For uint8 length encoding
49

50
/// Decodes a CBOR array with deterministic encoding validation (RFC 8949 Section 4.2)
51
/// Returns the raw bytes of the array elements if it passes all deterministic validation
52
/// rules.
53
///
54
/// From RFC 8949 Section 4.2:
55
/// Arrays must follow these deterministic encoding rules:
56
/// - Array lengths must use minimal encoding (Section 4.2.1)
57
/// - Indefinite-length arrays are not allowed (Section 4.2.2)
58
/// - All array elements must themselves be deterministically encoded
59
///
60
/// # Errors
61
///
62
/// Returns `DeterministicError` if:
63
/// - Input is empty (`UnexpectedEof`)
64
/// - Array uses indefinite-length encoding (`IndefiniteLength`)
65
/// - Array length is not encoded minimally (`NonMinimalInt`)
66
/// - Array element decoding fails (`DecoderError`)
67
/// - Array elements are not deterministically encoded
68
impl minicbor::Decode<'_, DecodeCtx> for Array {
69
    fn decode(
760✔
70
        d: &mut minicbor::Decoder<'_>, ctx: &mut DecodeCtx,
760✔
71
    ) -> Result<Self, minicbor::decode::Error> {
760✔
72
        // Capture position before reading the array header
73
        let header_start_pos = d.position();
760✔
74

75
        // Handle both definite and indefinite-length arrays
76
        let length = d.array()?.ok_or_else(|| {
760✔
77
            minicbor::decode::Error::message(
2✔
78
                "Indefinite-length items must be made definite-length items",
79
            )
80
        })?;
2✔
81

82
        ctx.try_check(|| check_array_minimal_length(d, header_start_pos, length))?;
756✔
83

84
        decode_array_elements(d, length, ctx).map(Self)
755✔
85
    }
760✔
86
}
87

88
/// Validates that a CBOR array's length is encoded using the minimal number of bytes as
89
/// required by RFC 8949's deterministic encoding rules.
90
///
91
/// According to the deterministic encoding requirements:
92
/// - The length of an array MUST be encoded using the smallest possible CBOR additional
93
///   information value
94
/// - For values 0 through 23, the additional info byte is used directly
95
/// - For values that fit in 8, 16, 32, or 64 bits, the appropriate multi-byte encoding
96
///   must be used
97
///
98
/// # Specification Reference
99
/// This implementation follows RFC 8949 Section 4.2.1 which requires that:
100
/// "The length of arrays, maps, and strings MUST be encoded using the smallest possible
101
/// CBOR additional information value."
102
fn check_array_minimal_length(
755✔
103
    decoder: &minicbor::Decoder, header_start_pos: usize, value: u64,
755✔
104
) -> Result<(), minicbor::decode::Error> {
755✔
105
    // For zero length, 0x80 is always the minimal encoding
106
    if value == 0 {
755✔
107
        return Ok(());
185✔
108
    }
570✔
109

110
    let initial_byte = decoder
570✔
111
        .input()
570✔
112
        .get(header_start_pos)
570✔
113
        .copied()
570✔
114
        .ok_or_else(|| {
570✔
NEW
115
            minicbor::decode::Error::message("Cannot read initial byte for minimality check")
×
NEW
116
        })?;
×
117

118
    // Only check minimality for array length encodings using uint8
119
    // Immediate values (0-23) are already minimal by definition
120
    if initial_byte == CBOR_ARRAY_LENGTH_UINT8 && value <= CBOR_MAX_TINY_VALUE {
570✔
121
        return Err(minicbor::decode::Error::message(
1✔
122
            "array minimal length failure",
1✔
123
        ));
1✔
124
    }
569✔
125

126
    Ok(())
569✔
127
}
755✔
128

129
/// Decodes all elements in the array
130
fn decode_array_elements(
755✔
131
    d: &mut minicbor::Decoder, length: u64, _ctx: &mut DecodeCtx,
755✔
132
) -> Result<Vec<Vec<u8>>, minicbor::decode::Error> {
755✔
133
    let capacity = usize::try_from(length).map_err(|_| {
755✔
NEW
134
        minicbor::decode::Error::message("Array length too large for current platform")
×
NEW
135
    })?;
×
136
    let mut elements = Vec::with_capacity(capacity);
755✔
137

138
    // Decode each array element
139
    for _ in 0..length {
755✔
140
        // Record the starting position of the element
141
        let element_start = d.position();
2,225✔
142

143
        // Skip over the element to find its end position
144
        d.skip()?;
2,225✔
145
        let element_end = d.position();
2,225✔
146

147
        // The elements themselves must be deterministically encoded (4.2.1)
148
        let element_bytes = get_bytes(d, element_start, element_end)?.to_vec();
2,225✔
149

150
        elements.push(element_bytes);
2,225✔
151
    }
152

153
    Ok(elements)
755✔
154
}
755✔
155

156
#[cfg(test)]
157
mod tests {
158
    use minicbor::{Decode, Decoder};
159

160
    use super::*;
161

162
    /// Ensures that encoding and decoding an array preserves:
163
    /// - The exact byte representation of elements
164
    /// - The definite length encoding format
165
    /// - The order of elements
166
    #[test]
167
    fn test_array_bytes_roundtrip() {
1✔
168
        // Create a valid deterministic array encoding
169
        let mut decoder = Decoder::new(&[
1✔
170
            0x82, // 2 elements
1✔
171
            0x41, 0x01, // h'01'
1✔
172
            0x42, 0x01, 0x02, // h'0102'
1✔
173
        ]);
1✔
174
        let result = Array::decode(&mut decoder, &mut DecodeCtx::Deterministic).unwrap();
1✔
175

176
        // Verify we got back exactly the same bytes
177
        assert_eq!(
1✔
178
            result,
179
            Array(vec![
1✔
180
                vec![0x41, 0x01],       // h'01'
1✔
181
                vec![0x42, 0x01, 0x02], // h'0102'
1✔
182
            ])
1✔
183
        );
184
    }
1✔
185

186
    /// Test empty array handling - special case mentioned in RFC 8949.
187
    /// An empty array is valid and must still follow length encoding rules
188
    /// from Section 4.2.1.
189
    #[test]
190
    fn test_empty_array() {
1✔
191
        let mut decoder = Decoder::new(&[
1✔
192
            0x80, // Array with 0 elements - encoded with immediate value as per Section 4.2.1
1✔
193
        ]);
1✔
194
        assert!(Array::decode(&mut decoder, &mut DecodeCtx::Deterministic).is_ok());
1✔
195
    }
1✔
196

197
    /// Test minimal length encoding rules for arrays as specified in RFC 8949 Section
198
    /// 4.2.1
199
    ///
200
    /// From RFC 8949 Section 4.2.1:
201
    /// "The length of arrays, maps, strings, and byte strings must be encoded in the
202
    /// smallest possible way. For arrays (major type 4), lengths 0-23 must be encoded
203
    /// in the initial byte."
204
    #[test]
205
    fn test_array_minimal_length_encoding() {
1✔
206
        // Test case 1: Valid minimal encoding (length = 1)
207
        let mut decoder = Decoder::new(&[
1✔
208
            0x81, // Array, length 1 (major type 4 with immediate value 1)
1✔
209
            0x01, // Element: unsigned int 1
1✔
210
        ]);
1✔
211
        assert!(Array::decode(&mut decoder, &mut DecodeCtx::Deterministic).is_ok());
1✔
212

213
        // Test case 2: Invalid non-minimal encoding (using additional info 24 for length 1)
214
        let mut decoder = Decoder::new(&[
1✔
215
            0x98, // Array with additional info = 24 (0x80 | 0x18)
1✔
216
            0x01, // Length encoded as uint8 = 1
1✔
217
            0x01, // Element: unsigned int 1
1✔
218
        ]);
1✔
219
        assert!(Array::decode(&mut decoder.clone(), &mut DecodeCtx::Deterministic).is_err());
1✔
220
        assert!(Array::decode(&mut decoder, &mut DecodeCtx::non_deterministic()).is_ok());
1✔
221
    }
1✔
222

223
    /// Test handling of complex element structures while maintaining deterministic
224
    /// encoding
225
    ///
226
    /// RFC 8949 Section 4.2 requires that all elements be deterministically encoded:
227
    /// "All contained items must also follow the same rules."
228
    #[test]
229
    fn test_array_complex_elements() {
1✔
230
        let mut decoder = Decoder::new(&[
1✔
231
            0x84, // Array with 4 elements
1✔
232
            0x41, 0x01, // Element 1: simple 1-byte string
1✔
233
            0x42, 0x01, 0x02, // Element 2: 2-byte string
1✔
234
            0x62, 0x68, 0x69, // Element 3: "hi"
1✔
235
            0xF9, 0x00, 0x00, // Element 4: float 0.0 half-precision canonical encoding
1✔
236
        ]);
1✔
237
        assert!(Array::decode(&mut decoder, &mut DecodeCtx::Deterministic).is_ok());
1✔
238
    }
1✔
239

240
    /// Test edge cases for array encoding while maintaining compliance with RFC 8949
241
    ///
242
    /// These cases test boundary conditions that must still follow all rules from
243
    /// Section 4.2:
244
    /// - Minimal length encoding (4.2.1)
245
    /// - No indefinite lengths (4.2.2)
246
    /// - Deterministic element encoding
247
    #[test]
248
    fn test_array_edge_cases() {
1✔
249
        // Single element array - must still follow minimal length encoding rules
250
        let mut decoder = Decoder::new(&[
1✔
251
            0x81, // Array with 1 element (using immediate value as per Section 4.2.1)
1✔
252
            0x41, 0x01, // Element: 1-byte string
1✔
253
        ]);
1✔
254
        assert!(Array::decode(&mut decoder, &mut DecodeCtx::Deterministic).is_ok());
1✔
255

256
        // Array with zero-length string element - tests smallest possible element case
257
        let mut decoder = Decoder::new(&[
1✔
258
            0x81, // Array with 1 element
1✔
259
            0x40, // Element: 0-byte string (smallest possible element)
1✔
260
        ]);
1✔
261
        assert!(Array::decode(&mut decoder, &mut DecodeCtx::Deterministic).is_ok());
1✔
262
    }
1✔
263

264
    /// Test array with multiple elements of different types
265
    #[test]
266
    fn test_array_mixed_elements() {
1✔
267
        // Array with integer, string, and nested array elements
268
        let mut decoder = Decoder::new(&[
1✔
269
            0x83, // Array with 3 elements
1✔
270
            0x01, // Element 1: unsigned int 1
1✔
271
            0x41, 0x48, // Element 2: 1-byte string "H"
1✔
272
            0x81, 0x02, // Element 3: nested array with one element (unsigned int 2)
1✔
273
        ]);
1✔
274
        assert!(Array::decode(&mut decoder, &mut DecodeCtx::Deterministic).is_ok());
1✔
275
    }
1✔
276

277
    /// Test array with multiple elements
278
    #[allow(clippy::indexing_slicing)]
279
    #[test]
280
    fn test_array_larger_size() {
1✔
281
        // Test with a simple array of 5 single-byte strings
282
        let mut decoder = Decoder::new(&[
1✔
283
            0x85, // Array with 5 elements
1✔
284
            0x41, 0x01, // Element 1: 1-byte string with value 0x01
1✔
285
            0x41, 0x02, // Element 2: 1-byte string with value 0x02
1✔
286
            0x41, 0x03, // Element 3: 1-byte string with value 0x03
1✔
287
            0x41, 0x04, // Element 4: 1-byte string with value 0x04
1✔
288
            0x41, 0x05, // Element 5: 1-byte string with value 0x05
1✔
289
        ]);
1✔
290
        let result = Array::decode(&mut decoder, &mut DecodeCtx::Deterministic);
1✔
291
        assert!(result.is_ok());
1✔
292

293
        let array = result.unwrap();
1✔
294
        assert_eq!(array.len(), 5);
1✔
295

296
        // Verify the elements are correctly decoded
297
        assert_eq!(array[0], vec![0x41, 0x01]);
1✔
298
        assert_eq!(array[1], vec![0x41, 0x02]);
1✔
299
        assert_eq!(array[2], vec![0x41, 0x03]);
1✔
300
        assert_eq!(array[3], vec![0x41, 0x04]);
1✔
301
        assert_eq!(array[4], vec![0x41, 0x05]);
1✔
302
    }
1✔
303

304
    /// Test indefinite-length array rejection in deterministic mode
305
    /// and acceptance in non-deterministic mode
306
    #[test]
307
    fn test_array_with_indefinite_length() {
1✔
308
        // Indefinite-length array (not allowed in deterministic encoding)
309
        let decoder = Decoder::new(&[
1✔
310
            0x9F, // Array with indefinite length
1✔
311
            0x01, // Element 1
1✔
312
            0x02, // Element 2
1✔
313
            0xFF, // Break code
1✔
314
        ]);
1✔
315
        assert!(Array::decode(&mut decoder.clone(), &mut DecodeCtx::Deterministic).is_err());
1✔
316
        // Even it's non-deterministic, this should fail, as we enforce for the defined length.
317
        assert!(Array::decode(&mut decoder.clone(), &mut DecodeCtx::non_deterministic()).is_err());
1✔
318
    }
1✔
319
}
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

© 2025 Coveralls, Inc