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

stacks-network / stacks-core / 23821853325

31 Mar 2026 10:11PM UTC coverage: 46.226% (-0.4%) from 46.639%
23821853325

Pull #7062

github

fcfcf7
web-flow
Merge 7be69e4f4 into 10ce9604d
Pull Request #7062: feat: add pox-5 support in pox-locking library

117 of 854 new or added lines in 10 files covered. (13.7%)

825 existing lines in 59 files now uncovered.

102030 of 220718 relevant lines covered (46.23%)

12583055.43 hits per line

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

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

45
    Sha256Sum::from_data(&message)
74,658✔
46
}
74,658✔
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(
×
51
    structured_data: Value,
×
52
    domain: Value,
×
53
    private_key: &Secp256k1PrivateKey,
×
54
) -> Result<MessageSignature, &str> {
×
55
    let msg_hash = structured_data_message_hash(structured_data, domain);
×
56
    private_key.sign(msg_hash.as_bytes())
×
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 {
74,658✔
61
    Value::Tuple(
74,658✔
62
        TupleData::from_data(vec![
74,658✔
63
            (
74,658✔
64
                "name".into(),
74,658✔
65
                Value::string_ascii_from_bytes(name.into()).unwrap(),
74,658✔
66
            ),
74,658✔
67
            (
74,658✔
68
                "version".into(),
74,658✔
69
                Value::string_ascii_from_bytes(version.into()).unwrap(),
74,658✔
70
            ),
74,658✔
71
            ("chain-id".into(), Value::UInt(chain_id.into())),
74,658✔
72
        ])
74,658✔
73
        .unwrap(),
74,658✔
74
    )
74,658✔
75
}
74,658✔
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,013✔
91
        make_structured_data_domain("pox-4-signer", "1.0.0", chain_id)
8,013✔
92
    }
8,013✔
93

94
    #[cfg_attr(test, mutants::skip)]
95
    pub fn make_pox_4_signer_key_message_hash(
8,013✔
96
        pox_addr: &PoxAddress,
8,013✔
97
        reward_cycle: u128,
8,013✔
98
        topic: &Pox4SignatureTopic,
8,013✔
99
        chain_id: u32,
8,013✔
100
        period: u128,
8,013✔
101
        max_amount: u128,
8,013✔
102
        auth_id: u128,
8,013✔
103
    ) -> Sha256Sum {
8,013✔
104
        let domain_tuple = make_pox_4_signed_data_domain(chain_id);
8,013✔
105
        let data_tuple = Value::Tuple(
8,013✔
106
            TupleData::from_data(vec![
8,013✔
107
                (
8,013✔
108
                    "pox-addr".into(),
8,013✔
109
                    pox_addr
8,013✔
110
                        .clone()
8,013✔
111
                        .as_clarity_tuple()
8,013✔
112
                        .expect("Error creating signature hash - invalid PoX Address")
8,013✔
113
                        .into(),
8,013✔
114
                ),
8,013✔
115
                ("reward-cycle".into(), Value::UInt(reward_cycle)),
8,013✔
116
                ("period".into(), Value::UInt(period)),
8,013✔
117
                (
8,013✔
118
                    "topic".into(),
8,013✔
119
                    Value::string_ascii_from_bytes(topic.get_name_str().into()).unwrap(),
8,013✔
120
                ),
8,013✔
121
                ("auth-id".into(), Value::UInt(auth_id)),
8,013✔
122
                ("max-amount".into(), Value::UInt(max_amount)),
8,013✔
123
            ])
8,013✔
124
            .expect("Error creating signature hash"),
8,013✔
125
        );
8,013✔
126
        structured_data_message_hash(data_tuple, domain_tuple)
8,013✔
127
    }
8,013✔
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,013✔
144
        pox_addr: &PoxAddress,
8,013✔
145
        signer_key: &StacksPrivateKey,
8,013✔
146
        reward_cycle: u128,
8,013✔
147
        topic: &Pox4SignatureTopic,
8,013✔
148
        chain_id: u32,
8,013✔
149
        period: u128,
8,013✔
150
        max_amount: u128,
8,013✔
151
        auth_id: u128,
8,013✔
152
    ) -> Result<MessageSignature, &'static str> {
8,013✔
153
        let msg_hash = make_pox_4_signer_key_message_hash(
8,013✔
154
            pox_addr,
8,013✔
155
            reward_cycle,
8,013✔
156
            topic,
8,013✔
157
            chain_id,
8,013✔
158
            period,
8,013✔
159
            max_amount,
8,013✔
160
            auth_id,
8,013✔
161
        );
162
        signer_key.sign(msg_hash.as_bytes())
8,013✔
163
    }
8,013✔
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

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

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

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

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

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

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

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

278
            // Test 1: valid result
279

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

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

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

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

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

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

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

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

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

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

404
pub mod pox5 {
405
    use clarity::vm::types::PrincipalData;
406

407
    use super::{
408
        make_structured_data_domain, structured_data_message_hash, MessageSignature, PoxAddress,
409
        PrivateKey, Sha256Sum, StacksPrivateKey, TupleData, Value,
410
    };
411

412
    define_named_enum!(Pox5SignatureTopic {
413
        Stake("stake"),
414
        StakeExtend("stake-extend"),
415
        StakeUpdate("stake-update"),
416
    });
417

UNCOV
418
    pub fn make_pox_5_signed_data_domain(chain_id: u32) -> Value {
×
UNCOV
419
        make_structured_data_domain("pox-5-signer", "1.0.0", chain_id)
×
UNCOV
420
    }
×
421

422
    pub fn make_pox_5_signer_key_message_hash(
×
423
        pox_addr: &PoxAddress,
×
424
        reward_cycle: u128,
×
425
        topic: &Pox5SignatureTopic,
×
426
        chain_id: u32,
×
427
        period: u128,
×
428
        max_amount: u128,
×
429
        auth_id: u128,
×
430
    ) -> Sha256Sum {
×
431
        let domain_tuple = make_pox_5_signed_data_domain(chain_id);
×
432
        let data_tuple = Value::Tuple(
×
433
            TupleData::from_data(vec![
×
434
                (
×
435
                    "pox-addr".into(),
×
436
                    pox_addr
×
437
                        .clone()
×
438
                        .as_clarity_tuple()
×
439
                        .expect("Error creating signature hash - invalid PoX Address")
×
440
                        .into(),
×
441
                ),
×
442
                ("reward-cycle".into(), Value::UInt(reward_cycle)),
×
443
                ("period".into(), Value::UInt(period)),
×
444
                (
×
445
                    "topic".into(),
×
446
                    Value::string_ascii_from_bytes(topic.get_name_str().into()).unwrap(),
×
447
                ),
×
448
                ("auth-id".into(), Value::UInt(auth_id)),
×
449
                ("max-amount".into(), Value::UInt(max_amount)),
×
450
            ])
×
451
            .expect("Error creating signature hash"),
×
452
        );
×
453
        structured_data_message_hash(data_tuple, domain_tuple)
×
454
    }
×
455

456
    pub fn make_pox_5_signer_key_signature(
×
457
        pox_addr: &PoxAddress,
×
458
        signer_key: &StacksPrivateKey,
×
459
        reward_cycle: u128,
×
460
        topic: &Pox5SignatureTopic,
×
461
        chain_id: u32,
×
462
        period: u128,
×
463
        max_amount: u128,
×
464
        auth_id: u128,
×
465
    ) -> Result<MessageSignature, &'static str> {
×
466
        let msg_hash = make_pox_5_signer_key_message_hash(
×
467
            pox_addr,
×
468
            reward_cycle,
×
469
            topic,
×
470
            chain_id,
×
471
            period,
×
472
            max_amount,
×
473
            auth_id,
×
474
        );
475
        signer_key.sign(msg_hash.as_bytes())
×
476
    }
×
477

478
    /// Generate the message hash for a signer key grant authorization.
479
    /// The grant message includes the stacker principal, an optional pox-addr constraint,
480
    /// and an auth-id for replay protection.
UNCOV
481
    pub fn make_pox_5_signer_grant_message_hash(
×
UNCOV
482
        stacker: &PrincipalData,
×
UNCOV
483
        pox_addr: Option<&PoxAddress>,
×
UNCOV
484
        auth_id: u128,
×
UNCOV
485
        chain_id: u32,
×
UNCOV
486
    ) -> Sha256Sum {
×
UNCOV
487
        let domain_tuple = make_pox_5_signed_data_domain(chain_id);
×
488

UNCOV
489
        let pox_addr_value = match pox_addr {
×
490
            Some(addr) => Value::some(
×
491
                addr.clone()
×
492
                    .as_clarity_tuple()
×
493
                    .expect("Error creating grant hash - invalid PoX Address")
×
494
                    .into(),
×
495
            )
496
            .unwrap(),
×
UNCOV
497
            None => Value::none(),
×
498
        };
499

UNCOV
500
        let data_tuple = Value::Tuple(
×
UNCOV
501
            TupleData::from_data(vec![
×
UNCOV
502
                (
×
UNCOV
503
                    "topic".into(),
×
UNCOV
504
                    Value::string_ascii_from_bytes("grant-authorization".into()).unwrap(),
×
UNCOV
505
                ),
×
UNCOV
506
                ("staker".into(), Value::Principal(stacker.clone())),
×
UNCOV
507
                ("pox-addr".into(), pox_addr_value),
×
UNCOV
508
                ("auth-id".into(), Value::UInt(auth_id)),
×
UNCOV
509
            ])
×
UNCOV
510
            .expect("Error creating grant hash"),
×
UNCOV
511
        );
×
UNCOV
512
        structured_data_message_hash(data_tuple, domain_tuple)
×
UNCOV
513
    }
×
514

515
    /// Sign a signer key grant authorization.
UNCOV
516
    pub fn make_pox_5_signer_grant_signature(
×
UNCOV
517
        stacker: &PrincipalData,
×
UNCOV
518
        pox_addr: Option<&PoxAddress>,
×
UNCOV
519
        auth_id: u128,
×
UNCOV
520
        chain_id: u32,
×
UNCOV
521
        signer_key: &StacksPrivateKey,
×
UNCOV
522
    ) -> Result<MessageSignature, &'static str> {
×
UNCOV
523
        let msg_hash = make_pox_5_signer_grant_message_hash(stacker, pox_addr, auth_id, chain_id);
×
UNCOV
524
        signer_key.sign(msg_hash.as_bytes())
×
UNCOV
525
    }
×
526
}
527

528
#[cfg(test)]
529
mod test {
530
    use clarity::vm::types::{TupleData, Value};
531
    use stacks_common::consts::CHAIN_ID_MAINNET;
532
    use stacks_common::util::hash::to_hex;
533

534
    use super::*;
535

536
    /// [SIP18 test vectors](https://github.com/stacksgov/sips/blob/main/sips/sip-018/sip-018-signed-structured-data.md)
537
    #[test]
538
    fn test_sip18_ref_structured_data_hash() {
×
539
        let value = Value::string_ascii_from_bytes("Hello World".into()).unwrap();
×
540
        let msg_hash = structured_data_hash(value);
×
541
        assert_eq!(
×
542
            to_hex(msg_hash.as_bytes()),
×
543
            "5297eef9765c466d945ad1cb2c81b30b9fed6c165575dc9226e9edf78b8cd9e8"
544
        )
545
    }
×
546

547
    /// [SIP18 test vectors](https://github.com/stacksgov/sips/blob/main/sips/sip-018/sip-018-signed-structured-data.md)
548
    #[test]
549
    fn test_sip18_ref_message_hashing() {
×
550
        let domain = Value::Tuple(
×
551
            TupleData::from_data(vec![
×
552
                (
×
553
                    "name".into(),
×
554
                    Value::string_ascii_from_bytes("Test App".into()).unwrap(),
×
555
                ),
×
556
                (
×
557
                    "version".into(),
×
558
                    Value::string_ascii_from_bytes("1.0.0".into()).unwrap(),
×
559
                ),
×
560
                ("chain-id".into(), Value::UInt(CHAIN_ID_MAINNET.into())),
×
561
            ])
×
562
            .unwrap(),
×
563
        );
×
564
        let data = Value::string_ascii_from_bytes("Hello World".into()).unwrap();
×
565

566
        let msg_hash = structured_data_message_hash(data, domain);
×
567

568
        assert_eq!(
×
569
            to_hex(msg_hash.as_bytes()),
×
570
            "1bfdab6d4158313ce34073fbb8d6b0fc32c154d439def12247a0f44bb2225259"
571
        );
572
    }
×
573

574
    /// [SIP18 test vectors](https://github.com/stacksgov/sips/blob/main/sips/sip-018/sip-018-signed-structured-data.md)
575
    #[test]
576
    fn test_sip18_ref_signing() {
×
577
        let key = Secp256k1PrivateKey::from_hex(
×
578
            "753b7cc01a1a2e86221266a154af739463fce51219d97e4f856cd7200c3bd2a601",
×
579
        )
580
        .unwrap();
×
581
        let domain = Value::Tuple(
×
582
            TupleData::from_data(vec![
×
583
                (
×
584
                    "name".into(),
×
585
                    Value::string_ascii_from_bytes("Test App".into()).unwrap(),
×
586
                ),
×
587
                (
×
588
                    "version".into(),
×
589
                    Value::string_ascii_from_bytes("1.0.0".into()).unwrap(),
×
590
                ),
×
591
                ("chain-id".into(), Value::UInt(CHAIN_ID_MAINNET.into())),
×
592
            ])
×
593
            .unwrap(),
×
594
        );
×
595
        let data = Value::string_ascii_from_bytes("Hello World".into()).unwrap();
×
596
        let signature =
×
597
            sign_structured_data(data, domain, &key).expect("Failed to sign structured data");
×
598

599
        let signature_rsv = signature.to_rsv();
×
600

601
        assert_eq!(to_hex(signature_rsv.as_slice()), "8b94e45701d857c9f1d1d70e8b2ca076045dae4920fb0160be0642a68cd78de072ab527b5c5277a593baeb2a8b657c216b99f7abb5d14af35b4bf12ba6460ba401");
×
602
    }
×
603

604
    #[test]
605
    fn test_prefix_bytes() {
×
606
        let hex = to_hex(STRUCTURED_DATA_PREFIX.as_ref());
×
607
        assert_eq!(hex, "534950303138");
×
608
    }
×
609
}
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