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

Chik-Network / chik_rs / 15659902311

15 Jun 2025 05:25AM UTC coverage: 65.303% (-1.8%) from 67.072%
15659902311

push

github

Chik-Network
update 0.23.0

257 of 694 new or added lines in 6 files covered. (37.03%)

3 existing lines in 1 file now uncovered.

11074 of 16958 relevant lines covered (65.3%)

856462.93 hits per line

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

99.15
/crates/chik-consensus/src/spendbundle_validation.rs
1
use crate::allocator::make_allocator;
2
use crate::consensus_constants::ConsensusConstants;
3
use crate::flags::COST_CONDITIONS;
4
use crate::owned_conditions::OwnedSpendBundleConditions;
5
use crate::spendbundle_conditions::run_spendbundle;
6
use crate::validation_error::ErrorCode;
7
use chik_bls::GTElement;
8
use chik_bls::{aggregate_verify_gt, hash_to_g2};
9
use chik_protocol::SpendBundle;
10
use chik_sha2::Sha256;
11
use klvmr::chik_dialect::ENABLE_KECCAK_OPS_OUTSIDE_GUARD;
12
use klvmr::LIMIT_HEAP;
13
use std::time::{Duration, Instant};
14

15
// type definition makes clippy happy
16
pub type ValidationPair = ([u8; 32], GTElement);
17

18
// currently in mempool_manager.py
19
// called in threads from pre_validate_spend_bundle()
20
// pybinding returns (error, cached_results, new_cache_entries, duration)
21
pub fn validate_klvm_and_signature(
56✔
22
    spend_bundle: &SpendBundle,
56✔
23
    max_cost: u64,
56✔
24
    constants: &ConsensusConstants,
56✔
25
    height: u32,
56✔
26
) -> Result<(OwnedSpendBundleConditions, Vec<ValidationPair>, Duration), ErrorCode> {
56✔
27
    let start_time = Instant::now();
56✔
28
    let mut a = make_allocator(LIMIT_HEAP);
56✔
29
    let (sbc, pkm_pairs) =
52✔
30
        run_spendbundle(&mut a, spend_bundle, max_cost, height, 0, constants).map_err(|e| e.1)?;
56✔
31
    let conditions = OwnedSpendBundleConditions::from(&a, sbc);
52✔
32

52✔
33
    // Collect all pairs in a single vector to avoid multiple iterations
52✔
34
    let mut pairs = Vec::new();
52✔
35

52✔
36
    let mut aug_msg = Vec::<u8>::new();
52✔
37

38
    for (pk, msg) in pkm_pairs {
100✔
39
        aug_msg.clear();
48✔
40
        aug_msg.extend_from_slice(&pk.to_bytes());
48✔
41
        aug_msg.extend(&*msg);
48✔
42
        let aug_hash = hash_to_g2(&aug_msg);
48✔
43
        let pairing = aug_hash.pair(&pk);
48✔
44

48✔
45
        let mut key = Sha256::new();
48✔
46
        key.update(&aug_msg);
48✔
47
        pairs.push((key.finalize(), pairing));
48✔
48
    }
48✔
49
    // Verify aggregated signature
50
    let result = aggregate_verify_gt(
52✔
51
        &spend_bundle.aggregated_signature,
52✔
52
        pairs.iter().map(|tuple| &tuple.1),
52✔
53
    );
52✔
54
    if !result {
52✔
55
        return Err(ErrorCode::BadAggregateSignature);
32✔
56
    }
20✔
57

20✔
58
    // Collect results
20✔
59
    Ok((conditions, pairs, start_time.elapsed()))
20✔
60
}
56✔
61

62
pub fn get_flags_for_height_and_constants(height: u32, constants: &ConsensusConstants) -> u32 {
319✔
63
    //  the hard-fork initiated with 2.0. To activate June 2024
319✔
64
    //  * costs are ascribed to some unknown condition codes, to allow for
319✔
65
    // soft-forking in new conditions with cost
319✔
66
    //  * a new condition, SOFTFORK, is added which takes a first parameter to
319✔
67
    //    specify its cost. This allows soft-forks similar to the softfork
319✔
68
    //    operator
319✔
69
    //  * BLS operators introduced in the soft-fork (behind the softfork
319✔
70
    //    guard) are made available outside of the guard.
319✔
71
    //  * division with negative numbers are allowed, and round toward
319✔
72
    //    negative infinity
319✔
73
    //  * AGG_SIG_* conditions are allowed to have unknown additional
319✔
74
    //    arguments
319✔
75
    //  * Allow the block generator to be serialized with the improved klvm
319✔
76
    //   serialization format (with back-references)
319✔
77

319✔
78
    // The soft fork initiated with 2.5.0. The activation date is still TBD.
319✔
79
    // Adds a new keccak256 operator under the softfork guard with extension 1.
319✔
80
    // This operator can be hard forked in later, but is not included in a hard fork yet.
319✔
81

319✔
82
    // In hard fork 2, we enable the keccak operator outside the softfork guard
319✔
83
    let mut flags: u32 = 0;
319✔
84
    if height >= constants.hard_fork2_height {
319✔
NEW
85
        flags |= ENABLE_KECCAK_OPS_OUTSIDE_GUARD | COST_CONDITIONS;
×
86
    }
319✔
87
    flags
319✔
88
}
319✔
89

90
#[cfg(test)]
91
mod tests {
92
    use super::*;
93
    use crate::consensus_constants::TEST_CONSTANTS;
94
    use crate::make_aggsig_final_message::u64_to_bytes;
95
    use crate::opcodes::{
96
        ConditionOpcode, AGG_SIG_AMOUNT, AGG_SIG_ME, AGG_SIG_PARENT, AGG_SIG_PARENT_AMOUNT,
97
        AGG_SIG_PARENT_PUZZLE, AGG_SIG_PUZZLE, AGG_SIG_PUZZLE_AMOUNT, AGG_SIG_UNSAFE,
98
    };
99
    use chik_bls::{sign, G2Element, PublicKey, SecretKey, Signature};
100
    use chik_protocol::{Coin, CoinSpend, Program};
101
    use hex::FromHex;
102
    use hex_literal::hex;
103
    use klvm_utils::tree_hash_atom;
104
    use rstest::rstest;
105

106
    fn mk_spend(puzzle: &[u8], solution: &[u8]) -> CoinSpend {
52✔
107
        let ph = tree_hash_atom(puzzle).to_bytes();
52✔
108
        let test_coin = Coin::new(
52✔
109
            hex!("4444444444444444444444444444444444444444444444444444444444444444").into(),
52✔
110
            ph.into(),
52✔
111
            1_000_000_000,
52✔
112
        );
52✔
113
        CoinSpend::new(test_coin, Program::new(puzzle.into()), solution.into())
52✔
114
    }
52✔
115

116
    fn keys() -> (PublicKey, SecretKey) {
48✔
117
        let sk_hex = "52d75c4707e39595b27314547f9723e5530c01198af3fc5849d9a7af65631efb";
48✔
118
        let sk = SecretKey::from_bytes(&<[u8; 32]>::from_hex(sk_hex).unwrap()).unwrap();
48✔
119
        (sk.public_key(), sk)
48✔
120
    }
48✔
121

122
    fn mk_agg_sig_solution(cond: ConditionOpcode, pk: &PublicKey) -> Vec<u8> {
48✔
123
        // ((<cond> <pk> "hello"))
48✔
124
        [
48✔
125
            hex!("ffff").as_slice(),
48✔
126
            &(cond as u8).to_be_bytes(),
48✔
127
            hex!("ffb0").as_slice(),
48✔
128
            pk.to_bytes().as_slice(),
48✔
129
            hex!("ff8568656c6c6f8080").as_slice(),
48✔
130
        ]
48✔
131
        .concat()
48✔
132
    }
48✔
133

134
    fn mk_agg_sig(
48✔
135
        cond: ConditionOpcode,
48✔
136
        sk: &SecretKey,
48✔
137
        spend: &CoinSpend,
48✔
138
        msg: &[u8],
48✔
139
    ) -> Signature {
48✔
140
        let msg = match cond {
48✔
141
            AGG_SIG_AMOUNT => [
6✔
142
                msg,
6✔
143
                u64_to_bytes(spend.coin.amount).as_slice(),
6✔
144
                TEST_CONSTANTS.agg_sig_amount_additional_data.as_slice(),
6✔
145
            ]
6✔
146
            .concat(),
6✔
147
            AGG_SIG_PARENT => [
6✔
148
                msg,
6✔
149
                spend.coin.parent_coin_info.as_slice(),
6✔
150
                TEST_CONSTANTS.agg_sig_parent_additional_data.as_slice(),
6✔
151
            ]
6✔
152
            .concat(),
6✔
153
            AGG_SIG_PUZZLE => [
6✔
154
                msg,
6✔
155
                spend.coin.puzzle_hash.as_slice(),
6✔
156
                TEST_CONSTANTS.agg_sig_puzzle_additional_data.as_slice(),
6✔
157
            ]
6✔
158
            .concat(),
6✔
159
            AGG_SIG_PUZZLE_AMOUNT => [
6✔
160
                msg,
6✔
161
                spend.coin.puzzle_hash.as_slice(),
6✔
162
                u64_to_bytes(spend.coin.amount).as_slice(),
6✔
163
                TEST_CONSTANTS
6✔
164
                    .agg_sig_puzzle_amount_additional_data
6✔
165
                    .as_slice(),
6✔
166
            ]
6✔
167
            .concat(),
6✔
168
            AGG_SIG_PARENT_AMOUNT => [
6✔
169
                msg,
6✔
170
                spend.coin.parent_coin_info.as_slice(),
6✔
171
                u64_to_bytes(spend.coin.amount).as_slice(),
6✔
172
                TEST_CONSTANTS
6✔
173
                    .agg_sig_parent_amount_additional_data
6✔
174
                    .as_slice(),
6✔
175
            ]
6✔
176
            .concat(),
6✔
177
            AGG_SIG_PARENT_PUZZLE => [
6✔
178
                msg,
6✔
179
                spend.coin.parent_coin_info.as_slice(),
6✔
180
                spend.coin.puzzle_hash.as_slice(),
6✔
181
                TEST_CONSTANTS
6✔
182
                    .agg_sig_parent_puzzle_additional_data
6✔
183
                    .as_slice(),
6✔
184
            ]
6✔
185
            .concat(),
6✔
186
            AGG_SIG_UNSAFE => msg.to_vec(),
6✔
187
            AGG_SIG_ME => [
6✔
188
                msg,
6✔
189
                spend.coin.coin_id().as_slice(),
6✔
190
                TEST_CONSTANTS.agg_sig_me_additional_data.as_slice(),
6✔
191
            ]
6✔
192
            .concat(),
6✔
NEW
193
            _ => panic!("unexpected"),
×
194
        };
195
        sign(sk, msg.as_slice())
48✔
196
    }
48✔
197

198
    #[rstest]
6✔
199
    #[case(0, 0)]
200
    #[case(TEST_CONSTANTS.hard_fork_height, 0)]
201
    #[case(5_716_000, 0)]
202
    fn test_get_flags(#[case] height: u32, #[case] expected_value: u32) {
203
        assert_eq!(
204
            get_flags_for_height_and_constants(height, &TEST_CONSTANTS),
205
            expected_value
206
        );
207
    }
208

209
    #[test]
210
    fn test_invalid_puzzle_hash() {
2✔
211
        let solution = hex!(
2✔
212
            "ff\
2✔
213
ff33\
2✔
214
ffa02222222222222222222222222222222222222222222222222222222222222222\
2✔
215
ff01\
2✔
216
80\
2✔
217
80"
2✔
218
        );
2✔
219
        let test_coin = Coin::new(
2✔
220
            hex!("4444444444444444444444444444444444444444444444444444444444444444").into(),
2✔
221
            hex!("4444444444444444444444444444444444444444444444444444444444444444").into(),
2✔
222
            1_000_000_000,
2✔
223
        );
2✔
224
        let spend = CoinSpend::new(
2✔
225
            test_coin,
2✔
226
            Program::new([1_u8].as_slice().into()),
2✔
227
            solution.as_slice().into(),
2✔
228
        );
2✔
229
        let spend_bundle = SpendBundle {
2✔
230
            coin_spends: vec![spend],
2✔
231
            aggregated_signature: Signature::default(),
2✔
232
        };
2✔
233
        assert_eq!(
2✔
234
            validate_klvm_and_signature(
2✔
235
                &spend_bundle,
2✔
236
                TEST_CONSTANTS.max_block_cost_klvm,
2✔
237
                &TEST_CONSTANTS,
2✔
238
                236,
2✔
239
            )
2✔
240
            .err(),
2✔
241
            Some(ErrorCode::WrongPuzzleHash)
2✔
242
        );
2✔
243
    }
2✔
244

245
    #[test]
246
    fn test_validate_no_pks() {
2✔
247
        let solution = hex!(
2✔
248
            "ff\
2✔
249
ff33\
2✔
250
ffa02222222222222222222222222222222222222222222222222222222222222222\
2✔
251
ff01\
2✔
252
80\
2✔
253
80"
2✔
254
        );
2✔
255
        let spend = mk_spend(&[1_u8], &solution);
2✔
256
        let spend_bundle = SpendBundle {
2✔
257
            coin_spends: vec![spend],
2✔
258
            aggregated_signature: Signature::default(),
2✔
259
        };
2✔
260
        validate_klvm_and_signature(
2✔
261
            &spend_bundle,
2✔
262
            TEST_CONSTANTS.max_block_cost_klvm,
2✔
263
            &TEST_CONSTANTS,
2✔
264
            236,
2✔
265
        )
2✔
266
        .expect("SpendBundle should be valid for this test");
2✔
267
    }
2✔
268

269
    #[test]
270
    fn test_go_over_cost() {
2✔
271
        use std::fs::read_to_string;
272
        let my_str =
2✔
273
            read_to_string("../../generator-tests/large_spendbundle_validation_test.clsp.hex")
2✔
274
                .expect("test file not found");
2✔
275
        let solution = hex::decode(my_str).expect("parsing hex");
2✔
276
        // this solution makes 2400 CREATE_COIN conditions
2✔
277

2✔
278
        let spend = mk_spend(&[1_u8], &solution);
2✔
279

2✔
280
        let spend_bundle = SpendBundle {
2✔
281
            coin_spends: vec![spend],
2✔
282
            aggregated_signature: G2Element::default(),
2✔
283
        };
2✔
284
        let expected_cost = 5_527_116_044;
2✔
285
        let max_cost = expected_cost;
2✔
286
        let test_height = 236;
2✔
287
        let (conds, _, _) =
2✔
288
            validate_klvm_and_signature(&spend_bundle, max_cost, &TEST_CONSTANTS, test_height)
2✔
289
                .expect("validate_klvm_and_signature failed");
2✔
290
        assert_eq!(conds.cost, expected_cost);
2✔
291
        let result =
2✔
292
            validate_klvm_and_signature(&spend_bundle, max_cost - 1, &TEST_CONSTANTS, test_height);
2✔
293
        assert!(matches!(result, Err(ErrorCode::CostExceeded)));
2✔
294
    }
2✔
295

296
    #[rstest]
32✔
297
    fn test_validate_agg_sig(
298
        #[values(
299
            AGG_SIG_AMOUNT,
300
            AGG_SIG_PARENT,
301
            AGG_SIG_PUZZLE,
302
            AGG_SIG_PUZZLE_AMOUNT,
303
            AGG_SIG_PARENT_AMOUNT,
304
            AGG_SIG_PARENT_PUZZLE,
305
            AGG_SIG_UNSAFE,
306
            AGG_SIG_ME
307
        )]
308
        cond: ConditionOpcode,
309
    ) {
310
        let (pk, sk) = keys();
311

312
        // ((<cond> <pk> "hello"))
313
        let solution = mk_agg_sig_solution(cond, &pk);
314

315
        let spend = mk_spend(&[1_u8], &solution);
316
        let sig = mk_agg_sig(cond, &sk, &spend, b"hello");
317
        let spend_bundle = SpendBundle {
318
            coin_spends: vec![spend],
319
            aggregated_signature: sig,
320
        };
321
        validate_klvm_and_signature(
322
            &spend_bundle,
323
            TEST_CONSTANTS.max_block_cost_klvm,
324
            &TEST_CONSTANTS,
325
            236,
326
        )
327
        .expect("SpendBundle should be valid for this test");
328
    }
329

330
    #[rstest]
32✔
331
    fn test_failures(
332
        #[values(
333
            AGG_SIG_AMOUNT,
334
            AGG_SIG_PARENT,
335
            AGG_SIG_PUZZLE,
336
            AGG_SIG_PUZZLE_AMOUNT,
337
            AGG_SIG_PARENT_AMOUNT,
338
            AGG_SIG_PARENT_PUZZLE,
339
            AGG_SIG_UNSAFE,
340
            AGG_SIG_ME
341
        )]
342
        condition_code: ConditionOpcode,
343
        #[values(0, 1)] whats_wrong: u8,
344
    ) {
345
        let (pk, sk) = keys();
346
        let solution = mk_agg_sig_solution(condition_code, &pk);
347
        let msg = if whats_wrong == 0 {
348
            b"goodbye".as_slice()
349
        } else {
350
            b"hello".as_slice()
351
        };
352
        let sk = if whats_wrong == 1 {
353
            SecretKey::from_bytes(
354
                &<[u8; 32]>::from_hex(
355
                    "52d75c4707e39595b27314547f9723e5530c01198af3fc5849d9a7af65631efc",
356
                )
357
                .unwrap(),
358
            )
359
            .unwrap()
360
        } else {
361
            sk
362
        };
363
        let spend = mk_spend(&[1_u8], &solution);
364
        let sig = mk_agg_sig(condition_code, &sk, &spend, msg);
365
        let spend_bundle = SpendBundle {
366
            coin_spends: vec![spend],
367
            aggregated_signature: sig,
368
        };
369
        let result = validate_klvm_and_signature(
370
            &spend_bundle,
371
            TEST_CONSTANTS.max_block_cost_klvm,
372
            &TEST_CONSTANTS,
373
            246,
374
        );
375
        assert!(matches!(result, Err(ErrorCode::BadAggregateSignature)));
376
    }
377
}
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

© 2025 Coveralls, Inc