• 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

98.39
/rust/cbork-utils/src/deterministic_helper.rs
1
//! CBOR decoding helper functions with deterministic encoding validation.
2
//!
3
//! Based on RFC 8949 Section 4.2 "Deterministically Encoded CBOR"
4
//! Rules for deterministic encoding:
5
//! 1. Integers must use the smallest possible encoding
6
//! 2. Lengths of arrays, maps, strings must use the smallest possible encoding
7
//! 3. Indefinite-length items are not allowed
8
//! 4. Keys in every map must be sorted in lexicographic order
9
//! 5. Duplicate keys in maps are not allowed
10
//! 6. Floating point values must use smallest possible encoding
11
//! 7. Non-finite floating point values are not allowed (NaN, infinite)
12

13
/// Maximum value that can be encoded in a 5-bit additional info field
14
/// RFC 8949 Section 4.2.1: "0 to 23 must be expressed in the same byte as the major type"
15
/// Values 0-23 are encoded directly in the additional info field of the initial byte
16
pub(crate) const CBOR_MAX_TINY_VALUE: u64 = 23;
17

18
/// Extracts the declared length from a CBOR data item according to RFC 8949 encoding
19
/// rules.
20
///
21
/// This function analyzes the major type and additional information in the CBOR initial
22
/// byte to determine if the data item has a declared length and what that length is.
23
///
24
/// ## CBOR Major Types and Length Semantics (RFC 8949 Section 3):
25
///
26
/// - **Major Type 0/1 (Unsigned/Negative Integers)**: No length concept - the value IS
27
///   the data
28
/// - **Major Type 2 (Byte String)**: Length indicates number of bytes in the string
29
/// - **Major Type 3 (Text String)**: Length indicates number of bytes in UTF-8 encoding
30
/// - **Major Type 4 (Array)**: Length indicates number of data items (elements) in the
31
///   array
32
/// - **Major Type 5 (Map)**: Length indicates number of key-value pairs in the map
33
/// - **Major Type 6 (Semantic Tag)**: Tags the following data item, length from tagged
34
///   content
35
/// - **Major Type 7 (Primitives)**: No length for simple values, floats, etc.
36
///
37
/// ## Errors
38
pub fn get_declared_length(bytes: &[u8]) -> Result<Option<usize>, minicbor::decode::Error> {
193✔
39
    let mut decoder = minicbor::Decoder::new(bytes);
193✔
40

41
    // Extract major type from high 3 bits of initial byte (RFC 8949 Section 3.1)
42
    match bytes.first().map(|&b| b >> 5) {
193✔
43
        Some(7 | 0 | 1 | 4 | 5 | 6) => Ok(None),
28✔
44
        Some(2) => {
45
            // Read length for byte string header
46
            let len = decoder.bytes()?;
16✔
47
            Ok(Some(len.len()))
16✔
48
        },
49
        Some(3) => {
50
            // Read length for text string header
51
            let len = decoder.str()?;
149✔
52
            Ok(Some(len.len()))
149✔
53
        },
54

NEW
55
        _ => Err(minicbor::decode::Error::message("Invalid type")),
×
56
    }
57
}
193✔
58

59
/// Returns the size of the CBOR header in bytes, based on RFC 8949 encoding rules.
60
///
61
/// CBOR encodes data items with a header that consists of:
62
/// 1. An initial byte containing:
63
///    - Major type (3 most significant bits)
64
///    - Additional information (5 least significant bits)
65
/// 2. Optional following bytes based on the additional information value
66
///
67
/// This function calculates only the size of the header itself, not including
68
/// any data that follows the header. It works with all CBOR major types:
69
/// - 0: Unsigned integer
70
/// - 1: Negative integer
71
/// - 2: Byte string
72
/// - 3: Text string
73
/// - 4: Array
74
/// - 5: Map
75
/// - 6: Tag
76
/// - 7: Simple/floating-point values
77
///
78
/// For deterministically encoded CBOR (as specified in RFC 8949 Section 4.2),
79
/// indefinite length items are not allowed, so this function will return an error
80
/// when encountering additional information value 31.
81
///
82
/// # Arguments
83
/// * `bytes` - A byte slice containing CBOR-encoded data
84
///
85
/// # Returns
86
/// * `Ok(usize)` - The size of the CBOR header in bytes
87
/// * `Err(DeterministicError)` - If the input is invalid or uses indefinite length
88
///   encoding
89
///
90
/// # Errors
91
/// Returns an error if:
92
/// - The input is empty
93
/// - The input uses indefinite length encoding (additional info = 31)
94
/// - The additional information value is invalid
95
pub fn get_cbor_header_size(bytes: &[u8]) -> Result<usize, minicbor::decode::Error> {
172✔
96
    // Extract the first byte which contains both major type and additional info
97
    let first_byte = bytes
172✔
98
        .first()
172✔
99
        .copied()
172✔
100
        .ok_or_else(|| minicbor::decode::Error::message("Empty cbor data"))?;
172✔
101
    // Major type is in the high 3 bits (not used in this function but noted for clarity)
102
    // let major_type = first_byte >> 5;
103
    // Additional info is in the low 5 bits and determines header size
104
    let additional_info = first_byte & 0b0001_1111;
171✔
105

106
    // Calculate header size based on additional info value
107
    match additional_info {
171✔
108
        // Values 0-23 are encoded directly in the additional info bits
109
        // Header is just the initial byte
110
        0..=23 => Ok(1),
171✔
111

112
        // Value 24 means the actual value is in the next 1 byte
113
        // Header is 2 bytes (initial byte + 1 byte)
114
        24 => Ok(2),
1✔
115

116
        // Value 25 means the actual value is in the next 2 bytes
117
        // Header is 3 bytes (initial byte + 2 bytes)
118
        25 => Ok(3),
2✔
119

120
        // Value 26 means the actual value is in the next 4 bytes
121
        // Header is 5 bytes (initial byte + 4 bytes)
122
        26 => Ok(5),
1✔
123

124
        // Value 27 means the actual value is in the next 8 bytes
125
        // Header is 9 bytes (initial byte + 8 bytes)
126
        27 => Ok(9),
1✔
127
        // Value 31 indicates indefinite length, which is not allowed in
128
        // deterministic encoding per RFC 8949 section 4.2.1
129
        31 => {
130
            Err(minicbor::decode::Error::message(
1✔
131
                "Cannot determine size of indefinite length item",
1✔
132
            ))
1✔
133
        },
134

135
        // Values 28-30 are reserved in RFC 8949 and not valid in current CBOR
136
        _ => {
137
            Err(minicbor::decode::Error::message(
1✔
138
                "Invalid additional info in CBOR header",
1✔
139
            ))
1✔
140
        },
141
    }
142
}
172✔
143

144
#[cfg(test)]
145
mod tests {
146
    use super::*;
147

148
    /// Test `get_declared_length` for all CBOR major types per RFC 8949
149
    #[test]
150
    fn test_get_declared_length() {
1✔
151
        // Example 1: Empty byte string
152
        // Encoding: [0x40]
153
        // - 0x40 = 0b010_00000 (major type 2, additional info 0)
154
        // - Length: 0 bytes
155
        // - Content: none
156
        let empty_bytes = vec![0x40];
1✔
157

158
        let declared_length = get_declared_length(&empty_bytes).unwrap().unwrap();
1✔
159

160
        assert_eq!(declared_length, 0);
1✔
161

162
        // Example 2: 2-byte string with immediate length encoding
163
        // Encoding: [0x42, 0x01, 0x02]
164
        // - 0x42 = 0b010_00010 (major type 2, additional info 2)
165
        // - Length: 2 bytes (encoded immediately in additional info)
166
        // - Content: [0x01, 0x02]
167
        let short_bytes = vec![0x42, 0x01, 0x02];
1✔
168

169
        let declared_length = get_declared_length(&short_bytes).unwrap().unwrap();
1✔
170

171
        assert_eq!(declared_length, 2);
1✔
172

173
        // Example 3: 24-byte string requiring uint8 length encoding
174
        // Encoding: [0x58, 0x18, 0x01, 0x02, ..., 0x18]
175
        // - 0x58 = 0b010_11000 (major type 2, additional info 24)
176
        // - Length: 24 (encoded as uint8 in next byte: 0x18 = 24)
177
        // - Content: 24 bytes [0x01, 0x02, ..., 0x18]
178
        let mut medium_bytes = vec![0x58, 0x18]; // Header: byte string, uint8 length 24
1✔
179
        medium_bytes.extend((1..=24).collect::<Vec<u8>>()); // Content: 24 bytes
1✔
180

181
        let declared_length = get_declared_length(&medium_bytes).unwrap().unwrap();
1✔
182
        assert_eq!(declared_length, 24);
1✔
183

184
        // Example 4: 256-byte string requiring uint16 length encoding
185
        // Encoding: [0x59, 0x01, 0x00, 0x00, 0x00, ..., 0xFF]
186
        // - 0x59 = 0b010_11001 (major type 2, additional info 25)
187
        // - Length: 256 (encoded as uint16 big-endian: [0x01, 0x00])
188
        // - Content: 256 bytes [0x00, 0x00, ..., 0xFF]
189
        let mut large_bytes = vec![0x59, 0x01, 0x00]; // Header: byte string, uint16 length 256
1✔
190
        large_bytes.extend(vec![0x00; 256]); // Content: 256 zero bytes
1✔
191

192
        let declared_length = get_declared_length(&large_bytes).unwrap().unwrap();
1✔
193
        assert_eq!(declared_length, 256);
1✔
194
    }
1✔
195

196
    #[test]
197
    fn test_get_cbor_header_size() {
1✔
198
        // Test direct values (additional info 0-23)
199
        assert_eq!(get_cbor_header_size(&[0b000_00000]).unwrap(), 1); // Major type 0, value 0
1✔
200
        assert_eq!(get_cbor_header_size(&[0b001_10111]).unwrap(), 1); // Major type 1, value 23
1✔
201

202
        // Test 1-byte uint (additional info 24)
203
        assert_eq!(get_cbor_header_size(&[0b010_11000, 0x42]).unwrap(), 2); // Major type 2
1✔
204

205
        // Test 2-byte uint (additional info 25)
206
        assert_eq!(get_cbor_header_size(&[0b011_11001, 0x12, 0x34]).unwrap(), 3); // Major type 3
1✔
207

208
        // Test 4-byte uint (additional info 26)
209
        assert_eq!(
1✔
210
            get_cbor_header_size(&[0b100_11010, 0x12, 0x34, 0x56, 0x78]).unwrap(),
1✔
211
            5
212
        ); // Major type 4
213

214
        // Test 8-byte uint (additional info 27)
215
        assert_eq!(
1✔
216
            get_cbor_header_size(&[0b101_11011, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF])
1✔
217
                .unwrap(),
1✔
218
            9
219
        ); // Major type 5
220

221
        // Error cases
222
        // Empty input
223
        assert!(get_cbor_header_size(&[]).is_err());
1✔
224

225
        // Indefinite length (additional info 31)
226
        let result = get_cbor_header_size(&[0b110_11111]);
1✔
227
        assert!(result.is_err());
1✔
228

229
        // Small map (size 1) - additional info 1
230
        assert_eq!(get_cbor_header_size(&[0b101_00001]).unwrap(), 1); // Map with 1 pair
1✔
231

232
        // Large map (size 65535) - additional info 25 (2-byte uint follows)
233
        assert_eq!(get_cbor_header_size(&[0b101_11001, 0xFF, 0xFF]).unwrap(), 3); // Map with 65535 pairs
1✔
234

235
        // Reserved values (additional info 28-30)
236
        assert!(get_cbor_header_size(&[0b111_11100]).is_err()); // Major type 7, value 28
1✔
237
    }
1✔
238
}
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