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

stacks-network / stacks-core / 24140301216

08 Apr 2026 02:18PM UTC coverage: 46.817% (-38.9%) from 85.712%
24140301216

Pull #6959

github

279acf
web-flow
Merge efbee1783 into 882e27245
Pull Request #6959: Perf/cache epoch version in ClarityDatabase

66 of 149 new or added lines in 8 files covered. (44.3%)

85999 existing lines in 334 files now uncovered.

102056 of 217989 relevant lines covered (46.82%)

12315981.16 hits per line

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

26.88
/stackslib/src/util_lib/signed_structured_data.rs
1
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
2
// Copyright (C) 2020-2026 Stacks Open Internet Foundation
3
//
4
// This program is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// This program is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

17
use clarity::vm::types::TupleData;
18
use clarity::vm::Value;
19
use stacks_common::types::chainstate::StacksPrivateKey;
20
use stacks_common::types::PrivateKey;
21
use stacks_common::util::hash::Sha256Sum;
22
use stacks_common::util::secp256k1::{MessageSignature, Secp256k1PrivateKey};
23

24
use crate::chainstate::stacks::address::PoxAddress;
25

26
/// Message prefix for signed structured data. "SIP018" in ascii
27
pub const STRUCTURED_DATA_PREFIX: [u8; 6] = [0x53, 0x49, 0x50, 0x30, 0x31, 0x38];
28

29
pub fn structured_data_hash(value: Value) -> Sha256Sum {
133,422✔
30
    let mut bytes = vec![];
133,422✔
31
    value.serialize_write(&mut bytes).unwrap();
133,422✔
32
    Sha256Sum::from_data(bytes.as_slice())
133,422✔
33
}
133,422✔
34

35
/// Generate a message hash for signing structured Clarity data.
36
/// Reference [SIP018](https://github.com/stacksgov/sips/blob/main/sips/sip-018/sip-018-signed-structured-data.md) for more information.
37
pub fn structured_data_message_hash(structured_data: Value, domain: Value) -> Sha256Sum {
66,711✔
38
    let message = [
66,711✔
39
        STRUCTURED_DATA_PREFIX.as_ref(),
66,711✔
40
        structured_data_hash(domain).as_bytes(),
66,711✔
41
        structured_data_hash(structured_data).as_bytes(),
66,711✔
42
    ]
66,711✔
43
    .concat();
66,711✔
44

45
    Sha256Sum::from_data(&message)
66,711✔
46
}
66,711✔
47

48
/// Sign structured Clarity data with a given private key.
49
/// Reference [SIP018](https://github.com/stacksgov/sips/blob/main/sips/sip-018/sip-018-signed-structured-data.md) for more information.
UNCOV
50
pub fn sign_structured_data(
×
UNCOV
51
    structured_data: Value,
×
UNCOV
52
    domain: Value,
×
UNCOV
53
    private_key: &Secp256k1PrivateKey,
×
UNCOV
54
) -> Result<MessageSignature, &str> {
×
UNCOV
55
    let msg_hash = structured_data_message_hash(structured_data, domain);
×
UNCOV
56
    private_key.sign(msg_hash.as_bytes())
×
UNCOV
57
}
×
58

59
// Helper function to generate domain for structured data hash
60
pub fn make_structured_data_domain(name: &str, version: &str, chain_id: u32) -> Value {
66,711✔
61
    Value::Tuple(
66,711✔
62
        TupleData::from_data(vec![
66,711✔
63
            (
66,711✔
64
                "name".into(),
66,711✔
65
                Value::string_ascii_from_bytes(name.into()).unwrap(),
66,711✔
66
            ),
66,711✔
67
            (
66,711✔
68
                "version".into(),
66,711✔
69
                Value::string_ascii_from_bytes(version.into()).unwrap(),
66,711✔
70
            ),
66,711✔
71
            ("chain-id".into(), Value::UInt(chain_id.into())),
66,711✔
72
        ])
66,711✔
73
        .unwrap(),
66,711✔
74
    )
66,711✔
75
}
66,711✔
76

77
pub mod pox4 {
78
    use super::{
79
        make_structured_data_domain, structured_data_message_hash, MessageSignature, PoxAddress,
80
        PrivateKey, Sha256Sum, StacksPrivateKey, TupleData, Value,
81
    };
82
    define_named_enum!(Pox4SignatureTopic {
83
        StackStx("stack-stx"),
84
        AggregationCommit("agg-commit"),
85
        AggregationIncrease("agg-increase"),
86
        StackExtend("stack-extend"),
87
        StackIncrease("stack-increase"),
88
    });
89

90
    pub fn make_pox_4_signed_data_domain(chain_id: u32) -> Value {
8,058✔
91
        make_structured_data_domain("pox-4-signer", "1.0.0", chain_id)
8,058✔
92
    }
8,058✔
93

94
    #[cfg_attr(test, mutants::skip)]
95
    pub fn make_pox_4_signer_key_message_hash(
8,058✔
96
        pox_addr: &PoxAddress,
8,058✔
97
        reward_cycle: u128,
8,058✔
98
        topic: &Pox4SignatureTopic,
8,058✔
99
        chain_id: u32,
8,058✔
100
        period: u128,
8,058✔
101
        max_amount: u128,
8,058✔
102
        auth_id: u128,
8,058✔
103
    ) -> Sha256Sum {
8,058✔
104
        let domain_tuple = make_pox_4_signed_data_domain(chain_id);
8,058✔
105
        let data_tuple = Value::Tuple(
8,058✔
106
            TupleData::from_data(vec![
8,058✔
107
                (
8,058✔
108
                    "pox-addr".into(),
8,058✔
109
                    pox_addr
8,058✔
110
                        .clone()
8,058✔
111
                        .as_clarity_tuple()
8,058✔
112
                        .expect("Error creating signature hash - invalid PoX Address")
8,058✔
113
                        .into(),
8,058✔
114
                ),
8,058✔
115
                ("reward-cycle".into(), Value::UInt(reward_cycle)),
8,058✔
116
                ("period".into(), Value::UInt(period)),
8,058✔
117
                (
8,058✔
118
                    "topic".into(),
8,058✔
119
                    Value::string_ascii_from_bytes(topic.get_name_str().into()).unwrap(),
8,058✔
120
                ),
8,058✔
121
                ("auth-id".into(), Value::UInt(auth_id)),
8,058✔
122
                ("max-amount".into(), Value::UInt(max_amount)),
8,058✔
123
            ])
8,058✔
124
            .expect("Error creating signature hash"),
8,058✔
125
        );
8,058✔
126
        structured_data_message_hash(data_tuple, domain_tuple)
8,058✔
127
    }
8,058✔
128

129
    impl Into<Pox4SignatureTopic> for &'static str {
130
        #[cfg_attr(test, mutants::skip)]
131
        fn into(self) -> Pox4SignatureTopic {
×
132
            match self {
×
133
                "stack-stx" => Pox4SignatureTopic::StackStx,
×
134
                "agg-commit" => Pox4SignatureTopic::AggregationCommit,
×
135
                "stack-extend" => Pox4SignatureTopic::StackExtend,
×
136
                "stack-increase" => Pox4SignatureTopic::StackIncrease,
×
137
                _ => panic!("Invalid pox-4 signature topic"),
×
138
            }
139
        }
×
140
    }
141

142
    #[cfg_attr(test, mutants::skip)]
143
    pub fn make_pox_4_signer_key_signature(
8,058✔
144
        pox_addr: &PoxAddress,
8,058✔
145
        signer_key: &StacksPrivateKey,
8,058✔
146
        reward_cycle: u128,
8,058✔
147
        topic: &Pox4SignatureTopic,
8,058✔
148
        chain_id: u32,
8,058✔
149
        period: u128,
8,058✔
150
        max_amount: u128,
8,058✔
151
        auth_id: u128,
8,058✔
152
    ) -> Result<MessageSignature, &'static str> {
8,058✔
153
        let msg_hash = make_pox_4_signer_key_message_hash(
8,058✔
154
            pox_addr,
8,058✔
155
            reward_cycle,
8,058✔
156
            topic,
8,058✔
157
            chain_id,
8,058✔
158
            period,
8,058✔
159
            max_amount,
8,058✔
160
            auth_id,
8,058✔
161
        );
162
        signer_key.sign(msg_hash.as_bytes())
8,058✔
163
    }
8,058✔
164

165
    #[cfg(test)]
166
    mod tests {
167
        use clarity::vm::clarity::{ClarityConnection, TransactionConnection};
168
        use clarity::vm::costs::LimitedCostTracker;
169
        use clarity::vm::types::PrincipalData;
170
        use clarity::vm::ClarityVersion;
171
        use stacks_common::address::AddressHashMode;
172
        use stacks_common::consts::CHAIN_ID_TESTNET;
173
        use stacks_common::types::chainstate::StacksAddress;
174
        use stacks_common::util::hash::to_hex;
175
        use stacks_common::util::secp256k1::Secp256k1PublicKey;
176

177
        use super::*;
178
        use crate::chainstate::stacks::boot::contract_tests::ClarityTestSim;
179
        use crate::chainstate::stacks::boot::{POX_4_CODE, POX_4_NAME};
180
        use crate::util_lib::boot::boot_code_id;
181

UNCOV
182
        fn call_get_signer_message_hash(
×
UNCOV
183
            sim: &mut ClarityTestSim,
×
UNCOV
184
            pox_addr: &PoxAddress,
×
UNCOV
185
            reward_cycle: u128,
×
UNCOV
186
            topic: &Pox4SignatureTopic,
×
UNCOV
187
            lock_period: u128,
×
UNCOV
188
            sender: &PrincipalData,
×
UNCOV
189
            max_amount: u128,
×
UNCOV
190
            auth_id: u128,
×
UNCOV
191
        ) -> Vec<u8> {
×
UNCOV
192
            let pox_contract_id = boot_code_id(POX_4_NAME, false);
×
UNCOV
193
            sim.execute_next_block_as_conn(|conn| {
×
UNCOV
194
                let result = conn.with_readonly_clarity_env(
×
195
                    false,
196
                    CHAIN_ID_TESTNET,
UNCOV
197
                    sender.clone(),
×
UNCOV
198
                    None,
×
UNCOV
199
                    LimitedCostTracker::new_free(),
×
UNCOV
200
                    |exec_state, invoke_ctx| {
×
UNCOV
201
                        let program = format!(
×
202
                            "(get-signer-key-message-hash {} u{} \"{}\" u{} u{} u{})",
UNCOV
203
                            Value::Tuple(pox_addr.clone().as_clarity_tuple().unwrap()), //p
×
204
                            reward_cycle,
UNCOV
205
                            topic.get_name_str(),
×
206
                            lock_period,
207
                            max_amount,
208
                            auth_id,
209
                        );
UNCOV
210
                        exec_state.eval_read_only(invoke_ctx, &pox_contract_id, &program)
×
UNCOV
211
                    },
×
212
                );
UNCOV
213
                result
×
UNCOV
214
                    .expect("FATAL: failed to execute contract call")
×
UNCOV
215
                    .expect_buff(32)
×
UNCOV
216
                    .expect("FATAL: expected buff result")
×
UNCOV
217
            })
×
UNCOV
218
        }
×
219

220
        #[test]
UNCOV
221
        fn test_make_pox_4_message_hash() {
×
UNCOV
222
            let mut sim = ClarityTestSim::new();
×
UNCOV
223
            sim.epoch_bounds = vec![0, 1, 2];
×
224

225
            // Test setup
UNCOV
226
            sim.execute_next_block(|_env| {});
×
UNCOV
227
            sim.execute_next_block(|_env| {});
×
UNCOV
228
            sim.execute_next_block(|_env| {});
×
229

UNCOV
230
            let body = &*POX_4_CODE;
×
UNCOV
231
            let pox_contract_id = boot_code_id(POX_4_NAME, false);
×
232

UNCOV
233
            sim.execute_next_block_as_conn(|conn| {
×
UNCOV
234
                conn.as_transaction(|clarity_db| {
×
UNCOV
235
                    let clarity_version = ClarityVersion::Clarity2;
×
UNCOV
236
                    let (ast, analysis) = clarity_db
×
UNCOV
237
                        .analyze_smart_contract(&pox_contract_id, clarity_version, body)
×
UNCOV
238
                        .unwrap();
×
UNCOV
239
                    clarity_db
×
UNCOV
240
                        .initialize_smart_contract(
×
UNCOV
241
                            &pox_contract_id,
×
UNCOV
242
                            clarity_version,
×
UNCOV
243
                            &ast,
×
UNCOV
244
                            body,
×
UNCOV
245
                            None,
×
246
                            |_, _| None,
UNCOV
247
                            None,
×
248
                        )
UNCOV
249
                        .unwrap();
×
UNCOV
250
                    clarity_db
×
UNCOV
251
                        .save_analysis(&pox_contract_id, &analysis)
×
UNCOV
252
                        .expect("FATAL: failed to store contract analysis");
×
UNCOV
253
                });
×
UNCOV
254
            });
×
255

UNCOV
256
            let pubkey = Secp256k1PublicKey::new();
×
UNCOV
257
            let stacks_addr = StacksAddress::p2pkh(false, &pubkey);
×
UNCOV
258
            let pubkey = Secp256k1PublicKey::new();
×
UNCOV
259
            let principal = PrincipalData::from(stacks_addr.clone());
×
UNCOV
260
            let pox_addr = PoxAddress::standard_burn_address(false);
×
UNCOV
261
            let reward_cycle: u128 = 1;
×
UNCOV
262
            let topic = Pox4SignatureTopic::StackStx;
×
UNCOV
263
            let lock_period = 12;
×
UNCOV
264
            let auth_id = 111;
×
UNCOV
265
            let max_amount = u128::MAX;
×
266

UNCOV
267
            let expected_hash_vec = make_pox_4_signer_key_message_hash(
×
UNCOV
268
                &pox_addr,
×
UNCOV
269
                reward_cycle,
×
UNCOV
270
                &Pox4SignatureTopic::StackStx,
×
271
                CHAIN_ID_TESTNET,
UNCOV
272
                lock_period,
×
UNCOV
273
                max_amount,
×
UNCOV
274
                auth_id,
×
275
            );
UNCOV
276
            let expected_hash = expected_hash_vec.as_bytes();
×
277

278
            // Test 1: valid result
279

UNCOV
280
            let result = call_get_signer_message_hash(
×
UNCOV
281
                &mut sim,
×
UNCOV
282
                &pox_addr,
×
UNCOV
283
                reward_cycle,
×
UNCOV
284
                &topic,
×
UNCOV
285
                lock_period,
×
UNCOV
286
                &principal,
×
UNCOV
287
                max_amount,
×
UNCOV
288
                auth_id,
×
289
            );
UNCOV
290
            assert_eq!(expected_hash.clone(), result.as_slice());
×
291

292
            // Test 2: invalid pox address
UNCOV
293
            let other_pox_address = PoxAddress::from_legacy(
×
UNCOV
294
                AddressHashMode::SerializeP2PKH,
×
UNCOV
295
                StacksAddress::p2pkh(false, &Secp256k1PublicKey::new())
×
UNCOV
296
                    .destruct()
×
UNCOV
297
                    .1,
×
298
            );
UNCOV
299
            let result = call_get_signer_message_hash(
×
UNCOV
300
                &mut sim,
×
UNCOV
301
                &other_pox_address,
×
UNCOV
302
                reward_cycle,
×
UNCOV
303
                &topic,
×
UNCOV
304
                lock_period,
×
UNCOV
305
                &principal,
×
UNCOV
306
                max_amount,
×
UNCOV
307
                auth_id,
×
308
            );
UNCOV
309
            assert_ne!(expected_hash.clone(), result.as_slice());
×
310

311
            // Test 3: invalid reward cycle
UNCOV
312
            let result = call_get_signer_message_hash(
×
UNCOV
313
                &mut sim,
×
UNCOV
314
                &pox_addr,
×
315
                0,
UNCOV
316
                &topic,
×
UNCOV
317
                lock_period,
×
UNCOV
318
                &principal,
×
UNCOV
319
                max_amount,
×
UNCOV
320
                auth_id,
×
321
            );
UNCOV
322
            assert_ne!(expected_hash.clone(), result.as_slice());
×
323

324
            // Test 4: invalid topic
UNCOV
325
            let result = call_get_signer_message_hash(
×
UNCOV
326
                &mut sim,
×
UNCOV
327
                &pox_addr,
×
UNCOV
328
                reward_cycle,
×
UNCOV
329
                &Pox4SignatureTopic::AggregationCommit,
×
UNCOV
330
                lock_period,
×
UNCOV
331
                &principal,
×
UNCOV
332
                max_amount,
×
UNCOV
333
                auth_id,
×
334
            );
UNCOV
335
            assert_ne!(expected_hash.clone(), result.as_slice());
×
336

337
            // Test 5: invalid lock period
UNCOV
338
            let result = call_get_signer_message_hash(
×
UNCOV
339
                &mut sim,
×
UNCOV
340
                &pox_addr,
×
UNCOV
341
                reward_cycle,
×
UNCOV
342
                &topic,
×
343
                0,
UNCOV
344
                &principal,
×
UNCOV
345
                max_amount,
×
UNCOV
346
                auth_id,
×
347
            );
UNCOV
348
            assert_ne!(expected_hash.clone(), result.as_slice());
×
349

350
            // Test 5: invalid max amount
UNCOV
351
            let result = call_get_signer_message_hash(
×
UNCOV
352
                &mut sim,
×
UNCOV
353
                &pox_addr,
×
UNCOV
354
                reward_cycle,
×
UNCOV
355
                &topic,
×
UNCOV
356
                lock_period,
×
UNCOV
357
                &principal,
×
358
                1010101,
UNCOV
359
                auth_id,
×
360
            );
UNCOV
361
            assert_ne!(expected_hash.clone(), result.as_slice());
×
362

363
            // Test 6: invalid auth id
UNCOV
364
            let result = call_get_signer_message_hash(
×
UNCOV
365
                &mut sim,
×
UNCOV
366
                &pox_addr,
×
UNCOV
367
                reward_cycle,
×
UNCOV
368
                &topic,
×
UNCOV
369
                lock_period,
×
UNCOV
370
                &principal,
×
UNCOV
371
                max_amount,
×
372
                10101,
373
            );
UNCOV
374
            assert_ne!(expected_hash.clone(), result.as_slice());
×
UNCOV
375
        }
×
376

377
        #[test]
378
        /// Fixture message hash to test against in other libraries
UNCOV
379
        fn test_sig_hash_fixture() {
×
UNCOV
380
            let fixture = "ec5b88aa81a96a6983c26cdba537a13d253425348ffc0ba6b07130869b025a2d";
×
UNCOV
381
            let pox_addr = PoxAddress::standard_burn_address(false);
×
UNCOV
382
            let pubkey_hex = "0206952cd8813a64f7b97144c984015490a8f9c5778e8f928fbc8aa6cbf02f48e6";
×
UNCOV
383
            let pubkey = Secp256k1PublicKey::from_hex(pubkey_hex).unwrap();
×
UNCOV
384
            let reward_cycle: u128 = 1;
×
UNCOV
385
            let lock_period = 12;
×
UNCOV
386
            let auth_id = 111;
×
UNCOV
387
            let max_amount = u128::MAX;
×
388

UNCOV
389
            let message_hash = make_pox_4_signer_key_message_hash(
×
UNCOV
390
                &pox_addr,
×
UNCOV
391
                reward_cycle,
×
UNCOV
392
                &Pox4SignatureTopic::StackStx,
×
393
                CHAIN_ID_TESTNET,
UNCOV
394
                lock_period,
×
UNCOV
395
                max_amount,
×
UNCOV
396
                auth_id,
×
397
            );
398

UNCOV
399
            assert_eq!(to_hex(message_hash.as_bytes()), fixture);
×
UNCOV
400
        }
×
401
    }
402
}
403

404
#[cfg(test)]
405
mod test {
406
    use clarity::vm::types::{TupleData, Value};
407
    use stacks_common::consts::CHAIN_ID_MAINNET;
408
    use stacks_common::util::hash::to_hex;
409

410
    use super::*;
411

412
    /// [SIP18 test vectors](https://github.com/stacksgov/sips/blob/main/sips/sip-018/sip-018-signed-structured-data.md)
413
    #[test]
UNCOV
414
    fn test_sip18_ref_structured_data_hash() {
×
UNCOV
415
        let value = Value::string_ascii_from_bytes("Hello World".into()).unwrap();
×
UNCOV
416
        let msg_hash = structured_data_hash(value);
×
UNCOV
417
        assert_eq!(
×
UNCOV
418
            to_hex(msg_hash.as_bytes()),
×
419
            "5297eef9765c466d945ad1cb2c81b30b9fed6c165575dc9226e9edf78b8cd9e8"
420
        )
UNCOV
421
    }
×
422

423
    /// [SIP18 test vectors](https://github.com/stacksgov/sips/blob/main/sips/sip-018/sip-018-signed-structured-data.md)
424
    #[test]
UNCOV
425
    fn test_sip18_ref_message_hashing() {
×
UNCOV
426
        let domain = Value::Tuple(
×
UNCOV
427
            TupleData::from_data(vec![
×
UNCOV
428
                (
×
UNCOV
429
                    "name".into(),
×
UNCOV
430
                    Value::string_ascii_from_bytes("Test App".into()).unwrap(),
×
UNCOV
431
                ),
×
UNCOV
432
                (
×
UNCOV
433
                    "version".into(),
×
UNCOV
434
                    Value::string_ascii_from_bytes("1.0.0".into()).unwrap(),
×
UNCOV
435
                ),
×
UNCOV
436
                ("chain-id".into(), Value::UInt(CHAIN_ID_MAINNET.into())),
×
UNCOV
437
            ])
×
UNCOV
438
            .unwrap(),
×
UNCOV
439
        );
×
UNCOV
440
        let data = Value::string_ascii_from_bytes("Hello World".into()).unwrap();
×
441

UNCOV
442
        let msg_hash = structured_data_message_hash(data, domain);
×
443

UNCOV
444
        assert_eq!(
×
UNCOV
445
            to_hex(msg_hash.as_bytes()),
×
446
            "1bfdab6d4158313ce34073fbb8d6b0fc32c154d439def12247a0f44bb2225259"
447
        );
UNCOV
448
    }
×
449

450
    /// [SIP18 test vectors](https://github.com/stacksgov/sips/blob/main/sips/sip-018/sip-018-signed-structured-data.md)
451
    #[test]
UNCOV
452
    fn test_sip18_ref_signing() {
×
UNCOV
453
        let key = Secp256k1PrivateKey::from_hex(
×
UNCOV
454
            "753b7cc01a1a2e86221266a154af739463fce51219d97e4f856cd7200c3bd2a601",
×
455
        )
UNCOV
456
        .unwrap();
×
UNCOV
457
        let domain = Value::Tuple(
×
UNCOV
458
            TupleData::from_data(vec![
×
UNCOV
459
                (
×
UNCOV
460
                    "name".into(),
×
UNCOV
461
                    Value::string_ascii_from_bytes("Test App".into()).unwrap(),
×
UNCOV
462
                ),
×
UNCOV
463
                (
×
UNCOV
464
                    "version".into(),
×
UNCOV
465
                    Value::string_ascii_from_bytes("1.0.0".into()).unwrap(),
×
UNCOV
466
                ),
×
UNCOV
467
                ("chain-id".into(), Value::UInt(CHAIN_ID_MAINNET.into())),
×
UNCOV
468
            ])
×
UNCOV
469
            .unwrap(),
×
UNCOV
470
        );
×
UNCOV
471
        let data = Value::string_ascii_from_bytes("Hello World".into()).unwrap();
×
UNCOV
472
        let signature =
×
UNCOV
473
            sign_structured_data(data, domain, &key).expect("Failed to sign structured data");
×
474

UNCOV
475
        let signature_rsv = signature.to_rsv();
×
476

UNCOV
477
        assert_eq!(to_hex(signature_rsv.as_slice()), "8b94e45701d857c9f1d1d70e8b2ca076045dae4920fb0160be0642a68cd78de072ab527b5c5277a593baeb2a8b657c216b99f7abb5d14af35b4bf12ba6460ba401");
×
UNCOV
478
    }
×
479

480
    #[test]
UNCOV
481
    fn test_prefix_bytes() {
×
UNCOV
482
        let hex = to_hex(STRUCTURED_DATA_PREFIX.as_ref());
×
UNCOV
483
        assert_eq!(hex, "534950303138");
×
UNCOV
484
    }
×
485
}
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