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

stacks-network / stacks-core / 25903914664-1

15 May 2026 06:28AM UTC coverage: 47.122% (-38.8%) from 85.959%
25903914664-1

Pull #7199

github

94e391
web-flow
Merge 109f2828c into 1c7b8e6ac
Pull Request #7199: Feat: L1 and L2 early unlocks, updating signer

103343 of 219309 relevant lines covered (47.12%)

12880462.62 hits per line

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

34.91
/stacks-node/src/keychain.rs
1
use stacks::burnchains::BurnchainSigner;
2
use stacks::chainstate::stacks::{
3
    StacksPrivateKey, StacksPublicKey, StacksTransactionSigner, TransactionAuth,
4
};
5
use stacks_common::address::{
6
    AddressHashMode, C32_ADDRESS_VERSION_MAINNET_SINGLESIG, C32_ADDRESS_VERSION_TESTNET_SINGLESIG,
7
};
8
use stacks_common::types::chainstate::StacksAddress;
9
use stacks_common::util::hash::{Hash160, Sha256Sum};
10
use stacks_common::util::secp256k1::{Secp256k1PrivateKey, Secp256k1PublicKey};
11
use stacks_common::util::vrf::{VRFPrivateKey, VRFProof, VRFPublicKey, VRF};
12

13
use super::operations::BurnchainOpSigner;
14

15
/// A wrapper around a node's seed, coupled with operations for using it
16
#[derive(Clone)]
17
pub struct Keychain {
18
    secret_state: Vec<u8>,
19
    nakamoto_mining_key: Secp256k1PrivateKey,
20
}
21

22
impl Keychain {
23
    /// Create a secret key from some state.
24
    /// Returns the bytes that can be fed into StacksPrivateKey
25
    fn make_secret_key_bytes(seed: &[u8]) -> Vec<u8> {
333,079✔
26
        let mut re_hashed_seed = seed.to_vec();
333,079✔
27
        loop {
28
            match StacksPrivateKey::from_slice(&re_hashed_seed[..]) {
334,713✔
29
                Ok(_sk) => {
333,079✔
30
                    break;
333,079✔
31
                }
32
                Err(_) => {
33
                    re_hashed_seed = Sha256Sum::from_data(&re_hashed_seed[..])
1,634✔
34
                        .as_bytes()
1,634✔
35
                        .to_vec()
1,634✔
36
                }
37
            }
38
        }
39
        re_hashed_seed
333,079✔
40
    }
333,079✔
41

42
    /// Create a secret key from our secret state
43
    fn get_secret_key(&self) -> StacksPrivateKey {
306,105✔
44
        let sk_bytes = Keychain::make_secret_key_bytes(&self.secret_state);
306,105✔
45
        StacksPrivateKey::from_slice(&sk_bytes[..]).expect("FATAL: Keychain::make_secret_key_bytes() returned bytes that could not be parsed into a secp256k1 secret key!")
306,105✔
46
    }
306,105✔
47

48
    /// Get the public key hash of the nakamoto mining key (i.e., Hash160(pubkey))
49
    pub fn get_nakamoto_pkh(&self) -> Hash160 {
1,916✔
50
        let pk = Secp256k1PublicKey::from_private(&self.nakamoto_mining_key);
1,916✔
51
        Hash160::from_node_public_key(&pk)
1,916✔
52
    }
1,916✔
53

54
    /// Get the secret key of the nakamoto mining key
55
    pub fn get_nakamoto_sk(&self) -> &Secp256k1PrivateKey {
2,473✔
56
        &self.nakamoto_mining_key
2,473✔
57
    }
2,473✔
58

59
    /// Set the secret key of the nakamoto mining key
60
    pub fn set_nakamoto_sk(&mut self, mining_key: Secp256k1PrivateKey) {
241✔
61
        self.nakamoto_mining_key = mining_key;
241✔
62
    }
241✔
63

64
    /// Create a default keychain from the seed, with a default nakamoto mining key derived
65
    ///  from the same seed (
66
    pub fn default(seed: Vec<u8>) -> Keychain {
1,661✔
67
        let secret_state = Self::make_secret_key_bytes(&seed);
1,661✔
68
        // re-hash secret_state to use as a default seed for the nakamoto mining key
69
        let nakamoto_mining_key =
1,661✔
70
            Secp256k1PrivateKey::from_seed(Sha256Sum::from_data(&secret_state).as_bytes());
1,661✔
71
        Keychain {
1,661✔
72
            secret_state,
1,661✔
73
            nakamoto_mining_key,
1,661✔
74
        }
1,661✔
75
    }
1,661✔
76

77
    /// Generate a VRF keypair for this burn block height.
78
    /// The keypair is unique to this burn block height.
79
    pub fn make_vrf_keypair(&self, block_height: u64) -> (VRFPublicKey, VRFPrivateKey) {
32,982✔
80
        let mut seed = {
32,982✔
81
            let mut secret_state = self.secret_state.clone();
32,982✔
82
            secret_state.extend_from_slice(&block_height.to_be_bytes());
32,982✔
83
            Sha256Sum::from_data(&secret_state)
32,982✔
84
        };
85

86
        // Not every 256-bit number is a valid Ed25519 secret key.
87
        // As such, we continuously generate seeds through re-hashing until one works.
88
        let sk = loop {
32,982✔
89
            match VRFPrivateKey::from_bytes(seed.as_bytes()) {
32,982✔
90
                Some(sk) => break sk,
32,982✔
91
                None => seed = Sha256Sum::from_data(seed.as_bytes()),
×
92
            }
93
        };
94
        let pk = VRFPublicKey::from_private(&sk);
32,982✔
95
        (pk, sk)
32,982✔
96
    }
32,982✔
97

98
    /// Generate a Stacks keypair for this burn block height.
99
    /// The keypair is unique to this burn block height.
100
    pub fn make_stacks_keypair(
25,313✔
101
        &self,
25,313✔
102
        block_height: u64,
25,313✔
103
        salt: &[u8],
25,313✔
104
    ) -> (StacksPublicKey, StacksPrivateKey) {
25,313✔
105
        let seed = {
25,313✔
106
            let mut secret_state = self.secret_state.clone();
25,313✔
107
            secret_state.extend_from_slice(&block_height.to_be_bytes());
25,313✔
108
            secret_state.extend_from_slice(salt);
25,313✔
109
            Sha256Sum::from_data(&secret_state)
25,313✔
110
        };
111

112
        let sk_bytes = Keychain::make_secret_key_bytes(&seed.0);
25,313✔
113
        let sk = StacksPrivateKey::from_slice(&sk_bytes[..]).expect("FATAL: Keychain::make_secret_key_bytes() returned bytes that could not be parsed into a secp256k1 secret key!");
25,313✔
114
        let pk = StacksPublicKey::from_private(&sk);
25,313✔
115

116
        (pk, sk)
25,313✔
117
    }
25,313✔
118

119
    /// Generate a VRF proof over a given byte message.
120
    /// `block_height` must be the _same_ block height called to make_vrf_keypair()
121
    pub fn generate_proof(&self, block_height: u64, bytes: &[u8; 32]) -> Option<VRFProof> {
32,705✔
122
        let (pk, sk) = self.make_vrf_keypair(block_height);
32,705✔
123
        let Some(proof) = VRF::prove(&sk, bytes.as_ref()) else {
32,705✔
124
            error!(
×
125
                "Failed to generate proof with keypair, will be unable to mine.";
126
                "block_height" => block_height,
×
127
                "pk" => ?pk
128
            );
129
            return None;
×
130
        };
131

132
        // Ensure that the proof is valid by verifying
133
        let is_valid = VRF::verify(&pk, &proof, bytes.as_ref())
32,705✔
134
            .inspect_err(|e| {
32,705✔
135
                error!(
×
136
                    "Failed to validate generated proof, will be unable to mine.";
137
                    "block_height" => block_height,
×
138
                    "pk" => ?pk,
139
                    "err" => %e,
140
                );
141
            })
×
142
            .ok()?;
32,705✔
143
        if !is_valid {
32,705✔
144
            error!(
9✔
145
                "Generated invalidat proof, will be unable to mine.";
146
                "block_height" => block_height,
×
147
                "pk" => ?pk,
148
            );
149
            None
9✔
150
        } else {
151
            Some(proof)
32,696✔
152
        }
153
    }
32,705✔
154

155
    /// Generate a microblock signing key for this burnchain block height.
156
    /// `salt` can be any byte string; in practice, it's the parent Stacks block's block ID hash.
157
    pub fn make_microblock_secret_key(
25,302✔
158
        &mut self,
25,302✔
159
        burn_block_height: u64,
25,302✔
160
        salt: &[u8],
25,302✔
161
    ) -> StacksPrivateKey {
25,302✔
162
        let (_, mut sk) = self.make_stacks_keypair(burn_block_height, salt);
25,302✔
163
        sk.set_compress_public(true);
25,302✔
164

165
        debug!("Microblock keypair rotated";
25,302✔
166
               "burn_block_height" => %burn_block_height,
167
               "pubkey_hash" => %Hash160::from_node_public_key(&StacksPublicKey::from_private(&sk)).to_string()
×
168
        );
169
        sk
25,302✔
170
    }
25,302✔
171

172
    pub fn get_pub_key(&self) -> Secp256k1PublicKey {
628✔
173
        let sk = self.get_secret_key();
628✔
174
        StacksPublicKey::from_private(&sk)
628✔
175
    }
628✔
176

177
    /// Get the Stacks address for the inner secret state
178
    pub fn get_address(&self, is_mainnet: bool) -> StacksAddress {
24,579✔
179
        let sk = self.get_secret_key();
24,579✔
180
        let pk = StacksPublicKey::from_private(&sk);
24,579✔
181

182
        let version = if is_mainnet {
24,579✔
183
            C32_ADDRESS_VERSION_MAINNET_SINGLESIG
24,575✔
184
        } else {
185
            C32_ADDRESS_VERSION_TESTNET_SINGLESIG
4✔
186
        };
187
        StacksAddress::from_public_keys(version, &AddressHashMode::SerializeP2PKH, 1, &vec![pk])
24,579✔
188
            .expect("FATAL: could not produce address from secret key")
24,579✔
189
    }
24,579✔
190

191
    /// Get a BurnchainSigner representation of this keychain
192
    pub fn get_burnchain_signer(&self) -> BurnchainSigner {
24,575✔
193
        BurnchainSigner(format!("{}", &self.get_address(true)))
24,575✔
194
    }
24,575✔
195

196
    /// Convenience wrapper around make_stacks_keypair
197
    pub fn get_microblock_key(&self, block_height: u64) -> StacksPrivateKey {
11✔
198
        self.make_stacks_keypair(block_height, &[]).1
11✔
199
    }
11✔
200

201
    /// Sign a transaction as if we were the origin
202
    pub fn sign_as_origin(&self, tx_signer: &mut StacksTransactionSigner) {
28,264✔
203
        let sk = self.get_secret_key();
28,264✔
204
        tx_signer
28,264✔
205
            .sign_origin(&sk)
28,264✔
206
            .expect("FATAL: failed to sign transaction origin");
28,264✔
207
    }
28,264✔
208

209
    /// Create a transaction authorization struct from this keychain's secret state
210
    pub fn get_transaction_auth(&self) -> Option<TransactionAuth> {
227,082✔
211
        TransactionAuth::from_p2pkh(&self.get_secret_key())
227,082✔
212
    }
227,082✔
213

214
    /// Get the origin address that this keychain represents
215
    pub fn origin_address(&self, is_mainnet: bool) -> Option<StacksAddress> {
198,818✔
216
        match self.get_transaction_auth() {
198,818✔
217
            Some(auth) => {
198,818✔
218
                let address = if is_mainnet {
198,818✔
219
                    auth.origin().address_mainnet()
×
220
                } else {
221
                    auth.origin().address_testnet()
198,818✔
222
                };
223
                Some(address)
198,818✔
224
            }
225
            None => None,
×
226
        }
227
    }
198,818✔
228

229
    /// Create a BurnchainOpSigner representation of this keychain
230
    pub fn generate_op_signer(&self) -> BurnchainOpSigner {
25,552✔
231
        BurnchainOpSigner::new(self.get_secret_key())
25,552✔
232
    }
25,552✔
233
}
234

235
#[cfg(test)]
236
#[allow(dead_code)]
237
mod tests {
238
    use std::collections::HashMap;
239

240
    use stacks::burnchains::PrivateKey;
241
    use stacks::chainstate::stacks::{
242
        StacksPrivateKey, StacksPublicKey, StacksTransaction, StacksTransactionSigner,
243
        TokenTransferMemo, TransactionAuth, TransactionPayload, TransactionPostConditionMode,
244
        TransactionVersion,
245
    };
246
    use stacks_common::address::AddressHashMode;
247
    use stacks_common::types::chainstate::StacksAddress;
248
    use stacks_common::util::hash::{Hash160, Sha256Sum};
249
    use stacks_common::util::vrf::{VRFPrivateKey, VRFProof, VRFPublicKey, VRF};
250

251
    use super::Keychain;
252
    use crate::operations::BurnchainOpSigner;
253
    use crate::stacks_common::types::Address;
254

255
    /// Legacy implementation; kept around for testing
256
    #[derive(Clone)]
257
    pub struct KeychainOld {
258
        secret_keys: Vec<StacksPrivateKey>,
259
        threshold: u16,
260
        hash_mode: AddressHashMode,
261
        pub hashed_secret_state: Sha256Sum,
262
        microblocks_secret_keys: Vec<StacksPrivateKey>,
263
        vrf_secret_keys: Vec<VRFPrivateKey>,
264
        vrf_map: HashMap<VRFPublicKey, VRFPrivateKey>,
265
    }
266

267
    impl KeychainOld {
268
        pub fn new(
×
269
            secret_keys: Vec<StacksPrivateKey>,
×
270
            threshold: u16,
×
271
            hash_mode: AddressHashMode,
×
272
        ) -> KeychainOld {
×
273
            // Compute hashed secret state
274
            let hashed_secret_state = {
×
275
                let mut buf: Vec<u8> = secret_keys.iter().flat_map(|sk| sk.to_bytes()).collect();
×
276
                buf.extend_from_slice(&[
×
277
                    (threshold >> 8) as u8,
×
278
                    (threshold & 0xff) as u8,
×
279
                    hash_mode as u8,
×
280
                ]);
×
281
                Sha256Sum::from_data(&buf[..])
×
282
            };
283

284
            Self {
×
285
                hash_mode,
×
286
                hashed_secret_state,
×
287
                microblocks_secret_keys: vec![],
×
288
                secret_keys,
×
289
                threshold,
×
290
                vrf_secret_keys: vec![],
×
291
                vrf_map: HashMap::new(),
×
292
            }
×
293
        }
×
294

295
        pub fn default(seed: Vec<u8>) -> KeychainOld {
×
296
            let mut re_hashed_seed = seed;
×
297
            let secret_key = loop {
×
298
                match StacksPrivateKey::from_slice(&re_hashed_seed[..]) {
×
299
                    Ok(sk) => break sk,
×
300
                    Err(_) => {
301
                        re_hashed_seed = Sha256Sum::from_data(&re_hashed_seed[..])
×
302
                            .as_bytes()
×
303
                            .to_vec()
×
304
                    }
305
                }
306
            };
307

308
            let threshold = 1;
×
309
            let hash_mode = AddressHashMode::SerializeP2PKH;
×
310

311
            KeychainOld::new(vec![secret_key], threshold, hash_mode)
×
312
        }
×
313

314
        pub fn rotate_vrf_keypair(&mut self, block_height: u64) -> VRFPublicKey {
×
315
            let mut seed = {
×
316
                let mut secret_state = self.hashed_secret_state.to_bytes().to_vec();
×
317
                secret_state.extend_from_slice(&block_height.to_be_bytes());
×
318
                Sha256Sum::from_data(&secret_state)
×
319
            };
320

321
            // Not every 256-bit number is a valid Ed25519 secret key.
322
            // As such, we continuously generate seeds through re-hashing until one works.
323
            let sk = loop {
×
324
                match VRFPrivateKey::from_bytes(seed.as_bytes()) {
×
325
                    Some(sk) => break sk,
×
326
                    None => seed = Sha256Sum::from_data(seed.as_bytes()),
×
327
                }
328
            };
329
            let pk = VRFPublicKey::from_private(&sk);
×
330

331
            self.vrf_secret_keys.push(sk.clone());
×
332
            self.vrf_map.insert(pk.clone(), sk);
×
333
            pk
×
334
        }
×
335

336
        pub fn rotate_microblock_keypair(&mut self, burn_block_height: u64) -> StacksPrivateKey {
×
337
            let mut secret_state = match self.microblocks_secret_keys.last() {
×
338
                // First key is the hash of the secret state
339
                None => self.hashed_secret_state.to_bytes().to_vec(),
×
340
                // Next key is the hash of the last
341
                Some(last_sk) => last_sk.to_bytes().to_vec(),
×
342
            };
343

344
            secret_state.extend_from_slice(&burn_block_height.to_be_bytes());
×
345

346
            let mut seed = Sha256Sum::from_data(&secret_state);
×
347

348
            // Not every 256-bit number is a valid secp256k1 secret key.
349
            // As such, we continuously generate seeds through re-hashing until one works.
350
            let mut sk = loop {
×
351
                match StacksPrivateKey::from_slice(&seed.to_bytes()[..]) {
×
352
                    Ok(sk) => break sk,
×
353
                    Err(_) => seed = Sha256Sum::from_data(seed.as_bytes()),
×
354
                }
355
            };
356
            sk.set_compress_public(true);
×
357
            self.microblocks_secret_keys.push(sk.clone());
×
358

359
            debug!("Microblock keypair rotated";
×
360
                   "burn_block_height" => %burn_block_height,
361
                   "pubkey_hash" => %Hash160::from_node_public_key(&StacksPublicKey::from_private(&sk)).to_string(),);
×
362

363
            sk
×
364
        }
×
365

366
        pub fn get_microblock_key(&self) -> Option<StacksPrivateKey> {
×
367
            self.microblocks_secret_keys.last().cloned()
×
368
        }
×
369

370
        pub fn sign_as_origin(&self, tx_signer: &mut StacksTransactionSigner) {
×
371
            let num_keys = if self.secret_keys.len() < self.threshold as usize {
×
372
                self.secret_keys.len()
×
373
            } else {
374
                self.threshold as usize
×
375
            };
376

377
            for i in 0..num_keys {
×
378
                tx_signer.sign_origin(&self.secret_keys[i]).unwrap();
×
379
            }
×
380
        }
×
381

382
        /// Given a VRF public key, generates a VRF Proof
383
        pub fn generate_proof(&self, vrf_pk: &VRFPublicKey, bytes: &[u8; 32]) -> Option<VRFProof> {
×
384
            // Retrieve the corresponding VRF secret key
385
            let vrf_sk = match self.vrf_map.get(vrf_pk) {
×
386
                Some(vrf_pk) => vrf_pk,
×
387
                None => {
388
                    warn!("No VRF secret key on file for {vrf_pk:?}");
×
389
                    return None;
×
390
                }
391
            };
392

393
            // Generate the proof
394
            let proof = VRF::prove(vrf_sk, bytes.as_ref())?;
×
395
            // Ensure that the proof is valid by verifying
396
            let is_valid = VRF::verify(vrf_pk, &proof, bytes.as_ref()).unwrap_or(false);
×
397
            assert!(is_valid);
×
398
            Some(proof)
×
399
        }
×
400

401
        /// Given the keychain's secret keys, computes and returns the corresponding Stack address.
402
        pub fn get_address(&self, is_mainnet: bool) -> StacksAddress {
×
403
            let public_keys = self
×
404
                .secret_keys
×
405
                .iter()
×
406
                .map(StacksPublicKey::from_private)
×
407
                .collect();
×
408
            let version = if is_mainnet {
×
409
                self.hash_mode.to_version_mainnet()
×
410
            } else {
411
                self.hash_mode.to_version_testnet()
×
412
            };
413
            StacksAddress::from_public_keys(
×
414
                version,
×
415
                &self.hash_mode,
×
416
                self.threshold as usize,
×
417
                &public_keys,
×
418
            )
419
            .unwrap()
×
420
        }
×
421

422
        pub fn get_transaction_auth(&self) -> Option<TransactionAuth> {
×
423
            match self.hash_mode {
×
424
                AddressHashMode::SerializeP2PKH => {
425
                    TransactionAuth::from_p2pkh(&self.secret_keys[0])
×
426
                }
427
                AddressHashMode::SerializeP2SH => {
428
                    TransactionAuth::from_p2sh(&self.secret_keys, self.threshold)
×
429
                }
430
                AddressHashMode::SerializeP2WPKH => {
431
                    TransactionAuth::from_p2wpkh(&self.secret_keys[0])
×
432
                }
433
                AddressHashMode::SerializeP2WSH => {
434
                    TransactionAuth::from_p2wsh(&self.secret_keys, self.threshold)
×
435
                }
436
            }
437
        }
×
438

439
        pub fn origin_address(&self, is_mainnet: bool) -> Option<StacksAddress> {
×
440
            match self.get_transaction_auth() {
×
441
                Some(auth) => {
×
442
                    let address = if is_mainnet {
×
443
                        auth.origin().address_mainnet()
×
444
                    } else {
445
                        auth.origin().address_testnet()
×
446
                    };
447
                    Some(address)
×
448
                }
449
                None => None,
×
450
            }
451
        }
×
452

453
        pub fn generate_op_signer(&self) -> BurnchainOpSigner {
×
454
            BurnchainOpSigner::new(self.secret_keys[0].clone())
×
455
        }
×
456
    }
457

458
    #[test]
459
    fn test_origin_address() {
×
460
        let seeds = [
×
461
            [0u8; 32],
×
462
            [
×
463
                0xc2, 0x7e, 0x1d, 0x7e, 0x9a, 0x0d, 0x47, 0xfa, 0xa5, 0x10, 0xbe, 0x50, 0x9b, 0xce,
×
464
                0xd4, 0x95, 0x99, 0x64, 0x40, 0x34, 0xbd, 0x5a, 0xf2, 0x2b, 0x51, 0x9c, 0x21, 0x19,
×
465
                0xbd, 0xaa, 0x5d, 0x62,
×
466
            ],
×
467
        ];
×
468

469
        for seed in seeds {
×
470
            let k1 = Keychain::default(seed.to_vec());
×
471
            let k2 = KeychainOld::default(seed.to_vec());
×
472

473
            assert_eq!(k1.origin_address(true), k2.origin_address(true));
×
474
            assert_eq!(k1.origin_address(false), k2.origin_address(false));
×
475
        }
476
    }
×
477

478
    #[test]
479
    fn test_get_address() {
×
480
        let seeds = [
×
481
            [0u8; 32],
×
482
            [
×
483
                0xc2, 0x7e, 0x1d, 0x7e, 0x9a, 0x0d, 0x47, 0xfa, 0xa5, 0x10, 0xbe, 0x50, 0x9b, 0xce,
×
484
                0xd4, 0x95, 0x99, 0x64, 0x40, 0x34, 0xbd, 0x5a, 0xf2, 0x2b, 0x51, 0x9c, 0x21, 0x19,
×
485
                0xbd, 0xaa, 0x5d, 0x62,
×
486
            ],
×
487
        ];
×
488

489
        for seed in seeds {
×
490
            let k1 = Keychain::default(seed.to_vec());
×
491
            let k2 = KeychainOld::default(seed.to_vec());
×
492

493
            assert_eq!(k1.get_address(true), k2.get_address(true));
×
494
            assert_eq!(k1.get_address(false), k2.get_address(false));
×
495
        }
496
    }
×
497

498
    #[test]
499
    fn test_get_transaction_auth() {
×
500
        let seeds = [
×
501
            [0u8; 32],
×
502
            [
×
503
                0xc2, 0x7e, 0x1d, 0x7e, 0x9a, 0x0d, 0x47, 0xfa, 0xa5, 0x10, 0xbe, 0x50, 0x9b, 0xce,
×
504
                0xd4, 0x95, 0x99, 0x64, 0x40, 0x34, 0xbd, 0x5a, 0xf2, 0x2b, 0x51, 0x9c, 0x21, 0x19,
×
505
                0xbd, 0xaa, 0x5d, 0x62,
×
506
            ],
×
507
        ];
×
508

509
        for seed in seeds {
×
510
            let k1 = Keychain::default(seed.to_vec());
×
511
            let k2 = KeychainOld::default(seed.to_vec());
×
512

513
            assert_eq!(k1.get_transaction_auth(), k2.get_transaction_auth());
×
514
        }
515
    }
×
516

517
    #[test]
518
    fn test_sign_as_origin() {
×
519
        let seeds = [
×
520
            [0u8; 32],
×
521
            [
×
522
                0xc2, 0x7e, 0x1d, 0x7e, 0x9a, 0x0d, 0x47, 0xfa, 0xa5, 0x10, 0xbe, 0x50, 0x9b, 0xce,
×
523
                0xd4, 0x95, 0x99, 0x64, 0x40, 0x34, 0xbd, 0x5a, 0xf2, 0x2b, 0x51, 0x9c, 0x21, 0x19,
×
524
                0xbd, 0xaa, 0x5d, 0x62,
×
525
            ],
×
526
        ];
×
527

528
        for seed in seeds {
×
529
            let k1 = Keychain::default(seed.to_vec());
×
530
            let k2 = KeychainOld::default(seed.to_vec());
×
531

532
            let recv_addr =
×
533
                StacksAddress::from_string("SP1Z4P459B2M5XC2PMM2CSCNZ6824DN5GZG2XYWFH").unwrap();
×
534

535
            let mut tx_stx_transfer_1 = StacksTransaction::new(
×
536
                TransactionVersion::Testnet,
×
537
                k1.get_transaction_auth().unwrap(),
×
538
                TransactionPayload::TokenTransfer(
×
539
                    recv_addr.clone().into(),
×
540
                    123,
×
541
                    TokenTransferMemo([0u8; 34]),
×
542
                ),
×
543
            );
544
            let mut tx_stx_transfer_2 = StacksTransaction::new(
×
545
                TransactionVersion::Testnet,
×
546
                k2.get_transaction_auth().unwrap(),
×
547
                TransactionPayload::TokenTransfer(
×
548
                    recv_addr.into(),
×
549
                    123,
×
550
                    TokenTransferMemo([0u8; 34]),
×
551
                ),
×
552
            );
553

554
            tx_stx_transfer_1.chain_id = 0x80000000;
×
555
            tx_stx_transfer_1.post_condition_mode = TransactionPostConditionMode::Allow;
×
556
            tx_stx_transfer_1.set_tx_fee(0);
×
557

558
            tx_stx_transfer_2.chain_id = 0x80000000;
×
559
            tx_stx_transfer_2.post_condition_mode = TransactionPostConditionMode::Allow;
×
560
            tx_stx_transfer_2.set_tx_fee(0);
×
561

562
            let mut signer_1 = StacksTransactionSigner::new(&tx_stx_transfer_1);
×
563
            k1.sign_as_origin(&mut signer_1);
×
564
            let tx_1 = signer_1.get_tx().unwrap();
×
565

566
            let mut signer_2 = StacksTransactionSigner::new(&tx_stx_transfer_2);
×
567
            k2.sign_as_origin(&mut signer_2);
×
568
            let tx_2 = signer_2.get_tx().unwrap();
×
569

570
            assert_eq!(tx_1, tx_2);
×
571
        }
572
    }
×
573
}
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