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

stacks-network / stacks-core / 30109040441-1

24 Jul 2026 04:26PM UTC coverage: 86.513% (+0.8%) from 85.712%
30109040441-1

Pull #7257

github

906082
web-flow
Merge 1c21b14c5 into 2b72f16d5
Pull Request #7257: tests: improve clarity vm tests

232 of 239 new or added lines in 6 files covered. (97.07%)

12108 existing lines in 169 files now uncovered.

200275 of 231496 relevant lines covered (86.51%)

19585538.68 hits per line

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

97.83
/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::{ClarityName, 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 {
198,081✔
30
    let mut bytes = vec![];
198,081✔
31
    value.serialize_write(&mut bytes).unwrap();
198,081✔
32
    Sha256Sum::from_data(bytes.as_slice())
198,081✔
33
}
198,081✔
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 {
99,040✔
38
    let message = [
99,040✔
39
        STRUCTURED_DATA_PREFIX.as_ref(),
99,040✔
40
        structured_data_hash(domain).as_bytes(),
99,040✔
41
        structured_data_hash(structured_data).as_bytes(),
99,040✔
42
    ]
99,040✔
43
    .concat();
99,040✔
44

45
    Sha256Sum::from_data(&message)
99,040✔
46
}
99,040✔
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.
50
pub fn sign_structured_data(
1✔
51
    structured_data: Value,
1✔
52
    domain: Value,
1✔
53
    private_key: &Secp256k1PrivateKey,
1✔
54
) -> Result<MessageSignature, &str> {
1✔
55
    let msg_hash = structured_data_message_hash(structured_data, domain);
1✔
56
    private_key.sign(msg_hash.as_bytes())
1✔
57
}
1✔
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 {
99,038✔
61
    Value::Tuple(
99,038✔
62
        TupleData::from_data(vec![
99,038✔
63
            (
99,038✔
64
                ClarityName::from_literal("name"),
99,038✔
65
                Value::string_ascii_from_bytes(name.into()).unwrap(),
99,038✔
66
            ),
99,038✔
67
            (
99,038✔
68
                ClarityName::from_literal("version"),
99,038✔
69
                Value::string_ascii_from_bytes(version.into()).unwrap(),
99,038✔
70
            ),
99,038✔
71
            (
99,038✔
72
                ClarityName::from_literal("chain-id"),
99,038✔
73
                Value::UInt(chain_id.into()),
99,038✔
74
            ),
99,038✔
75
        ])
99,038✔
76
        .unwrap(),
99,038✔
77
    )
99,038✔
78
}
99,038✔
79

80
pub mod pox4 {
81
    use clarity::vm::ClarityName;
82

83
    use super::{
84
        make_structured_data_domain, structured_data_message_hash, MessageSignature, PoxAddress,
85
        PrivateKey, Sha256Sum, StacksPrivateKey, TupleData, Value,
86
    };
87
    define_named_enum!(Pox4SignatureTopic {
88
        StackStx("stack-stx"),
89
        AggregationCommit("agg-commit"),
90
        AggregationIncrease("agg-increase"),
91
        StackExtend("stack-extend"),
92
        StackIncrease("stack-increase"),
93
    });
94

95
    pub fn make_pox_4_signed_data_domain(chain_id: u32) -> Value {
15,448✔
96
        make_structured_data_domain("pox-4-signer", "1.0.0", chain_id)
15,448✔
97
    }
15,448✔
98

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

140
    impl Into<Pox4SignatureTopic> for &'static str {
141
        #[cfg_attr(test, mutants::skip)]
UNCOV
142
        fn into(self) -> Pox4SignatureTopic {
×
UNCOV
143
            match self {
×
UNCOV
144
                "stack-stx" => Pox4SignatureTopic::StackStx,
×
UNCOV
145
                "agg-commit" => Pox4SignatureTopic::AggregationCommit,
×
UNCOV
146
                "stack-extend" => Pox4SignatureTopic::StackExtend,
×
UNCOV
147
                "stack-increase" => Pox4SignatureTopic::StackIncrease,
×
UNCOV
148
                _ => panic!("Invalid pox-4 signature topic"),
×
149
            }
UNCOV
150
        }
×
151
    }
152

153
    #[cfg_attr(test, mutants::skip)]
154
    pub fn make_pox_4_signer_key_signature(
15,366✔
155
        pox_addr: &PoxAddress,
15,366✔
156
        signer_key: &StacksPrivateKey,
15,366✔
157
        reward_cycle: u128,
15,366✔
158
        topic: &Pox4SignatureTopic,
15,366✔
159
        chain_id: u32,
15,366✔
160
        period: u128,
15,366✔
161
        max_amount: u128,
15,366✔
162
        auth_id: u128,
15,366✔
163
    ) -> Result<MessageSignature, &'static str> {
15,366✔
164
        let msg_hash = make_pox_4_signer_key_message_hash(
15,366✔
165
            pox_addr,
15,366✔
166
            reward_cycle,
15,366✔
167
            topic,
15,366✔
168
            chain_id,
15,366✔
169
            period,
15,366✔
170
            max_amount,
15,366✔
171
            auth_id,
15,366✔
172
        );
173
        signer_key.sign(msg_hash.as_bytes())
15,366✔
174
    }
15,366✔
175

176
    #[cfg(test)]
177
    mod tests {
178
        use clarity::vm::clarity::{ClarityConnection, TransactionConnection};
179
        use clarity::vm::costs::LimitedCostTracker;
180
        use clarity::vm::resource_limiter::ResourceBudget;
181
        use clarity::vm::types::PrincipalData;
182
        use clarity::vm::ClarityVersion;
183
        use stacks_common::address::AddressHashMode;
184
        use stacks_common::consts::CHAIN_ID_TESTNET;
185
        use stacks_common::types::chainstate::StacksAddress;
186
        use stacks_common::util::hash::to_hex;
187
        use stacks_common::util::secp256k1::Secp256k1PublicKey;
188

189
        use super::*;
190
        use crate::chainstate::stacks::boot::contract_tests::ClarityTestSim;
191
        use crate::chainstate::stacks::boot::{POX_4_CODE, POX_4_NAME};
192
        use crate::util_lib::boot::boot_code_id;
193

7✔
194
        fn call_get_signer_message_hash(
7✔
195
            sim: &mut ClarityTestSim,
7✔
196
            pox_addr: &PoxAddress,
7✔
197
            reward_cycle: u128,
7✔
198
            topic: &Pox4SignatureTopic,
7✔
199
            lock_period: u128,
7✔
200
            sender: &PrincipalData,
7✔
201
            max_amount: u128,
7✔
202
            auth_id: u128,
7✔
203
        ) -> Vec<u8> {
7✔
204
            let pox_contract_id = boot_code_id(POX_4_NAME, false);
7✔
205
            sim.execute_next_block_as_conn(|conn| {
7✔
206
                let result = conn.with_readonly_clarity_env(
207
                    false,
208
                    CHAIN_ID_TESTNET,
7✔
209
                    sender.clone(),
7✔
210
                    None,
7✔
211
                    LimitedCostTracker::new_free(),
7✔
212
                    |exec_state, invoke_ctx| {
7✔
213
                        let program = format!(
214
                            "(get-signer-key-message-hash {} u{} \"{}\" u{} u{} u{})",
7✔
215
                            Value::Tuple(pox_addr.clone().as_clarity_tuple().unwrap()), //p
216
                            reward_cycle,
7✔
217
                            topic.get_name_str(),
218
                            lock_period,
219
                            max_amount,
220
                            auth_id,
221
                        );
7✔
222
                        exec_state.eval_read_only(invoke_ctx, &pox_contract_id, &program)
7✔
223
                    },
224
                );
7✔
225
                result
7✔
226
                    .expect("FATAL: failed to execute contract call")
7✔
227
                    .expect_buff(32)
7✔
228
                    .expect("FATAL: expected buff result")
7✔
229
            })
7✔
230
        }
231

232
        #[test]
1✔
233
        fn test_make_pox_4_message_hash() {
1✔
234
            let mut sim = ClarityTestSim::new();
1✔
235
            sim.epoch_bounds = vec![0, 1, 2];
236

237
            // Test setup
1✔
238
            sim.execute_next_block(|_env| {});
1✔
239
            sim.execute_next_block(|_env| {});
1✔
240
            sim.execute_next_block(|_env| {});
241

1✔
242
            let body = &*POX_4_CODE;
1✔
243
            let pox_contract_id = boot_code_id(POX_4_NAME, false);
244

1✔
245
            sim.execute_next_block_as_conn(|conn| {
1✔
246
                conn.as_transaction(|clarity_db| {
1✔
247
                    let clarity_version = ClarityVersion::Clarity2;
1✔
248
                    let (ast, analysis) = clarity_db
1✔
249
                        .analyze_smart_contract(
1✔
250
                            &pox_contract_id,
1✔
251
                            clarity_version,
1✔
252
                            body,
1✔
253
                            &ResourceBudget::unlimited(),
1✔
254
                        )
1✔
255
                        .unwrap();
1✔
256
                    clarity_db
1✔
257
                        .initialize_smart_contract(
258
                            &pox_contract_id,
1✔
259
                            clarity_version,
260
                            &ast,
1✔
261
                            body,
1✔
262
                            None,
1✔
263
                            |_, _| None,
1✔
264
                            &ResourceBudget::unlimited(),
1✔
265
                        )
1✔
266
                        .unwrap();
267
                    clarity_db
1✔
268
                        .save_analysis(&pox_contract_id, &analysis)
1✔
269
                        .expect("FATAL: failed to store contract analysis");
1✔
270
                });
1✔
271
            });
1✔
272

1✔
273
            let pubkey = Secp256k1PublicKey::new();
1✔
274
            let stacks_addr = StacksAddress::p2pkh(false, &pubkey);
1✔
275
            let pubkey = Secp256k1PublicKey::new();
1✔
276
            let principal = PrincipalData::from(stacks_addr.clone());
1✔
277
            let pox_addr = PoxAddress::standard_burn_address(false);
278
            let reward_cycle: u128 = 1;
1✔
279
            let topic = Pox4SignatureTopic::StackStx;
1✔
280
            let lock_period = 12;
1✔
281
            let auth_id = 111;
1✔
282
            let max_amount = u128::MAX;
283

1✔
284
            let expected_hash_vec = make_pox_4_signer_key_message_hash(
1✔
285
                &pox_addr,
1✔
286
                reward_cycle,
287
                &Pox4SignatureTopic::StackStx,
1✔
288
                CHAIN_ID_TESTNET,
289
                lock_period,
290
                max_amount,
291
                auth_id,
1✔
292
            );
1✔
293
            let expected_hash = expected_hash_vec.as_bytes();
1✔
294

1✔
295
            // Test 1: valid result
1✔
296

1✔
297
            let result = call_get_signer_message_hash(
1✔
298
                &mut sim,
1✔
299
                &pox_addr,
1✔
300
                reward_cycle,
301
                &topic,
1✔
302
                lock_period,
303
                &principal,
304
                max_amount,
1✔
305
                auth_id,
1✔
306
            );
1✔
307
            assert_eq!(expected_hash.clone(), result.as_slice());
1✔
308

1✔
309
            // Test 2: invalid pox address
310
            let other_pox_address = PoxAddress::from_legacy(
1✔
311
                AddressHashMode::SerializeP2PKH,
1✔
312
                StacksAddress::p2pkh(false, &Secp256k1PublicKey::new())
1✔
313
                    .destruct()
1✔
314
                    .1,
1✔
315
            );
1✔
316
            let result = call_get_signer_message_hash(
1✔
317
                &mut sim,
1✔
318
                &other_pox_address,
1✔
319
                reward_cycle,
320
                &topic,
1✔
321
                lock_period,
322
                &principal,
323
                max_amount,
1✔
324
                auth_id,
1✔
325
            );
1✔
326
            assert_ne!(expected_hash.clone(), result.as_slice());
327

1✔
328
            // Test 3: invalid reward cycle
1✔
329
            let result = call_get_signer_message_hash(
1✔
330
                &mut sim,
1✔
331
                &pox_addr,
1✔
332
                0,
333
                &topic,
1✔
334
                lock_period,
335
                &principal,
336
                max_amount,
1✔
337
                auth_id,
1✔
338
            );
1✔
339
            assert_ne!(expected_hash.clone(), result.as_slice());
1✔
340

1✔
341
            // Test 4: invalid topic
1✔
342
            let result = call_get_signer_message_hash(
1✔
343
                &mut sim,
1✔
344
                &pox_addr,
1✔
345
                reward_cycle,
346
                &Pox4SignatureTopic::AggregationCommit,
1✔
347
                lock_period,
348
                &principal,
349
                max_amount,
1✔
350
                auth_id,
1✔
351
            );
1✔
352
            assert_ne!(expected_hash.clone(), result.as_slice());
1✔
353

1✔
354
            // Test 5: invalid lock period
355
            let result = call_get_signer_message_hash(
1✔
356
                &mut sim,
1✔
357
                &pox_addr,
1✔
358
                reward_cycle,
359
                &topic,
1✔
360
                0,
361
                &principal,
362
                max_amount,
1✔
363
                auth_id,
1✔
364
            );
1✔
365
            assert_ne!(expected_hash.clone(), result.as_slice());
1✔
366

1✔
367
            // Test 5: invalid max amount
1✔
368
            let result = call_get_signer_message_hash(
1✔
369
                &mut sim,
370
                &pox_addr,
1✔
371
                reward_cycle,
372
                &topic,
1✔
373
                lock_period,
374
                &principal,
375
                1010101,
1✔
376
                auth_id,
1✔
377
            );
1✔
378
            assert_ne!(expected_hash.clone(), result.as_slice());
1✔
379

1✔
380
            // Test 6: invalid auth id
1✔
381
            let result = call_get_signer_message_hash(
1✔
382
                &mut sim,
1✔
383
                &pox_addr,
384
                reward_cycle,
385
                &topic,
1✔
386
                lock_period,
1✔
387
                &principal,
388
                max_amount,
389
                10101,
390
            );
1✔
391
            assert_ne!(expected_hash.clone(), result.as_slice());
1✔
392
        }
1✔
393

1✔
394
        #[test]
1✔
395
        /// Fixture message hash to test against in other libraries
1✔
396
        fn test_sig_hash_fixture() {
1✔
397
            let fixture = "ec5b88aa81a96a6983c26cdba537a13d253425348ffc0ba6b07130869b025a2d";
1✔
398
            let pox_addr = PoxAddress::standard_burn_address(false);
1✔
399
            let pubkey_hex = "0206952cd8813a64f7b97144c984015490a8f9c5778e8f928fbc8aa6cbf02f48e6";
400
            let pubkey = Secp256k1PublicKey::from_hex(pubkey_hex).unwrap();
1✔
401
            let reward_cycle: u128 = 1;
1✔
402
            let lock_period = 12;
1✔
403
            let auth_id = 111;
1✔
404
            let max_amount = u128::MAX;
405

1✔
406
            let message_hash = make_pox_4_signer_key_message_hash(
1✔
407
                &pox_addr,
1✔
408
                reward_cycle,
409
                &Pox4SignatureTopic::StackStx,
410
                CHAIN_ID_TESTNET,
1✔
411
                lock_period,
1✔
412
                max_amount,
413
                auth_id,
414
            );
415

416
            assert_eq!(to_hex(message_hash.as_bytes()), fixture);
417
        }
418
    }
419
}
420

421
pub mod pox5 {
422
    use clarity::vm::types::PrincipalData;
423
    use clarity::vm::ClarityName;
424

270✔
425
    use super::{
270✔
426
        make_structured_data_domain, structured_data_message_hash, MessageSignature, PrivateKey,
270✔
427
        Secp256k1PrivateKey, Sha256Sum, TupleData, Value,
428
    };
429

430
    pub fn make_pox_5_signed_data_domain(chain_id: u32) -> Value {
431
        make_structured_data_domain("pox-5-signer", "1.0.0", chain_id)
270✔
432
    }
270✔
433

270✔
434
    /// Compute the hash of the `grant-authorization` message that is signed
270✔
435
    /// by a signer key when authorizing a `signer-manager` contract to
270✔
436
    /// register the corresponding signer via pox-5's `grant-signer-key`.
270✔
437
    pub fn make_pox_5_signer_grant_message_hash(
270✔
438
        signer_manager: &PrincipalData,
270✔
439
        auth_id: u128,
270✔
440
        chain_id: u32,
270✔
441
    ) -> Sha256Sum {
270✔
442
        let domain_tuple = make_pox_5_signed_data_domain(chain_id);
270✔
443
        let data_tuple = Value::Tuple(
270✔
444
            TupleData::from_data(vec![
270✔
445
                (
270✔
446
                    ClarityName::from_literal("topic"),
270✔
447
                    Value::string_ascii_from_bytes("grant-authorization".into()).unwrap(),
270✔
448
                ),
270✔
449
                (
270✔
450
                    ClarityName::from_literal("signer-manager"),
270✔
451
                    Value::Principal(signer_manager.clone()),
270✔
452
                ),
270✔
453
                (ClarityName::from_literal("auth-id"), Value::UInt(auth_id)),
454
            ])
455
            .expect("Error creating signature hash"),
250✔
456
        );
250✔
457
        structured_data_message_hash(data_tuple, domain_tuple)
250✔
458
    }
250✔
459

250✔
460
    /// Sign a pox-5 `grant-authorization` message with `signer_key`.
250✔
461
    pub fn make_pox_5_signer_grant_signature(
250✔
462
        signer_manager: &PrincipalData,
250✔
463
        auth_id: u128,
250✔
464
        chain_id: u32,
465
        signer_key: &Secp256k1PrivateKey,
466
    ) -> Result<MessageSignature, &'static str> {
467
        let msg_hash = make_pox_5_signer_grant_message_hash(signer_manager, auth_id, chain_id);
468
        signer_key.sign(msg_hash.as_bytes())
469
    }
470
}
471

472
#[cfg(test)]
473
mod test {
474
    use clarity::vm::types::{TupleData, Value};
475
    use stacks_common::consts::CHAIN_ID_MAINNET;
476
    use stacks_common::util::hash::to_hex;
1✔
477

1✔
478
    use super::*;
1✔
479

1✔
480
    /// [SIP18 test vectors](https://github.com/stacksgov/sips/blob/main/sips/sip-018/sip-018-signed-structured-data.md)
1✔
481
    #[test]
482
    fn test_sip18_ref_structured_data_hash() {
483
        let value = Value::string_ascii_from_bytes("Hello World".into()).unwrap();
1✔
484
        let msg_hash = structured_data_hash(value);
485
        assert_eq!(
486
            to_hex(msg_hash.as_bytes()),
487
            "5297eef9765c466d945ad1cb2c81b30b9fed6c165575dc9226e9edf78b8cd9e8"
1✔
488
        )
1✔
489
    }
1✔
490

1✔
491
    /// [SIP18 test vectors](https://github.com/stacksgov/sips/blob/main/sips/sip-018/sip-018-signed-structured-data.md)
1✔
492
    #[test]
1✔
493
    fn test_sip18_ref_message_hashing() {
1✔
494
        let domain = Value::Tuple(
1✔
495
            TupleData::from_data(vec![
1✔
496
                (
1✔
497
                    ClarityName::from_literal("name"),
1✔
498
                    Value::string_ascii_from_bytes("Test App".into()).unwrap(),
1✔
499
                ),
1✔
500
                (
1✔
501
                    ClarityName::from_literal("version"),
1✔
502
                    Value::string_ascii_from_bytes("1.0.0".into()).unwrap(),
1✔
503
                ),
1✔
504
                (
1✔
505
                    ClarityName::from_literal("chain-id"),
1✔
506
                    Value::UInt(CHAIN_ID_MAINNET.into()),
507
                ),
1✔
508
            ])
509
            .unwrap(),
1✔
510
        );
1✔
511
        let data = Value::string_ascii_from_bytes("Hello World".into()).unwrap();
512

513
        let msg_hash = structured_data_message_hash(data, domain);
1✔
514

515
        assert_eq!(
516
            to_hex(msg_hash.as_bytes()),
517
            "1bfdab6d4158313ce34073fbb8d6b0fc32c154d439def12247a0f44bb2225259"
1✔
518
        );
1✔
519
    }
1✔
520

521
    /// [SIP18 test vectors](https://github.com/stacksgov/sips/blob/main/sips/sip-018/sip-018-signed-structured-data.md)
1✔
522
    #[test]
1✔
523
    fn test_sip18_ref_signing() {
1✔
524
        let key = Secp256k1PrivateKey::from_hex(
1✔
525
            "753b7cc01a1a2e86221266a154af739463fce51219d97e4f856cd7200c3bd2a601",
1✔
526
        )
1✔
527
        .unwrap();
1✔
528
        let domain = Value::Tuple(
1✔
529
            TupleData::from_data(vec![
1✔
530
                (
1✔
531
                    ClarityName::from_literal("name"),
1✔
532
                    Value::string_ascii_from_bytes("Test App".into()).unwrap(),
1✔
533
                ),
1✔
534
                (
1✔
535
                    ClarityName::from_literal("version"),
1✔
536
                    Value::string_ascii_from_bytes("1.0.0".into()).unwrap(),
1✔
537
                ),
1✔
538
                (
1✔
539
                    ClarityName::from_literal("chain-id"),
1✔
540
                    Value::UInt(CHAIN_ID_MAINNET.into()),
1✔
541
                ),
1✔
542
            ])
543
            .unwrap(),
1✔
544
        );
545
        let data = Value::string_ascii_from_bytes("Hello World".into()).unwrap();
1✔
546
        let signature =
1✔
547
            sign_structured_data(data, domain, &key).expect("Failed to sign structured data");
548

549
        let signature_rsv = signature.to_rsv();
1✔
550

1✔
551
        assert_eq!(to_hex(signature_rsv.as_slice()), "8b94e45701d857c9f1d1d70e8b2ca076045dae4920fb0160be0642a68cd78de072ab527b5c5277a593baeb2a8b657c216b99f7abb5d14af35b4bf12ba6460ba401");
1✔
552
    }
1✔
553

554
    #[test]
555
    fn test_prefix_bytes() {
556
        let hex = to_hex(STRUCTURED_DATA_PREFIX.as_ref());
557
        assert_eq!(hex, "534950303138");
558
    }
559
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc