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

ergoplatform / sigma-rust / 15667516698

15 Jun 2025 09:22PM UTC coverage: 77.672% (-0.6%) from 78.291%
15667516698

Pull #832

github

web-flow
Merge 39c40f47b into 6f12ef8f2
Pull Request #832: Soft Fork support

298 of 462 new or added lines in 72 files covered. (64.5%)

53 existing lines in 22 files now uncovered.

11765 of 15147 relevant lines covered (77.67%)

2.91 hits per line

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

91.49
/ergotree-interpreter/src/sigma_protocol/verifier.rs
1
//! Verifier
2

3
use super::dht_protocol;
4
use super::dht_protocol::FirstDhTupleProverMessage;
5
use super::fiat_shamir::FiatShamirTreeSerializationError;
6
use super::prover::ProofBytes;
7
use super::sig_serializer::SigParsingError;
8
use super::unchecked_tree::UncheckedDhTuple;
9
use super::{
10
    dlog_protocol,
11
    fiat_shamir::{fiat_shamir_hash_fn, fiat_shamir_tree_to_bytes},
12
    sig_serializer::parse_sig_compute_challenges,
13
    unchecked_tree::{UncheckedLeaf, UncheckedSchnorr},
14
    SigmaBoolean, UncheckedTree,
15
};
16
use crate::eval::EvalError;
17
use crate::eval::{reduce_to_crypto, ReductionDiagnosticInfo};
18
use alloc::vec::Vec;
19
use bounded_vec::BoundedVecOutOfBounds;
20
use dlog_protocol::FirstDlogProverMessage;
21
use ergotree_ir::chain::context::Context;
22
use ergotree_ir::ergo_tree::ErgoTree;
23
use ergotree_ir::ergo_tree::ErgoTreeError;
24

25
use derive_more::From;
26
use thiserror::Error;
27

28
/// Errors on proof verification
29
#[derive(Error, Debug, From)]
30
pub enum VerifierError {
31
    /// Failed to parse ErgoTree from bytes
32
    #[error("ErgoTreeError: {0}")]
33
    ErgoTreeError(ErgoTreeError),
34
    /// Failed to evaluate ErgoTree
35
    #[error("EvalError: {0}")]
36
    EvalError(EvalError),
37
    /// Signature parsing error
38
    #[error("SigParsingError: {0}")]
39
    SigParsingError(SigParsingError),
40
    /// Error while tree serialization for Fiat-Shamir hash
41
    #[error("Fiat-Shamir tree serialization error: {0}")]
42
    FiatShamirTreeSerializationError(FiatShamirTreeSerializationError),
43
    /// BoundedVecOutOfBounds error
44
    #[error("Bounded vec out of bounds: {0}")]
45
    BoundedVecOutOfBounds(BoundedVecOutOfBounds),
46
}
47

48
/// Result of Box.ergoTree verification procedure (see `verify` method).
49
#[derive(Debug, Clone)]
50
pub struct VerificationResult {
51
    /// result of SigmaProp condition verification via sigma protocol
52
    pub result: bool,
53
    /// estimated cost of contract execution
54
    pub cost: u64,
55
    /// Diagnostic information about the reduction (pretty printed expr and/or env)
56
    pub diag: ReductionDiagnosticInfo,
57
}
58

59
/// Verifier for the proofs generater by [`super::prover::Prover`]
60
pub trait Verifier {
61
    /// Executes the script in a given context.
62
    /// Step 1: Deserialize context variables
63
    /// Step 2: Evaluate expression and produce SigmaProp value, which is zero-knowledge statement (see also `SigmaBoolean`).
64
    /// Step 3: Verify that the proof is presented to satisfy SigmaProp conditions.
65
    fn verify<'ctx>(
6✔
66
        &self,
67
        tree: &ErgoTree,
68
        ctx: &Context<'ctx>,
69
        proof: ProofBytes,
70
        message: &[u8],
71
    ) -> Result<VerificationResult, VerifierError> {
72
        let reduction_result = reduce_to_crypto(tree, ctx)?;
10✔
73
        let res: bool = match reduction_result.sigma_prop {
4✔
74
            SigmaBoolean::TrivialProp(b) => b,
1✔
75
            sb => {
4✔
76
                match proof {
4✔
77
                    ProofBytes::Empty => false,
×
78
                    ProofBytes::Some(proof_bytes) => {
4✔
79
                        // Perform Verifier Steps 1-3
80
                        let unchecked_tree = parse_sig_compute_challenges(&sb, proof_bytes)?;
7✔
81
                        // Perform Verifier Steps 4-6
82
                        check_commitments(unchecked_tree, message)?
8✔
83
                    }
84
                }
85
            }
86
        };
87
        Ok(VerificationResult {
3✔
88
            result: res,
3✔
89
            cost: 0,
×
90
            diag: reduction_result.diag,
5✔
91
        })
92
    }
93
}
94

95
/// Verify that the signature is presented to satisfy SigmaProp conditions.
96
pub fn verify_signature(
1✔
97
    sigma_tree: SigmaBoolean,
98
    message: &[u8],
99
    signature: &[u8],
100
) -> Result<bool, VerifierError> {
101
    let res: bool = match sigma_tree {
1✔
102
        SigmaBoolean::TrivialProp(b) => b,
×
103
        sb => {
1✔
104
            match signature {
105
                [] => false,
1✔
106
                _ => {
107
                    // Perform Verifier Steps 1-3
108
                    let unchecked_tree = parse_sig_compute_challenges(&sb, signature.to_vec())?;
2✔
109
                    // Perform Verifier Steps 4-6
110
                    check_commitments(unchecked_tree, message)?
2✔
111
                }
112
            }
113
        }
114
    };
115
    Ok(res)
1✔
116
}
117

118
/// Perform Verifier Steps 4-6
119
fn check_commitments(sp: UncheckedTree, message: &[u8]) -> Result<bool, VerifierError> {
3✔
120
    // Perform Verifier Step 4
121
    let new_root = compute_commitments(sp)?;
3✔
122
    let mut s = fiat_shamir_tree_to_bytes(&new_root.clone().into())?;
8✔
123
    s.append(&mut message.to_vec());
3✔
124
    // Verifier Steps 5-6: Convert the tree to a string `s` for input to the Fiat-Shamir hash function,
125
    // using the same conversion as the prover in 7
126
    // Accept the proof if the challenge at the root of the tree is equal to the Fiat-Shamir hash of `s`
127
    // (and, if applicable,  the associated data). Reject otherwise.
128
    let expected_challenge = fiat_shamir_hash_fn(s.as_slice());
5✔
129
    Ok(new_root.challenge() == expected_challenge.into())
16✔
130
}
131

132
/// Verifier Step 4: For every leaf node, compute the commitment a from the challenge e and response $z$,
133
/// per the verifier algorithm of the leaf's Sigma-protocol.
134
/// If the verifier algorithm of the Sigma-protocol for any of the leaves rejects, then reject the entire proof.
135
pub fn compute_commitments(sp: UncheckedTree) -> Result<UncheckedTree, BoundedVecOutOfBounds> {
3✔
136
    Ok(match sp {
7✔
137
        UncheckedTree::UncheckedLeaf(leaf) => match leaf {
3✔
138
            UncheckedLeaf::UncheckedSchnorr(sn) => {
3✔
139
                let a = dlog_protocol::interactive_prover::compute_commitment(
3✔
140
                    &sn.proposition,
141
                    &sn.challenge,
3✔
142
                    &sn.second_message,
3✔
143
                );
144
                UncheckedSchnorr {
4✔
145
                    commitment_opt: Some(FirstDlogProverMessage { a: a.into() }),
4✔
146
                    ..sn
147
                }
148
                .into()
149
            }
150
            UncheckedLeaf::UncheckedDhTuple(dh) => {
4✔
151
                let (a, b) = dht_protocol::interactive_prover::compute_commitment(
8✔
152
                    &dh.proposition,
153
                    &dh.challenge,
154
                    &dh.second_message,
4✔
155
                );
156
                UncheckedDhTuple {
4✔
157
                    commitment_opt: Some(FirstDhTupleProverMessage::new(a, b)),
4✔
158
                    ..dh
159
                }
160
                .into()
161
            }
162
        },
163
        UncheckedTree::UncheckedConjecture(conj) => conj
9✔
164
            .clone()
3✔
165
            .with_children(
166
                conj.children_ust()
6✔
167
                    .iter()
168
                    .cloned()
169
                    .map(compute_commitments)
NEW
170
                    .collect::<Result<Vec<_>, _>>()?,
×
171
            )?
172
            .into(),
5✔
173
    })
174
}
175

176
/// Test Verifier implementation
177
pub struct TestVerifier;
178

179
impl Verifier for TestVerifier {}
180

181
#[allow(clippy::unwrap_used)]
182
#[allow(clippy::panic)]
183
#[cfg(test)]
184
#[cfg(feature = "arbitrary")]
185
mod tests {
186
    use core::convert::TryFrom;
187

188
    use crate::sigma_protocol::private_input::{DhTupleProverInput, DlogProverInput, PrivateInput};
189
    use crate::sigma_protocol::prover::hint::HintsBag;
190
    use crate::sigma_protocol::prover::{Prover, TestProver};
191

192
    use super::*;
193
    use ergotree_ir::mir::atleast::Atleast;
194
    use ergotree_ir::mir::constant::{Constant, Literal};
195
    use ergotree_ir::mir::expr::Expr;
196
    use ergotree_ir::mir::sigma_and::SigmaAnd;
197
    use ergotree_ir::mir::sigma_or::SigmaOr;
198
    use ergotree_ir::mir::value::CollKind;
199
    use ergotree_ir::sigma_protocol::sigma_boolean::SigmaProp;
200
    use ergotree_ir::types::stype::SType;
201
    use proptest::collection::vec;
202
    use proptest::prelude::*;
203
    use sigma_test_util::force_any_val;
204

205
    fn proof_append_some_byte(proof: &ProofBytes) -> ProofBytes {
206
        match proof {
207
            ProofBytes::Empty => panic!(),
208
            ProofBytes::Some(bytes) => {
209
                let mut new_bytes = bytes.clone();
210
                new_bytes.push(1u8);
211
                ProofBytes::Some(new_bytes)
212
            }
213
        }
214
    }
215
    proptest! {
216

217
        #![proptest_config(ProptestConfig::with_cases(16))]
218

219
        #[test]
220
        fn test_prover_verifier_p2pk(secret in any::<DlogProverInput>(), message in vec(any::<u8>(), 100..200)) {
221
            let pk = secret.public_image();
222
            let tree = ErgoTree::try_from(Expr::Const(pk.into())).unwrap();
223

224
            let prover = TestProver {
225
                secrets: vec![PrivateInput::DlogProverInput(secret)],
226
            };
227
            let res = prover.prove(&tree,
228
                &force_any_val::<Context>(),
229
                message.as_slice(),
230
                &HintsBag::empty());
231
            let proof = res.unwrap().proof;
232
            let verifier = TestVerifier;
233
            prop_assert_eq!(verifier.verify(&tree,
234
                                            &force_any_val::<Context>(),
235
                                            proof.clone(),
236
                                            message.as_slice())
237
                            .unwrap().result,
238
                            true);
239

240
            // possible to append bytes
241
            prop_assert_eq!(verifier.verify(&tree,
242
                                            &force_any_val::<Context>(),
243
                                            proof_append_some_byte(&proof),
244
                                            message.as_slice())
245
                            .unwrap().result,
246
                            true);
247

248
            // wrong message
249
            prop_assert_eq!(verifier.verify(&tree,
250
                                            &force_any_val::<Context>(),
251
                                            proof,
252
                                            vec![1u8; 100].as_slice())
253
                            .unwrap().result,
254
                            false);
255
        }
256

257
        #[test]
258
        fn test_prover_verifier_dht(secret in any::<DhTupleProverInput>(), message in vec(any::<u8>(), 100..200)) {
259
            let pk = secret.public_image().clone();
260
            let tree = ErgoTree::try_from(Expr::Const(pk.into())).unwrap();
261

262
            let prover = TestProver {
263
                secrets: vec![PrivateInput::DhTupleProverInput(secret)],
264
            };
265
            let res = prover.prove(&tree,
266
                &force_any_val::<Context>(),
267
                message.as_slice(),
268
                &HintsBag::empty());
269
            let proof = res.unwrap().proof;
270
            let verifier = TestVerifier;
271
            prop_assert_eq!(verifier.verify(&tree,
272
                                            &force_any_val::<Context>(),
273
                                            proof.clone(),
274
                                            message.as_slice())
275
                            .unwrap().result,
276
                            true);
277

278
            // possible to append bytes
279
            prop_assert_eq!(verifier.verify(&tree,
280
                                            &force_any_val::<Context>(),
281
                                            proof_append_some_byte(&proof),
282
                                            message.as_slice())
283
                            .unwrap().result,
284
                            true);
285

286
            // wrong message
287
            prop_assert_eq!(verifier.verify(&tree,
288
                                            &force_any_val::<Context>(),
289
                                            proof,
290
                                            vec![1u8; 100].as_slice())
291
                            .unwrap().result,
292
                            false);
293
        }
294

295
        #[test]
296
        fn test_prover_verifier_conj_and(secret1 in any::<PrivateInput>(),
297
                                         secret2 in any::<PrivateInput>(),
298
                                         message in vec(any::<u8>(), 100..200)) {
299
            let pk1 = secret1.public_image();
300
            let pk2 = secret2.public_image();
301
            let expr: Expr = SigmaAnd::new(vec![Expr::Const(pk1.into()), Expr::Const(pk2.into())])
302
                .unwrap()
303
                .into();
304
            let tree = ErgoTree::try_from(expr).unwrap();
305
            let prover = TestProver {
306
                secrets: vec![secret1, secret2],
307
            };
308
            let res = prover.prove(&tree,
309
                &force_any_val::<Context>(),
310
                message.as_slice(),
311
                &HintsBag::empty());
312
            let proof = res.unwrap().proof;
313
            let verifier = TestVerifier;
314
            let ver_res = verifier.verify(&tree,
315
                                          &force_any_val::<Context>(),
316
                                          proof,
317
                                          message.as_slice());
318
            prop_assert_eq!(ver_res.unwrap().result, true);
319
        }
320

321
        #[test]
322
        fn test_prover_verifier_conj_and_and(secret1 in any::<PrivateInput>(),
323
                                             secret2 in any::<PrivateInput>(),
324
                                             secret3 in any::<PrivateInput>(),
325
                                             message in vec(any::<u8>(), 100..200)) {
326
            let pk1 = secret1.public_image();
327
            let pk2 = secret2.public_image();
328
            let pk3 = secret3.public_image();
329
            let expr: Expr = SigmaAnd::new(vec![
330
                Expr::Const(pk1.into()),
331
                SigmaAnd::new(vec![Expr::Const(pk2.into()), Expr::Const(pk3.into())])
332
                    .unwrap()
333
                    .into(),
334
            ]).unwrap().into();
335
            let tree = ErgoTree::try_from(expr).unwrap();
336
            let prover = TestProver { secrets: vec![secret1, secret2, secret3] };
337
            let res = prover.prove(&tree,
338
                &force_any_val::<Context>(),
339
                message.as_slice(),
340
                &HintsBag::empty());
341
            let proof = res.unwrap().proof;
342
            let verifier = TestVerifier;
343
            let ver_res = verifier.verify(&tree,
344
                                          &force_any_val::<Context>(),
345
                                          proof,
346
                                          message.as_slice());
347
            prop_assert_eq!(ver_res.unwrap().result, true);
348
        }
349

350
        #[test]
351
        fn test_prover_verifier_conj_or(secret1 in any::<PrivateInput>(),
352
                                         secret2 in any::<PrivateInput>(),
353
                                         message in vec(any::<u8>(), 100..200)) {
354
            let pk1 = secret1.public_image();
355
            let pk2 = secret2.public_image();
356
            let expr: Expr = SigmaOr::new(vec![Expr::Const(pk1.into()), Expr::Const(pk2.into())])
357
                .unwrap()
358
                .into();
359
            let tree = ErgoTree::try_from(expr).unwrap();
360
            let secrets = vec![secret1, secret2];
361
            // any secret (out of 2) known to prover should be enough
362
            for secret in secrets {
363
                let prover = TestProver {
364
                    secrets: vec![secret.clone()],
365
                };
366
                let res = prover.prove(&tree,
367
                    &force_any_val::<Context>(),
368
                    message.as_slice(),
369
                    &HintsBag::empty());
370
                let proof = res.unwrap_or_else(|_| panic!("proof failed for secret: {:?}", secret)).proof;
371
                let verifier = TestVerifier;
372
                let ver_res = verifier.verify(&tree,
373
                                              &force_any_val::<Context>(),
374
                                              proof,
375
                                              message.as_slice());
376
                prop_assert_eq!(ver_res.unwrap().result, true, "verify failed on secret: {:?}", &secret);
377
            }
378
        }
379

380
        #[test]
381
        fn test_prover_verifier_conj_or_or(secret1 in any::<PrivateInput>(),
382
                                             secret2 in any::<PrivateInput>(),
383
                                             secret3 in any::<PrivateInput>(),
384
                                             message in vec(any::<u8>(), 100..200)) {
385
            let pk1 = secret1.public_image();
386
            let pk2 = secret2.public_image();
387
            let pk3 = secret3.public_image();
388
            let expr: Expr = SigmaOr::new(vec![
389
                Expr::Const(pk1.into()),
390
                SigmaOr::new(vec![Expr::Const(pk2.into()), Expr::Const(pk3.into())])
391
                    .unwrap()
392
                    .into(),
393
            ]).unwrap().into();
394
            let tree = ErgoTree::try_from(expr).unwrap();
395
            let secrets = vec![secret1, secret2, secret3];
396
            // any secret (out of 3) known to prover should be enough
397
            for secret in secrets {
398
                let prover = TestProver {
399
                    secrets: vec![secret.clone()],
400
                };
401
                let res = prover.prove(&tree,
402
                    &force_any_val::<Context>(),
403
                    message.as_slice(),
404
                    &HintsBag::empty());
405
                let proof = res.unwrap_or_else(|_| panic!("proof failed for secret: {:?}", secret)).proof;
406
                let verifier = TestVerifier;
407
                let ver_res = verifier.verify(&tree,
408
                                              &force_any_val::<Context>(),
409
                                              proof,
410
                                              message.as_slice());
411
                prop_assert_eq!(ver_res.unwrap().result, true, "verify failed on secret: {:?}", &secret);
412
            }
413
        }
414

415
        #[test]
416
        fn test_prover_verifier_atleast(secret1 in any::<DlogProverInput>(),
417
                                            secret2 in any::<DlogProverInput>(),
418
                                             secret3 in any::<DlogProverInput>(),
419
                                             message in vec(any::<u8>(), 100..200)) {
420
            let bound = Expr::Const(2i32.into());
421
            let inputs = Literal::Coll(
422
                CollKind::from_collection(
423
                    SType::SSigmaProp,
424
                    [
425
                        SigmaProp::from(secret1.public_image()).into(),
426
                        SigmaProp::from(secret2.public_image()).into(),
427
                        SigmaProp::from(secret3.public_image()).into(),
428
                    ],
429
                )
430
                .unwrap(),
431
            );
432
            let input = Constant {
433
                tpe: SType::SColl(SType::SSigmaProp.into()),
434
                v: inputs,
435
            }
436
            .into();
437
            let expr: Expr = Atleast::new(bound, input).unwrap().into();
438
            let tree = ErgoTree::try_from(expr).unwrap();
439
            let prover = TestProver {
440
                secrets: vec![secret1.into(), secret3.into()],
441
            };
442

443
            let res = prover.prove(&tree,
444
                &force_any_val::<Context>(),
445
                message.as_slice(),
446
                &HintsBag::empty());
447
            let proof = res.unwrap().proof;
448
            let verifier = TestVerifier;
449
            let ver_res = verifier.verify(&tree,
450
                &force_any_val::<Context>(),
451
                proof,
452
                message.as_slice());
453
            prop_assert_eq!(ver_res.unwrap().result, true)
454
        }
455
    }
456
}
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