• 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

75.42
/ergotree-interpreter/src/sigma_protocol/prover.rs
1
//! Interpreter with enhanced functionality to prove statements.
2

3
mod prover_result;
4

5
pub mod hint;
6

7
use crate::eval::reduce_to_crypto;
8
use crate::eval::ReductionDiagnosticInfo;
9
use crate::sigma_protocol::dht_protocol;
10
use crate::sigma_protocol::fiat_shamir::fiat_shamir_hash_fn;
11
use crate::sigma_protocol::fiat_shamir::fiat_shamir_tree_to_bytes;
12
use crate::sigma_protocol::proof_tree::ProofTree;
13
use crate::sigma_protocol::unchecked_tree::{UncheckedDhTuple, UncheckedLeaf};
14
use crate::sigma_protocol::unproven_tree::CandUnproven;
15
use crate::sigma_protocol::unproven_tree::CorUnproven;
16
use crate::sigma_protocol::unproven_tree::NodePosition;
17
use crate::sigma_protocol::unproven_tree::UnprovenDhTuple;
18
use crate::sigma_protocol::Challenge;
19
use crate::sigma_protocol::UnprovenLeaf;
20
use alloc::vec::Vec;
21
use bounded_vec::BoundedVecOutOfBounds;
22
use core::convert::TryInto;
23
use ergotree_ir::sigma_protocol::sigma_boolean::SigmaBoolean;
24
use gf2_192::gf2_192poly::Gf2_192Poly;
25
use gf2_192::gf2_192poly::Gf2_192PolyError;
26
use gf2_192::Gf2_192Error;
27

28
use ergotree_ir::ergo_tree::ErgoTree;
29
use ergotree_ir::ergo_tree::ErgoTreeError;
30
use ergotree_ir::sigma_protocol::sigma_boolean::SigmaConjecture;
31
use ergotree_ir::sigma_protocol::sigma_boolean::SigmaProofOfKnowledgeTree;
32
pub use prover_result::*;
33

34
use self::hint::HintsBag;
35

36
use super::dlog_protocol;
37
use super::fiat_shamir::FiatShamirTreeSerializationError;
38
use super::private_input::PrivateInput;
39
use super::proof_tree;
40
use super::proof_tree::ProofTreeLeaf;
41
use super::sig_serializer::serialize_sig;
42
use super::unchecked_tree::UncheckedConjecture;
43
use super::unchecked_tree::UncheckedSchnorr;
44
use super::unchecked_tree::UncheckedTree;
45
use super::unproven_tree::CthresholdUnproven;
46
use super::unproven_tree::UnprovenConjecture;
47
use super::unproven_tree::UnprovenSchnorr;
48
use super::unproven_tree::UnprovenTree;
49
use super::FirstProverMessage::FirstDhtProverMessage;
50
use super::FirstProverMessage::FirstDlogProverMessage;
51

52
use crate::eval::EvalError;
53
use ergotree_ir::chain::context::Context;
54

55
use thiserror::Error;
56

57
/// Prover errors
58
#[derive(Error, Debug)]
59
pub enum ProverError {
60
    /// Failed to parse ErgoTree
61
    #[error("Ergo tree error: {0}")]
62
    ErgoTreeError(ErgoTreeError),
63
    /// Failed to evaluate ErgoTree
64
    #[error("Evaluation error: {0}")]
65
    EvalError(EvalError),
66
    /// `gf2_192` error
67
    #[error("gf2_192 error: {0}")]
68
    Gf2_192Error(Gf2_192Error),
69
    /// Script reduced to false
70
    #[error("Script reduced to false")]
71
    ReducedToFalse,
72
    /// Script reduced to false with diagnostic info
73
    #[error("Script reduced to false. Diagnostic info: {0}")]
74
    ReducedToFalseWithDiag(ReductionDiagnosticInfo),
75
    /// Failed on step2(prover does not have enough witnesses to perform the proof)
76
    #[error("Failed on step2(prover does not have enough witnesses to perform the proof)")]
77
    TreeRootIsNotReal,
78
    /// Simulated leaf does not have challenge
79
    #[error("Simulated leaf does not have challenge")]
80
    SimulatedLeafWithoutChallenge,
81
    /// Lacking challenge on step 9 for "real" unproven tree
82
    #[error("Lacking challenge on step 9 for \"real\" unproven tree")]
83
    RealUnprovenTreeWithoutChallenge,
84
    /// Cannot find a secret for "real" unproven leaf
85
    #[error("Cannot find a secret for \"real\" unproven leaf")]
86
    SecretNotFound,
87
    /// Unexpected value encountered
88
    #[error("Unexpected: {0}")]
89
    Unexpected(&'static str), // keep it &str to avoid leaking sensitive data in runtime
90
    /// Error while tree serialization for Fiat-Shamir hash
91
    #[error("Fiat-Shamir tree serialization error: {0}")]
92
    FiatShamirTreeSerializationError(FiatShamirTreeSerializationError),
93
    /// Unsupported operation
94
    #[error("RNG is not available in no_std environments, can't generate signature without Hint")]
95
    Unsupported,
96
    /// BoundedVecOutOfBounds error
97
    #[error("BoundedVec error: {0}")]
98
    BoundedVecOutOfBounds(#[from] BoundedVecOutOfBounds),
99
}
100

101
impl From<ErgoTreeError> for ProverError {
102
    fn from(e: ErgoTreeError) -> Self {
×
103
        ProverError::ErgoTreeError(e)
×
104
    }
105
}
106

107
impl From<FiatShamirTreeSerializationError> for ProverError {
108
    fn from(e: FiatShamirTreeSerializationError) -> Self {
×
109
        ProverError::FiatShamirTreeSerializationError(e)
×
110
    }
111
}
112

113
impl From<Gf2_192Error> for ProverError {
114
    fn from(e: Gf2_192Error) -> Self {
×
115
        ProverError::Gf2_192Error(e)
×
116
    }
117
}
118

119
impl From<Gf2_192PolyError> for ProverError {
120
    fn from(e: Gf2_192PolyError) -> Self {
×
121
        ProverError::Gf2_192Error(Gf2_192Error::Gf2_192PolyError(e))
×
122
    }
123
}
124

125
/// Prover
126
pub trait Prover {
127
    /// Secrets of the prover
128
    fn secrets(&self) -> &[PrivateInput];
129

130
    /// Add an extra secret to the prover
131
    fn append_secret(&mut self, input: PrivateInput);
132

133
    /// The comments in this section are taken from the algorithm for the
134
    /// Sigma-protocol prover as described in the ErgoScript white-paper
135
    /// <https://ergoplatform.org/docs/ErgoScript.pdf>, Appendix A
136
    ///
137
    /// Generate proofs for the given message for ErgoTree reduced to Sigma boolean expression
138
    fn prove<'ctx>(
2✔
139
        &self,
140
        tree: &ErgoTree,
141
        ctx: &'ctx Context,
142
        message: &[u8],
143
        hints_bag: &HintsBag,
144
    ) -> Result<ProverResult, ProverError> {
145
        let ctx_ext = ctx.extension.clone();
2✔
146
        let reduction_result = reduce_to_crypto(tree, ctx).map_err(ProverError::EvalError)?;
4✔
147
        self.generate_proof(reduction_result.sigma_prop, message, hints_bag)
4✔
148
            .map(|p| ProverResult {
6✔
149
                proof: p,
×
150
                extension: ctx_ext,
2✔
151
            })
152
            .map_err(|e| match e {
4✔
153
                ProverError::ReducedToFalse => {
×
154
                    ProverError::ReducedToFalseWithDiag(reduction_result.diag.clone())
4✔
155
                }
156
                _ => e,
1✔
157
            })
158
    }
159

160
    /// Generate proofs for the given message for the given Sigma boolean expression
161
    fn generate_proof(
2✔
162
        &self,
163
        sigmabool: SigmaBoolean,
164
        message: &[u8],
165
        hints_bag: &HintsBag,
166
    ) -> Result<ProofBytes, ProverError> {
167
        let unchecked_tree_opt = match sigmabool {
10✔
168
            SigmaBoolean::TrivialProp(true) => Ok(None),
2✔
169
            SigmaBoolean::TrivialProp(false) => Err(ProverError::ReducedToFalse),
2✔
170
            sb => {
2✔
171
                let tree = convert_to_unproven(sb)?;
4✔
172
                let unchecked_tree = prove_to_unchecked(self, tree, message, hints_bag)?;
5✔
173
                Ok(Some(unchecked_tree))
2✔
174
            }
175
        }?;
176
        Ok(match unchecked_tree_opt {
4✔
177
            Some(tree) => serialize_sig(tree),
4✔
178
            None => ProofBytes::Empty,
2✔
179
        })
180
    }
181
}
182

183
/// The comments in this section are taken from the algorithm for the
184
/// Sigma-protocol prover as described in the white paper
185
/// <https://ergoplatform.org/docs/ErgoScript.pdf> (Appendix A)
186
// if we are concerned about timing attacks against the prover, we should make sure that this code
187
//  takes the same amount of time regardless of which nodes are real and which nodes are simulated
188
//  In particular, we should avoid the use of exists and forall, because they short-circuit the evaluation
189
//  once the right value is (or is not) found. We should also make all loops look similar, the same
190
//  amount of copying is done regardless of what's real or simulated,
191
//  real vs. simulated computations take the same time, etc.
192
fn prove_to_unchecked<P: Prover + ?Sized>(
2✔
193
    prover: &P,
194
    unproven_tree: UnprovenTree,
195
    message: &[u8],
196
    hints_bag: &HintsBag,
197
) -> Result<UncheckedTree, ProverError> {
198
    // Prover Step 1: Mark as real everything the prover can prove
199
    let step1 = mark_real(prover, unproven_tree, hints_bag)?;
2✔
200
    // dbg!(&step1);
201

202
    // Prover Step 2: If the root of the tree is marked "simulated" then the prover does not have enough witnesses
203
    // to perform the proof. Abort.
204
    if !step1.is_real() {
4✔
205
        return Err(ProverError::TreeRootIsNotReal);
1✔
206
    }
207

208
    // Prover Step 3: Change some "real" nodes to "simulated" to make sure each node
209
    // has the right number of simulated children.
210

211
    let step3 = polish_simulated(prover, step1)?;
4✔
212
    // dbg!(&step3);
213

214
    // Prover Steps 4, 5, and 6 together: find challenges for simulated nodes; simulate simulated leaves;
215
    // compute commitments for real leaves
216
    let step6 = simulate_and_commit(step3, hints_bag)?;
4✔
217
    // dbg!(&step6);
218

219
    // Prover Steps 7: convert the relevant information in the tree (namely, tree structure, node types,
220
    // the statements being proven and commitments at the leaves)
221
    // to a string
222
    let var_name = fiat_shamir_tree_to_bytes(&step6.clone().into())?;
4✔
223
    let mut s = var_name;
2✔
224

225
    // Prover Step 8: compute the challenge for the root of the tree as the Fiat-Shamir hash of s
226
    // and the message being signed.
227
    s.append(&mut message.to_vec());
4✔
228
    let root_challenge: Challenge = fiat_shamir_hash_fn(s.as_slice()).into();
2✔
229
    let step8 = step6.with_challenge(root_challenge);
2✔
230
    // dbg!(&step8);
231

232
    // Prover Step 9: complete the proof by computing challenges at real nodes and additionally responses at real leaves
233
    let step9 = proving(prover, step8.into(), hints_bag)?;
2✔
234
    // dbg!(&step9);
235
    // Prover Step 10: output the right information into the proof
236
    convert_to_unchecked(step9)
2✔
237
}
238

239
/**
240
Prover Step 1: This step will mark as "real" every node for which the prover can produce a real proof.
241
This step may mark as "real" more nodes than necessary if the prover has more than the minimal
242
necessary number of witnesses (for example, more than one child of an OR).
243
This will be corrected in the next step.
244
In a bottom-up traversal of the tree, do the following for each node:
245
 */
246
fn mark_real<P: Prover + ?Sized>(
2✔
247
    prover: &P,
248
    unproven_tree: UnprovenTree,
249
    hints_bag: &HintsBag,
250
) -> Result<UnprovenTree, ProverError> {
251
    proof_tree::rewrite_bu(unproven_tree.into(), &|tree| {
4✔
252
        Ok(match tree {
4✔
253
            ProofTree::UnprovenTree(unp) => match unp {
2✔
254
                UnprovenTree::UnprovenLeaf(unp_leaf) => {
2✔
255
                    // If the node is a leaf, mark it "real'' if either the witness for it is
256
                    // available or a hint shows the secret is known to an external participant in multi-signing;
257
                    // else mark it "simulated"
258
                    let secret_known = hints_bag.real_images().contains(&unp_leaf.proposition())
3✔
259
                        || prover
2✔
260
                            .secrets()
×
261
                            .iter()
×
262
                            .any(|s| s.public_image() == unp_leaf.proposition());
4✔
263
                    Some(unp_leaf.clone().with_simulated(!secret_known).into())
2✔
264
                }
265
                UnprovenTree::UnprovenConjecture(unp_conj) => match unp_conj {
2✔
266
                    UnprovenConjecture::CandUnproven(cand) => {
2✔
267
                        // If the node is AND, mark it "real" if all of its children are marked real; else mark it "simulated"
268
                        let simulated = cast_to_unp(cand.children.as_slice())?
4✔
269
                            .iter()
×
270
                            .any(|c| c.simulated());
6✔
271
                        Some(
2✔
272
                            CandUnproven {
2✔
273
                                simulated,
×
274
                                ..cand.clone()
2✔
275
                            }
276
                            .into(),
×
277
                        )
278
                    }
279
                    UnprovenConjecture::CorUnproven(cor) => {
2✔
280
                        // If the node is OR, mark it "real" if at least one child is marked real; else mark it "simulated"
281
                        let simulated = cast_to_unp(cor.children.as_slice())?
4✔
282
                            .iter()
×
283
                            .all(|c| c.simulated());
6✔
284
                        Some(
2✔
285
                            CorUnproven {
2✔
286
                                simulated,
×
287
                                ..cor.clone()
2✔
288
                            }
289
                            .into(),
×
290
                        )
291
                    }
292
                    UnprovenConjecture::CthresholdUnproven(ct) => {
2✔
293
                        // If the node is THRESHOLD(k), mark it "real" if at least k of its children are marked real; else mark it "simulated"
294
                        let simulated = cast_to_unp(ct.children.as_slice())?
6✔
295
                            .iter()
×
296
                            .filter(|c| c.is_real())
4✔
297
                            .count()
×
298
                            < ct.k as usize;
4✔
299
                        Some(ct.clone().with_simulated(simulated).into())
2✔
300
                    }
301
                },
302
            },
303
            ProofTree::UncheckedTree(_) => None,
×
304
        })
305
    })?
306
    .try_into()
307
    .map_err(|_| ProverError::Unexpected("mark_real: failed to get UnprovenTree from ProofTree"))
×
308
}
309

310
/// Set positions for children of a unproven inner node (conjecture, so AND/OR/THRESHOLD)
311
fn set_positions(uc: UnprovenConjecture) -> Result<UnprovenConjecture, ProverError> {
2✔
312
    let upd_children = uc
4✔
313
        .children()
314
        .iter()
315
        .cloned()
316
        .map(|c| match c {
4✔
UNCOV
317
            ProofTree::UncheckedTree(_) => Err(ProverError::Unexpected(
×
318
                "set_positions: expected UnprovenTree, got UncheckedTree",
319
            )),
320
            ProofTree::UnprovenTree(unp) => Ok(unp),
2✔
321
        })
322
        .enumerate()
323
        .map(|(idx, utree)| utree.map(|utree| utree.with_position(uc.position().child(idx)).into()))
8✔
324
        .collect::<Result<Vec<_>, _>>()?;
325
    Ok(match uc {
4✔
326
        UnprovenConjecture::CandUnproven(cand) => cand.with_children(upd_children).into(),
4✔
327
        UnprovenConjecture::CorUnproven(cor) => cor.with_children(upd_children).into(),
4✔
328
        UnprovenConjecture::CthresholdUnproven(ct) => {
2✔
329
            ct.with_children(upd_children.try_into()?).into()
2✔
330
        }
331
    })
332
}
333

334
/// If the node is OR marked "real",  mark all but one of its children "simulated"
335
/// (the node is guaranteed by step 1 to have at least one "real" child).
336
/// Which particular child is left "real" is not important for security;
337
/// the choice can be guided by efficiency or convenience considerations.
338
fn make_cor_children_simulated(cor: CorUnproven) -> Result<CorUnproven, ProverError> {
2✔
339
    let casted_children = cast_to_unp(cor.children.as_slice())?;
4✔
340
    let first_real_child = casted_children.iter().find(|it| it.is_real()).ok_or({
10✔
341
        ProverError::Unexpected(
2✔
342
            "make_cor_children_simulated: no real child is found amoung Cor children",
343
        )
344
    })?;
345
    let children = casted_children
2✔
346
        .iter()
347
        .map(|c| {
2✔
348
            if c == first_real_child || c.simulated() {
2✔
349
                c.clone()
2✔
350
            } else {
351
                c.clone().with_simulated(true)
2✔
352
            }
353
        })
354
        .map(|c| c.into())
4✔
355
        .collect();
356
    Ok(CorUnproven { children, ..cor })
2✔
357
}
358

359
fn cast_to_unp(children: &[ProofTree]) -> Result<Vec<UnprovenTree>, ProverError> {
2✔
360
    children
2✔
361
        .iter()
362
        .cloned()
363
        .map(|c| {
2✔
364
            if let ProofTree::UnprovenTree(ut) = c {
4✔
365
                Ok(ut)
2✔
366
            } else {
NEW
367
                Err(ProverError::Unexpected(
×
368
                    "make_cor_children_simulated: expected UnprovenTree got UncheckedTree",
369
                ))
370
            }
371
        })
372
        .collect()
373
}
374

375
/// Prover Step 3: This step will change some "real" nodes to "simulated" to make sure each node has
376
/// the right number of simulated children.
377
/// In a top-down traversal of the tree, do the following for each node:
378
fn polish_simulated<P: Prover + ?Sized>(
2✔
379
    _prover: &P,
380
    unproven_tree: UnprovenTree,
381
) -> Result<UnprovenTree, ProverError> {
382
    proof_tree::rewrite_td(unproven_tree.into(), &|tree| match tree {
6✔
383
        ProofTree::UnprovenTree(ut) => match ut {
2✔
384
            UnprovenTree::UnprovenLeaf(_) => Ok(None),
2✔
385
            UnprovenTree::UnprovenConjecture(conj) => match conj {
2✔
386
                UnprovenConjecture::CandUnproven(cand) => {
2✔
387
                    // If the node is marked "simulated", mark all of its children "simulated"
388
                    let a: CandUnproven = if cand.simulated {
2✔
389
                        cand.clone().with_children(
2✔
390
                            cast_to_unp(&cand.children)?
3✔
NEW
391
                                .iter()
×
NEW
392
                                .cloned()
×
393
                                .map(|c| c.with_simulated(true).into())
2✔
NEW
394
                                .collect(),
×
395
                        )
396
                    } else {
397
                        cand.clone()
2✔
398
                    };
399
                    Ok(Some(set_positions(a.into())?.into()))
2✔
400
                }
401
                UnprovenConjecture::CorUnproven(cor) => {
2✔
402
                    // If the node is marked "simulated", mark all of its children "simulated"
403
                    let o: CorUnproven = if cor.simulated {
2✔
404
                        CorUnproven {
1✔
405
                            children: cast_to_unp(&cor.children)?
1✔
NEW
406
                                .into_iter()
×
407
                                .map(|c| c.with_simulated(true).into())
2✔
NEW
408
                                .collect(),
×
409
                            ..cor.clone()
1✔
410
                        }
411
                    } else {
412
                        // If the node is OR marked "real",  mark all but one of its children "simulated"
413
                        make_cor_children_simulated(cor.clone())?
2✔
414
                    };
415
                    Ok(Some(set_positions(o.into())?.into()))
2✔
416
                }
417
                UnprovenConjecture::CthresholdUnproven(ct) => {
2✔
418
                    // If the node is marked "simulated", mark all of its children "simulated"
419
                    let t: CthresholdUnproven = if ct.simulated {
2✔
420
                        ct.clone().with_children(
×
NEW
421
                            cast_to_unp(ct.children.as_slice())?
×
NEW
422
                                .iter()
×
NEW
423
                                .cloned()
×
NEW
424
                                .map(|c| c.with_simulated(true).into())
×
NEW
425
                                .collect::<Vec<_>>()
×
NEW
426
                                .try_into()?,
×
427
                        )
428
                    } else {
429
                        // If the node is THRESHOLD(k) marked "real", mark all but k of its children "simulated"
430
                        // (the node is guaranteed, by the previous step, to have at least k "real" children).
431
                        // Which particular ones are left "real" is not important for security;
432
                        // the choice can be guided by efficiency or convenience considerations.
433
                        //
434
                        // We'll mark the first k real ones real
435
                        let mut count_of_real = 0;
2✔
436
                        let mut children_indices_to_be_marked_simulated = Vec::new();
2✔
437
                        let unproven_children = cast_to_unp(ct.children.as_slice())?;
4✔
438
                        for (idx, kid) in unproven_children.iter().enumerate() {
4✔
439
                            if kid.is_real() {
4✔
440
                                count_of_real += 1;
2✔
441
                                if count_of_real > ct.k {
2✔
442
                                    children_indices_to_be_marked_simulated.push(idx);
1✔
443
                                };
444
                            };
445
                        }
446
                        ct.clone().with_children(
4✔
447
                            unproven_children
4✔
NEW
448
                                .iter()
×
NEW
449
                                .cloned()
×
NEW
450
                                .enumerate()
×
451
                                .map(|(idx, c)| {
4✔
452
                                    if children_indices_to_be_marked_simulated.contains(&idx) {
8✔
453
                                        c.with_simulated(true)
2✔
454
                                    } else {
455
                                        c
2✔
456
                                    }
NEW
457
                                    .into()
×
458
                                })
NEW
459
                                .collect::<Vec<_>>()
×
NEW
460
                                .try_into()?,
×
461
                        )
462
                    };
463
                    Ok(Some(set_positions(t.into())?.into()))
2✔
464
                }
465
            },
466
        },
467
        ProofTree::UncheckedTree(_) => Ok(None),
×
468
    })?
469
    .try_into()
470
    .map_err(|_| {
×
471
        ProverError::Unexpected("polish_simulated: failed to convert ProofTree to UnprovenTree")
×
472
    })
473
}
474

475
fn step4_real_conj(
2✔
476
    uc: UnprovenConjecture,
477
    hints_bag: &HintsBag,
478
) -> Result<Option<ProofTree>, ProverError> {
479
    assert!(uc.is_real());
4✔
480
    match uc {
2✔
481
        // A real AND node has no simulated children
482
        UnprovenConjecture::CandUnproven(_) => Ok(None),
2✔
483
        //real OR Threshold case
484
        UnprovenConjecture::CorUnproven(_) | UnprovenConjecture::CthresholdUnproven(_) => {
485
            let new_children = cast_to_unp(uc.children())?
6✔
486
                .iter()
487
                .cloned()
488
                .map(|c| -> Result<_, ProverError> {
2✔
489
                    if c.is_real() {
6✔
490
                        Ok(c)
2✔
491
                    } else {
492
                        // take challenge from previously done proof stored in the hints bag,
493
                        // or generate random challenge for simulated child
494
                        let new_challenge: Challenge = if let Some(new_challenge) = hints_bag
9✔
495
                            .proofs()
496
                            .into_iter()
497
                            .find(|p| p.position() == c.position())
2✔
498
                            .map(|p| p.challenge().clone())
4✔
499
                        {
500
                            new_challenge
1✔
501
                        } else {
502
                            #[cfg(feature = "std")]
503
                            {
504
                                Challenge::secure_random()
4✔
505
                            }
506
                            #[cfg(not(feature = "std"))]
507
                            {
508
                                return Err(ProverError::Unsupported);
509
                            }
510
                        };
511
                        Ok(c.with_challenge(new_challenge))
2✔
512
                    }
513
                })
514
                .map(|c| c.map(ProofTree::from))
4✔
515
                .collect::<Result<Vec<_>, _>>()?;
516
            Ok(Some(
2✔
517
                uc.with_children(new_children)?.into(), // CorUnproven {
2✔
518
                                                        //     children: new_children,
519
                                                        //     ..cor.clone()
520
                                                        // }
521
                                                        // .into(),
522
            ))
523
        }
524
    }
525
}
526

527
fn step4_simulated_and_conj(cand: CandUnproven) -> Result<Option<ProofTree>, ProverError> {
1✔
528
    assert!(cand.simulated);
1✔
529
    // If the node is AND, then all of its children get e_0 as the challenge
530
    if let Some(challenge) = cand.challenge_opt.clone() {
2✔
531
        let new_children = cand
2✔
532
            .children
533
            .iter()
534
            .cloned()
535
            .map(|it| it.with_challenge(challenge.clone()))
2✔
536
            .collect();
537
        Ok(Some(
1✔
538
            CandUnproven {
1✔
539
                children: new_children,
540
                ..cand
541
            }
542
            .into(),
543
        ))
544
    } else {
545
        Err(ProverError::Unexpected(
×
546
            "step4_simulated_and_conj: missing CandUnproven(simulated).challenge",
547
        ))
548
    }
549
}
550

551
#[cfg(feature = "std")]
552
fn step4_simulated_or_conj(cor: CorUnproven) -> Result<Option<ProofTree>, ProverError> {
1✔
553
    // If the node is OR, then each of its children except one gets a fresh uniformly random
554
    // challenge in {0,1}^t. The remaining child gets a challenge computed as an XOR of the challenges of all
555
    // the other children and e_0.
556
    assert!(cor.simulated);
1✔
557
    if let Some(challenge) = cor.challenge_opt.clone() {
2✔
558
        let unproven_children = cast_to_unp(&cor.children)?;
2✔
559
        let mut tail: Vec<UnprovenTree> = unproven_children
2✔
560
            .clone()
561
            .into_iter()
562
            .skip(1)
563
            .map(|it| it.with_challenge(Challenge::secure_random()))
2✔
564
            .collect();
565
        let mut xored_challenge = challenge;
1✔
566
        for it in &tail {
3✔
567
            xored_challenge = xored_challenge.xor(it.challenge().ok_or({
3✔
568
                ProverError::Unexpected("step4_simulated_or_conj: no challenge in UnprovenTree")
1✔
569
            })?);
570
        }
571
        let head = unproven_children
2✔
572
            .first()
NEW
573
            .ok_or_else(|| ProverError::Unexpected("Or.unproven_children is empty"))?
×
574
            .clone()
575
            .with_challenge(xored_challenge);
2✔
576
        let mut new_children = vec![head];
2✔
577
        new_children.append(&mut tail);
1✔
578
        #[allow(clippy::unwrap_used)] // since quantity is preserved unwrap is safe here
579
        Ok(Some(
1✔
580
            CorUnproven {
1✔
581
                children: new_children
1✔
582
                    .into_iter()
583
                    .map(|c| c.into())
2✔
584
                    .collect::<Vec<ProofTree>>(),
585
                ..cor
586
            }
587
            .into(),
588
        ))
589
    } else {
590
        Err(ProverError::Unexpected(
×
591
            "step4_simulated_or_conj: missing CandUnproven(simulated).challenge",
592
        ))
593
    }
594
}
595
#[cfg(not(feature = "std"))]
596
fn step4_simulated_or_conj(_cor: CorUnproven) -> Result<Option<ProofTree>, ProverError> {
597
    Err(ProverError::Unsupported)
598
}
599

600
#[cfg(feature = "std")]
601
fn step4_simulated_threshold_conj(
×
602
    ct: CthresholdUnproven,
603
) -> Result<Option<ProofTree>, ProverError> {
604
    use crate::sigma_protocol::gf2_192::gf2_192poly_from_byte_array;
605
    // The faster algorithm is as follows. Pick n-k fresh uniformly random values
606
    // q_1, ..., q_{n-k} from {0,1}^t and let q_0=e_0.
607
    // Viewing 1, 2, ..., n and q_0, ..., q_{n-k} as elements of GF(2^t),
608
    // evaluate the polynomial Q(x) = sum {q_i x^i} over GF(2^t) at points 1, 2, ..., n
609
    // to get challenges for child 1, 2, ..., n, respectively.
610
    assert!(ct.simulated);
×
611
    if let Some(challenge) = ct.challenge_opt.clone() {
×
NEW
612
        let unproven_children = cast_to_unp(ct.children.as_slice())?;
×
613
        let n = ct.children.len();
×
614
        let q = gf2_192poly_from_byte_array(
615
            challenge,
×
616
            super::crypto_utils::secure_random_bytes(super::SOUNDNESS_BYTES * (n - ct.k as usize)),
×
617
        )?;
NEW
618
        let new_children: Vec<_> = unproven_children
×
619
            .into_iter()
620
            .enumerate()
NEW
621
            .map(|(idx, c)| {
×
622
                // Note the cast to `u8` is safe since `unproven_children` is of type
623
                // `SigmaConjectureItems<_>` which is a `BoundedVec<_, 2, 255>`.
624
                let one_based_idx = (idx + 1) as u8;
×
625
                let new_challenge = q.evaluate(one_based_idx).into();
×
626
                c.with_challenge(new_challenge)
×
627
            })
NEW
628
            .map(|c| c.into())
×
629
            .collect();
630
        Ok(Some(
×
NEW
631
            ct.with_children(new_children.try_into()?)
×
NEW
632
                .with_polynomial(q)?
×
633
                .into(),
634
        ))
635
    } else {
636
        Err(ProverError::Unexpected(
×
637
            "step4_simulated_threshold_conj: missing CthresholdUnproven(simulated).challenge",
638
        ))
639
    }
640
}
641

642
#[cfg(not(feature = "std"))]
643
fn step4_simulated_threshold_conj(
644
    _ct: CthresholdUnproven,
645
) -> Result<Option<ProofTree>, ProverError> {
646
    Err(ProverError::Unsupported)
647
}
648

649
fn step5_schnorr(
2✔
650
    us: UnprovenSchnorr,
651
    hints_bag: &HintsBag,
652
) -> Result<Option<ProofTree>, ProverError> {
653
    // Steps 5 & 6: first try pulling out commitment from the hints bag. If it exists proceed with it,
654
    // otherwise, compute the commitment (if the node is real) or simulate it (if the node is simulated)
655

656
    // Step 6 (real leaf -- compute the commitment a or take it from the hints bag)
657
    let res: ProofTree = match hints_bag
8✔
658
        .commitments()
659
        .into_iter()
660
        .find(|c| c.position() == &us.position)
4✔
661
    {
662
        Some(cmt_hint) => {
1✔
663
            let pt: ProofTree = UnprovenSchnorr {
664
                commitment_opt: Some(
1✔
665
                    cmt_hint
666
                        .commitment()
667
                        .clone()
668
                        .try_into()
669
                        .map_err(|_| ProverError::Unexpected("step5_schnorr: failed to convert FirstProverMessage to FirstDlogProverMessage"))?,
670
                ),
671
                ..us.clone()
672
            }
673
            .into();
674
            pt
1✔
675
        }
676
        #[cfg(feature = "std")]
677
        None => {
678
            if us.simulated {
8✔
679
                // Step 5 (simulated leaf -- complete the simulation)
680
                if let Some(challenge) = us.challenge_opt.clone() {
4✔
681
                    let (fm, sm) =
2✔
682
                        dlog_protocol::interactive_prover::simulate(&us.proposition, &challenge);
683
                    Ok(ProofTree::UncheckedTree(
2✔
684
                        UncheckedSchnorr {
2✔
685
                            proposition: us.proposition.clone(),
2✔
686
                            commitment_opt: Some(fm),
2✔
687
                            challenge,
2✔
688
                            second_message: sm,
689
                        }
690
                        .into(),
691
                    ))
692
                } else {
693
                    Err(ProverError::SimulatedLeafWithoutChallenge)
×
694
                }
695
            } else {
696
                // Step 6 (real leaf -- compute the commitment a)
697
                let (r, commitment) = dlog_protocol::interactive_prover::first_message();
4✔
698
                Ok(ProofTree::UnprovenTree(
2✔
699
                    UnprovenSchnorr {
2✔
700
                        commitment_opt: Some(commitment),
2✔
701
                        randomness_opt: Some(r),
2✔
702
                        ..us.clone()
2✔
703
                    }
704
                    .into(),
705
                ))
706
            }?
707
        }
708
        #[cfg(not(feature = "std"))]
709
        None => return Err(ProverError::Unsupported),
710
    };
711
    Ok(Some(res))
2✔
712
}
713

714
fn step5_diffie_hellman_tuple(
2✔
715
    dhu: UnprovenDhTuple,
716
    hints_bag: &HintsBag,
717
) -> Result<Option<ProofTree>, ProverError> {
718
    //Steps 5 & 6: pull out commitment from the hints bag, otherwise, compute the commitment(if the node is real),
719
    // or simulate it (if the node is simulated)
720

721
    // Step 6 (real leaf -- compute the commitment a or take it from the hints bag)
722
    let res: Result<ProofTree, _> = hints_bag
8✔
723
        .commitments()
724
        .iter()
725
        .find(|c| c.position() == &dhu.position)
4✔
726
        .map(|cmt_hint| {
1✔
727
            Ok(dhu
3✔
728
                .clone()
729
                .with_commitment(match cmt_hint.commitment() {
2✔
730
                    FirstDlogProverMessage(_) => {
731
                        return Err(ProverError::Unexpected(
×
732
                            "Step 5 & 6 for UnprovenDhTuple: FirstDlogProverMessage is not expected here",
733
                        ));
734
                    }
735
                    FirstDhtProverMessage(dhtm) => dhtm.clone(),
1✔
736
                })
737
                .into())
738
        })
739
        .unwrap_or_else(|| {
2✔
740
            if dhu.simulated {
4✔
741
                // Step 5 (simulated leaf -- complete the simulation)
742
                #[cfg(feature = "std")]
743
                if let Some(dhu_challenge) = dhu.challenge_opt.clone() {
4✔
744
                    let (fm, sm) = dht_protocol::interactive_prover::simulate(
4✔
745
                        &dhu.proposition,
2✔
746
                        &dhu_challenge,
747
                    );
748
                    Ok(UncheckedDhTuple {
2✔
749
                        proposition: dhu.proposition.clone(),
2✔
750
                        commitment_opt: Some(fm),
2✔
751
                        challenge: dhu_challenge,
2✔
752
                        second_message: sm,
753
                    }
754
                        .into())
755
                } else {
756
                    Err(ProverError::SimulatedLeafWithoutChallenge)
×
757
                }
758
                #[cfg(not(feature = "std"))]
759
                return Err(ProverError::Unsupported);
760

761
            } else {
762
                #[cfg(feature = "std")]
763
                {
764
                // Step 6 -- compute the commitment
765
                let (r, fm) =
1✔
766
                    dht_protocol::interactive_prover::first_message(&dhu.proposition);
1✔
767
                Ok(UnprovenDhTuple {
2✔
768
                    commitment_opt: Some(fm),
1✔
769
                    randomness_opt: Some(r),
1✔
770
                    ..dhu.clone()
1✔
771
                }
772
                    .into())
773
                }
774
                #[cfg(not(feature = "std"))]
775
                {
776
                    Err(ProverError::Unsupported)
777
                }
778
            }
779
        });
780
    Ok(Some(res?))
2✔
781
}
782

783
/**
784
Prover Step 4: In a top-down traversal of the tree, compute the challenges e for simulated children of every node
785
Prover Step 5: For every leaf marked "simulated", use the simulator of the Sigma-protocol for that leaf
786
to compute the commitment $a$ and the response z, given the challenge e that is already stored in the leaf.
787
Prover Step 6: For every leaf marked "real", use the first prover step of the Sigma-protocol for that leaf to
788
compute the commitment a.
789
 */
790
fn simulate_and_commit(
2✔
791
    unproven_tree: UnprovenTree,
792
    hints_bag: &HintsBag,
793
) -> Result<UnprovenTree, ProverError> {
794
    proof_tree::rewrite_td(unproven_tree.into(), &|tree| {
4✔
795
        match tree {
4✔
796
            // Step 4 part 1: If the node is marked "real", jhen each of its simulated children gets a fresh uniformly
797
            // random challenge in {0,1}^t.
798
            ProofTree::UnprovenTree(UnprovenTree::UnprovenConjecture(uc)) => {
2✔
799
                if uc.is_real() {
2✔
800
                    step4_real_conj(uc.clone(), hints_bag)
2✔
801
                } else {
802
                    match uc {
2✔
803
                        // Step 4 part 2: If the node is marked "simulated", let e_0 be the challenge computed for it.
804
                        // All of its children are simulated, and thus we compute challenges for all
805
                        // of them, as follows:
806
                        UnprovenConjecture::CandUnproven(cand) => {
1✔
807
                            step4_simulated_and_conj(cand.clone())
1✔
808
                        }
809
                        UnprovenConjecture::CorUnproven(cor) => {
1✔
810
                            step4_simulated_or_conj(cor.clone())
1✔
811
                        }
812
                        UnprovenConjecture::CthresholdUnproven(ct) => {
×
813
                            step4_simulated_threshold_conj(ct.clone())
×
814
                        }
815
                    }
816
                }
817
            }
818

819
            ProofTree::UnprovenTree(UnprovenTree::UnprovenLeaf(UnprovenLeaf::UnprovenSchnorr(
820
                us,
2✔
821
            ))) => step5_schnorr(us.clone(), hints_bag),
2✔
822

823
            ProofTree::UnprovenTree(UnprovenTree::UnprovenLeaf(UnprovenLeaf::UnprovenDhTuple(
824
                dhu,
2✔
825
            ))) => step5_diffie_hellman_tuple(dhu.clone(), hints_bag),
2✔
826
            ProofTree::UncheckedTree(_) => Ok(None),
×
827
        }
828
    })?
829
    .try_into()
830
    .map_err(|_| {
×
831
        ProverError::Unexpected("simulate_and_commit: failed to convert ProofTree to UnprovenTree")
×
832
    })
833
}
834

835
fn step9_real_and(cand: CandUnproven) -> Result<Option<ProofTree>, ProverError> {
2✔
836
    assert!(cand.is_real());
4✔
837
    // If the node is AND, let each of its children have the challenge e_0
838
    if let Some(challenge) = cand.challenge_opt.clone() {
2✔
839
        let updated = cand
4✔
840
            .children
841
            .iter()
842
            .map(|child| child.with_challenge(challenge.clone()))
4✔
843
            .collect();
844
        Ok(Some(cand.with_children(updated).into()))
2✔
845
    } else {
846
        Err(ProverError::Unexpected(
×
847
            "step9_real_and: CandUnproven.challenge_opt is empty",
848
        ))
849
    }
850
}
851

852
fn step9_real_or(cor: CorUnproven) -> Result<Option<ProofTree>, ProverError> {
2✔
853
    assert!(cor.is_real());
4✔
854
    // If the node is OR, it has only one child marked "real".
855
    // Let this child have the challenge equal to the XOR of the challenges of all
856
    // the other children and e_0
857
    if let Some(root_challenge) = &cor.challenge_opt {
2✔
858
        let challenge: Challenge = cor
8✔
859
            .children
860
            .clone()
861
            .iter()
862
            .flat_map(|c| c.challenge())
6✔
863
            .fold(root_challenge.clone(), |acc, c| acc.xor(c));
8✔
864
        let children = cor
2✔
865
            .children
866
            .iter()
867
            .cloned()
868
            .map(|c| match c {
4✔
869
                ProofTree::UnprovenTree(ref ut) if ut.is_real() => {
4✔
870
                    c.with_challenge(challenge.clone())
2✔
871
                }
872
                _ => c,
2✔
873
            })
874
            .collect();
875
        Ok(Some(
2✔
876
            CorUnproven {
2✔
877
                children,
2✔
878
                ..cor.clone()
2✔
879
            }
880
            .into(),
881
        ))
882
    } else {
883
        Err(ProverError::Unexpected(
×
884
            "step9_real_or: CorUnproven.challenge_opt is empty",
885
        ))
886
    }
887
}
888

889
fn step9_real_threshold(ct: CthresholdUnproven) -> Result<Option<ProofTree>, ProverError> {
2✔
890
    assert!(ct.is_real());
4✔
891
    // If the node is THRESHOLD(k), number its children from 1 to no. Let i_1,..., i_{n-k}
892
    // be the indices of thechildren marked `"simulated" and e_1, ...,  e_{n-k} be
893
    // their corresponding challenges.
894
    // Let i_0 = 0. Viewing 0, 1, 2, ..., n and e_0, ..., e_{n-k} as elements of GF(2^t),
895
    // find (via polynomial interpolation) the lowest-degree polynomial
896
    // Q(x)=sum_{i=0}^{n-k} a_i x^i  over GF(2^t) that is equal to e_j at i_j
897
    // for each f from 0 to n-k
898
    // (this polynomial will have n-k+1 coefficients, and the lowest coefficient
899
    // will be e_0). For child number i of the node, if the child is marked "real",
900
    // compute its challenge as Q(i) (if the child is marked
901
    // "simulated", its challenge is already Q(i), by construction of Q).
902
    if let Some(challenge) = ct.challenge_opt.clone() {
2✔
903
        let mut points = Vec::new();
2✔
904
        let mut values = Vec::new();
2✔
905
        for (idx, child) in ct.children.clone().enumerated() {
8✔
906
            let one_based_idx = (idx + 1) as u8;
4✔
907
            let challenge_opt = match child {
2✔
908
                ProofTree::UncheckedTree(ut) => match ut {
2✔
909
                    UncheckedTree::UncheckedLeaf(ul) => Some(ul.challenge()),
2✔
910
                    UncheckedTree::UncheckedConjecture(_) => return Err(ProverError::Unexpected(
×
911
                        "step9_real_threshold: CthresholdUnproven.children has unexpected UncheckedConjecture",
912
                    )),
913
                },
914
                ProofTree::UnprovenTree(unpt) => unpt.challenge(),
2✔
915
            };
916
            if let Some(challenge) = challenge_opt {
6✔
917
                points.push(one_based_idx);
2✔
918
                values.push(challenge.into());
2✔
919
            };
920
        }
921

922
        let value_at_zero = challenge.into();
2✔
923
        let q = Gf2_192Poly::interpolate(&points, &values, value_at_zero)?;
2✔
924

925
        let new_children = ct.children.clone().enumerated().mapped(|(idx, child)| {
8✔
926
            // Note the cast to `u8` is safe since `ct.children` is of type
927
            // `SigmaConjectureItems<_>` which is a `BoundedVec<_, 2, 255>`.
928
            let one_based_idx = (idx + 1) as u8;
2✔
929
            match &child {
2✔
930
                ProofTree::UnprovenTree(ut) if ut.is_real() => {
4✔
931
                    child.with_challenge(q.evaluate(one_based_idx).into())
2✔
932
                }
933
                _ => child,
2✔
934
            }
935
        });
936
        Ok(Some(
3✔
937
            ct.with_children(new_children).with_polynomial(q)?.into(),
2✔
938
        ))
939
    } else {
940
        Err(ProverError::Unexpected(
×
941
            "step9_real_threshold: CthresholdUnproven.challenge_opt is empty",
942
        ))
943
    }
944
}
945

946
fn step9_real_schnorr<P: Prover + ?Sized>(
2✔
947
    us: UnprovenSchnorr,
948
    prover: &P,
949
    hints_bag: &HintsBag,
950
) -> Result<Option<ProofTree>, ProverError> {
951
    assert!(us.is_real());
6✔
952
    // If the node is a leaf marked "real", compute its response according to the second prover step
953
    // of the Sigma-protocol given the commitment, challenge, and witness, or pull response from the hints bag
954
    if let Some(challenge) = us.challenge_opt.clone() {
3✔
955
        let priv_key_opt = prover
9✔
956
            .secrets()
957
            .iter()
958
            .find(|s| s.public_image() == us.proposition.clone().into());
9✔
959
        let z = match priv_key_opt {
3✔
960
            Some(PrivateInput::DlogProverInput(priv_key)) => match hints_bag
9✔
961
                .own_commitments()
×
962
                .iter()
×
963
                .find(|c| c.position == us.position)
5✔
964
            {
965
                Some(oc) => dlog_protocol::interactive_prover::second_message(
966
                    priv_key,
×
967
                    oc.secret_randomness.clone(),
1✔
968
                    &challenge,
×
969
                ),
970
                None => dlog_protocol::interactive_prover::second_message(
971
                    priv_key,
×
972
                    us.randomness_opt.clone().ok_or({
6✔
973
                        ProverError::Unexpected(
3✔
974
                            "step9_real_schnorr: empty randomness in UnprovenSchnorr",
×
975
                        )
976
                    })?,
977
                    &challenge,
×
978
                ),
979
            },
980
            Some(PrivateInput::DhTupleProverInput(_)) => {
×
981
                return Err(ProverError::Unexpected(
×
982
                    "step9_real_schnorr: Expected DLOG prover input in prover secrets, got DhTupleProverInput",
×
983
                ));
984
            }
985
            None => match hints_bag
3✔
986
                .real_proofs()
×
987
                .into_iter()
×
988
                .find(|comm| comm.position == us.position)
3✔
989
            {
990
                Some(tree) => {
1✔
991
                    let unchecked_tree = tree.unchecked_tree;
1✔
992
                    if let UncheckedTree::UncheckedLeaf(UncheckedLeaf::UncheckedSchnorr(
2✔
993
                        unchecked_schnorr,
1✔
994
                    )) = unchecked_tree
×
995
                    {
996
                        unchecked_schnorr.second_message
1✔
997
                    } else {
998
                        return Err(ProverError::SecretNotFound);
×
999
                    }
1000
                }
1001
                None => {
×
1002
                    #[cfg(feature = "std")]
1003
                    {
1004
                        let bs =
×
1005
                            ergotree_ir::sigma_protocol::dlog_group::random_scalar_in_group_range(
1006
                                crate::sigma_protocol::crypto_utils::secure_rng(),
1✔
1007
                            )
1008
                            .into();
1009
                        dlog_protocol::SecondDlogProverMessage { z: bs }
1010
                    }
1011
                    #[cfg(not(feature = "std"))]
1012
                    {
1013
                        return Err(ProverError::Unsupported);
×
1014
                    }
1015
                }
1016
            },
1017
        };
1018
        Ok(Some(
3✔
1019
            UncheckedSchnorr {
2✔
1020
                proposition: us.proposition,
3✔
1021
                commitment_opt: None,
3✔
1022
                challenge,
3✔
1023
                second_message: z,
2✔
1024
            }
1025
            .into(),
×
1026
        ))
1027
    } else {
1028
        Err(ProverError::RealUnprovenTreeWithoutChallenge)
×
1029
    }
1030
}
1031

1032
fn step9_real_dh_tuple<P: Prover + ?Sized>(
2✔
1033
    dhu: UnprovenDhTuple,
1034
    prover: &P,
1035
    hints_bag: &HintsBag,
1036
) -> Result<Option<ProofTree>, ProverError> {
1037
    assert!(dhu.is_real());
4✔
1038
    // If the node is a leaf marked "real", compute its response according to the second prover step
1039
    // of the Sigma-protocol given the commitment, challenge, and witness, or pull response from
1040
    // the hints bag
1041
    if let Some(dhu_challenge) = dhu.challenge_opt.clone() {
2✔
1042
        let priv_key_opt = prover
6✔
1043
            .secrets()
1044
            .iter()
1045
            .find(|s| s.public_image() == dhu.proposition.clone().into());
6✔
1046
        let z = match priv_key_opt {
2✔
1047
            Some(PrivateInput::DhTupleProverInput(priv_key)) => match hints_bag
8✔
1048
                .own_commitments()
×
1049
                .iter()
×
1050
                .find(|c| c.position == dhu.position)
4✔
1051
            {
1052
                Some(commitment_from_hints_bag) => {
1✔
1053
                    dht_protocol::interactive_prover::second_message(
1054
                        priv_key,
×
1055
                        &commitment_from_hints_bag.secret_randomness,
1✔
1056
                        &dhu_challenge,
×
1057
                    )
1058
                }
1059
                None => dht_protocol::interactive_prover::second_message(
1060
                    priv_key,
×
1061
                    &dhu.randomness_opt.clone().ok_or({
2✔
1062
                        ProverError::Unexpected(
1✔
1063
                            "step9_real_dh_tuple: empty randomness in UnprovenDhTuple",
×
1064
                        )
1065
                    })?,
1066
                    &dhu_challenge,
×
1067
                ),
1068
            },
1069
            Some(PrivateInput::DlogProverInput(_)) => {
×
1070
                return Err(ProverError::Unexpected("step9_real_dh_tuple: Expected DhTupleProverInput  in prover secrets, got DlogProverInput"));
×
1071
            }
1072
            None => match hints_bag
3✔
1073
                .real_proofs()
×
1074
                .iter()
×
1075
                .find(|c| c.position == dhu.position)
1✔
1076
            {
1077
                Some(proof) => {
×
1078
                    let unchecked_tree = proof.clone().unchecked_tree;
×
1079
                    if let UncheckedTree::UncheckedLeaf(UncheckedLeaf::UncheckedDhTuple(
×
1080
                        unchecked_dht,
×
1081
                    )) = unchecked_tree
×
1082
                    {
1083
                        unchecked_dht.second_message
×
1084
                    } else {
1085
                        return Err(ProverError::Unexpected("step9_real_dh_tuple: Expected unchecked DH tuple in proof.unchecked_tree"));
×
1086
                    }
1087
                }
1088
                None => {
×
1089
                    #[cfg(feature = "std")]
1090
                    {
1091
                        let z =
×
1092
                            ergotree_ir::sigma_protocol::dlog_group::random_scalar_in_group_range(
1093
                                super::crypto_utils::secure_rng(),
1✔
1094
                            )
1095
                            .into();
1096
                        dht_protocol::SecondDhTupleProverMessage { z }
1097
                    }
1098
                    #[cfg(not(feature = "std"))]
1099
                    {
1100
                        return Err(ProverError::Unsupported);
×
1101
                    }
1102
                }
1103
            },
1104
        };
1105
        Ok(Some(
2✔
1106
            UncheckedDhTuple {
2✔
1107
                proposition: dhu.proposition.clone(),
2✔
1108
                commitment_opt: None,
2✔
1109
                challenge: dhu_challenge,
2✔
1110
                second_message: z,
2✔
1111
            }
1112
            .into(),
×
1113
        ))
1114
    } else {
1115
        Err(ProverError::RealUnprovenTreeWithoutChallenge)
×
1116
    }
1117
}
1118

1119
/**
1120
Prover Step 9: Perform a top-down traversal of only the portion of the tree marked "real" in order to compute
1121
the challenge e for every node marked "real" below the root and, additionally, the response z for every leaf
1122
marked "real"
1123
 */
1124
fn proving<P: Prover + ?Sized>(
2✔
1125
    prover: &P,
1126
    proof_tree: ProofTree,
1127
    hints_bag: &HintsBag,
1128
) -> Result<ProofTree, ProverError> {
1129
    proof_tree::rewrite_td(proof_tree, &|tree| {
4✔
1130
        match &tree {
2✔
1131
            ProofTree::UncheckedTree(unch) => match unch {
2✔
1132
                UncheckedTree::UncheckedLeaf(_) => Ok(None),
2✔
1133
                UncheckedTree::UncheckedConjecture(_) => Err(ProverError::Unexpected(
×
1134
                    "proving: unexpected UncheckedConjecture in proof_tree",
×
1135
                )),
1136
            },
1137

1138
            ProofTree::UnprovenTree(unproven_tree) => match unproven_tree {
2✔
1139
                UnprovenTree::UnprovenConjecture(conj) => {
2✔
1140
                    if conj.is_real() {
4✔
1141
                        match conj {
2✔
1142
                            UnprovenConjecture::CandUnproven(cand) => step9_real_and(cand.clone()),
2✔
1143
                            UnprovenConjecture::CorUnproven(cor) => step9_real_or(cor.clone()),
2✔
1144
                            UnprovenConjecture::CthresholdUnproven(ct) => {
2✔
1145
                                step9_real_threshold(ct.clone())
2✔
1146
                            }
1147
                        }
1148
                    } else {
1149
                        Ok(None)
2✔
1150
                    }
1151
                }
1152

1153
                UnprovenTree::UnprovenLeaf(unp_leaf) => {
2✔
1154
                    if unp_leaf.is_real() {
3✔
1155
                        match unp_leaf {
2✔
1156
                            UnprovenLeaf::UnprovenSchnorr(us) => {
2✔
1157
                                step9_real_schnorr(us.clone(), prover, hints_bag)
2✔
1158
                            }
1159
                            UnprovenLeaf::UnprovenDhTuple(dhu) => {
2✔
1160
                                step9_real_dh_tuple(dhu.clone(), prover, hints_bag)
2✔
1161
                            }
1162
                        }
1163
                    } else {
1164
                        // if the simulated node is proven by someone else, take it from hints bag
1165
                        let res: ProofTree = hints_bag
2✔
1166
                            .simulated_proofs()
×
1167
                            .into_iter()
×
1168
                            .find(|proof| proof.image == unp_leaf.proposition())
2✔
1169
                            .map(|proof| proof.unchecked_tree.into())
2✔
1170
                            .unwrap_or_else(|| unp_leaf.clone().into());
1✔
1171
                        Ok(Some(res))
1✔
1172
                    }
1173
                }
1174
            },
1175
        }
1176
    })
1177
}
1178

1179
fn convert_to_unproven(sb: SigmaBoolean) -> Result<UnprovenTree, ProverError> {
2✔
1180
    Ok(match sb {
4✔
1181
        SigmaBoolean::ProofOfKnowledge(pok) => match pok {
2✔
1182
            SigmaProofOfKnowledgeTree::ProveDhTuple(pdht) => UnprovenDhTuple {
4✔
1183
                proposition: pdht,
2✔
1184
                commitment_opt: None,
2✔
1185
                randomness_opt: None,
2✔
1186
                challenge_opt: None,
2✔
1187
                simulated: false,
1188
                position: NodePosition::crypto_tree_prefix(),
2✔
1189
            }
1190
            .into(),
1191
            SigmaProofOfKnowledgeTree::ProveDlog(prove_dlog) => UnprovenSchnorr {
4✔
1192
                proposition: prove_dlog,
2✔
1193
                commitment_opt: None,
2✔
1194
                randomness_opt: None,
2✔
1195
                challenge_opt: None,
2✔
1196
                simulated: false,
1197
                position: NodePosition::crypto_tree_prefix(),
2✔
1198
            }
1199
            .into(),
1200
        },
1201
        SigmaBoolean::SigmaConjecture(conj) => match conj {
2✔
1202
            SigmaConjecture::Cand(cand) => CandUnproven {
4✔
1203
                proposition: cand.clone(),
2✔
1204
                challenge_opt: None,
2✔
1205
                simulated: false,
1206
                children: cand
4✔
1207
                    .items
1208
                    .into_iter()
1209
                    .map(|it| convert_to_unproven(it).map(Into::into))
4✔
NEW
1210
                    .collect::<Result<Vec<_>, _>>()?,
×
1211
                position: NodePosition::crypto_tree_prefix(),
2✔
1212
            }
1213
            .into(),
2✔
1214
            SigmaConjecture::Cor(cor) => CorUnproven {
4✔
1215
                proposition: cor.clone(),
2✔
1216
                challenge_opt: None,
2✔
1217
                simulated: false,
1218
                children: cor
4✔
1219
                    .items
1220
                    .into_iter()
1221
                    .map(|it| convert_to_unproven(it).map(Into::into))
4✔
NEW
1222
                    .collect::<Result<Vec<_>, _>>()?,
×
1223
                position: NodePosition::crypto_tree_prefix(),
2✔
1224
            }
1225
            .into(),
2✔
1226
            SigmaConjecture::Cthreshold(ct) => CthresholdUnproven::new(
6✔
1227
                ct.clone(),
4✔
1228
                ct.k,
2✔
1229
                ct.children
4✔
1230
                    .try_mapped(|it| convert_to_unproven(it).map(Into::into))?,
4✔
1231
                None,
2✔
1232
                false,
1233
                NodePosition::crypto_tree_prefix(),
2✔
1234
            )
1235
            .into(),
2✔
1236
        },
1237
        SigmaBoolean::TrivialProp(_) => {
1238
            return Err(ProverError::Unexpected("TrivialProp is not expected here"));
×
1239
        }
1240
    })
1241
}
1242

1243
fn convert_to_unchecked(tree: ProofTree) -> Result<UncheckedTree, ProverError> {
2✔
1244
    match tree {
2✔
1245
        ProofTree::UncheckedTree(unch_tree) => match unch_tree {
2✔
1246
            UncheckedTree::UncheckedLeaf(_) => Ok(unch_tree.clone()),
4✔
1247
            UncheckedTree::UncheckedConjecture(_) => Err(ProverError::Unexpected(
×
1248
                "convert_to_unchecked: unexpected UncheckedConjecture",
1249
            )),
1250
        },
1251
        ProofTree::UnprovenTree(unp_tree) => match unp_tree {
2✔
1252
            UnprovenTree::UnprovenLeaf(_) => Err(ProverError::Unexpected(
×
1253
                "convert_to_unchecked: unexpected UnprovenLeaf",
1254
            )),
1255
            UnprovenTree::UnprovenConjecture(conj) => match conj {
2✔
1256
                UnprovenConjecture::CandUnproven(cand) => Ok(UncheckedConjecture::CandUnchecked {
4✔
1257
                    challenge: cand
4✔
1258
                        .challenge_opt
1259
                        .clone()
1260
                        .ok_or(ProverError::Unexpected("no challenge in CandUnproven"))?,
2✔
1261
                    children: cand
4✔
1262
                        .children
1263
                        .into_iter()
1264
                        .map(convert_to_unchecked)
NEW
1265
                        .collect::<Result<Vec<_>, _>>()?,
×
1266
                }
1267
                .into()),
4✔
1268
                UnprovenConjecture::CorUnproven(cor) => Ok(UncheckedConjecture::CorUnchecked {
4✔
1269
                    challenge: cor
4✔
1270
                        .challenge_opt
1271
                        .clone()
1272
                        .ok_or(ProverError::Unexpected("no challenge in CorUnproven"))?,
2✔
1273
                    children: cor
4✔
1274
                        .children
1275
                        .into_iter()
1276
                        .map(convert_to_unchecked)
NEW
1277
                        .collect::<Result<Vec<_>, _>>()?,
×
1278
                }
1279
                .into()),
2✔
1280
                UnprovenConjecture::CthresholdUnproven(ct) => {
2✔
1281
                    Ok(UncheckedConjecture::CthresholdUnchecked {
3✔
1282
                        challenge: ct.challenge_opt.clone().ok_or({
4✔
1283
                            ProverError::Unexpected("no challenge in CthresholdUnproven")
2✔
1284
                        })?,
1285
                        children: ct.children.clone().try_mapped(convert_to_unchecked)?,
4✔
1286
                        k: ct.k,
2✔
1287
                        polynomial: ct.polinomial_opt().ok_or({
5✔
1288
                            ProverError::Unexpected("no polynomial in CthresholdUnproven")
2✔
1289
                        })?,
1290
                    }
1291
                    .into())
1292
                }
1293
            },
1294
        },
1295
    }
1296
}
1297

1298
/// Test prover implementation
1299
pub struct TestProver {
1300
    /// secrets to be used in proofs generation
1301
    pub secrets: Vec<PrivateInput>,
1302
}
1303

1304
impl Prover for TestProver {
1305
    fn secrets(&self) -> &[PrivateInput] {
2✔
1306
        self.secrets.as_ref()
2✔
1307
    }
1308

1309
    fn append_secret(&mut self, input: PrivateInput) {
×
1310
        self.secrets.push(input)
×
1311
    }
1312
}
1313

1314
#[allow(clippy::unwrap_used)]
1315
#[cfg(test)]
1316
mod tests {
1317
    use super::*;
1318
    use crate::sigma_protocol::private_input::DhTupleProverInput;
1319
    use crate::sigma_protocol::private_input::DlogProverInput;
1320
    use core::convert::TryFrom;
1321
    use ergotree_ir::mir::atleast::Atleast;
1322
    use ergotree_ir::mir::collection::Collection;
1323
    use ergotree_ir::mir::constant::Constant;
1324
    use ergotree_ir::mir::constant::Literal;
1325
    use ergotree_ir::mir::expr::Expr;
1326
    use ergotree_ir::mir::sigma_and::SigmaAnd;
1327
    use ergotree_ir::mir::sigma_or::SigmaOr;
1328
    use ergotree_ir::sigma_protocol::sigma_boolean::SigmaProp;
1329
    use ergotree_ir::types::stype::SType;
1330
    use sigma_test_util::force_any_val;
1331

1332
    #[test]
1333
    fn test_prove_true_prop() {
1334
        let bool_true_tree = ErgoTree::try_from(Expr::Const(Constant {
1335
            tpe: SType::SBoolean,
1336
            v: Literal::Boolean(true),
1337
        }))
1338
        .unwrap();
1339
        let message = vec![0u8; 100];
1340

1341
        let prover = TestProver { secrets: vec![] };
1342
        let res = prover.prove(
1343
            &bool_true_tree,
1344
            &force_any_val::<Context>(),
1345
            message.as_slice(),
1346
            &HintsBag::empty(),
1347
        );
1348
        assert!(res.is_ok());
1349
        assert_eq!(res.unwrap().proof, ProofBytes::Empty);
1350
    }
1351

1352
    #[test]
1353
    fn test_prove_false_prop() {
1354
        let bool_false_tree = ErgoTree::try_from(Expr::Const(Constant {
1355
            tpe: SType::SBoolean,
1356
            v: Literal::Boolean(false),
1357
        }))
1358
        .unwrap();
1359
        let message = vec![0u8; 100];
1360

1361
        let prover = TestProver { secrets: vec![] };
1362
        let res = prover.prove(
1363
            &bool_false_tree,
1364
            &force_any_val::<Context>(),
1365
            message.as_slice(),
1366
            &HintsBag::empty(),
1367
        );
1368
        assert!(res.is_err());
1369
    }
1370

1371
    #[test]
1372
    fn test_prove_pk_prop() {
1373
        let secret = DlogProverInput::random();
1374
        let pk = secret.public_image();
1375
        let tree = ErgoTree::try_from(Expr::Const(pk.into())).unwrap();
1376
        let message = vec![0u8; 100];
1377

1378
        let prover = TestProver {
1379
            secrets: vec![PrivateInput::DlogProverInput(secret)],
1380
        };
1381
        let res = prover.prove(
1382
            &tree,
1383
            &force_any_val::<Context>(),
1384
            message.as_slice(),
1385
            &HintsBag::empty(),
1386
        );
1387
        assert!(res.is_ok());
1388
        assert_ne!(res.unwrap().proof, ProofBytes::Empty);
1389
    }
1390

1391
    #[test]
1392
    fn test_prove_pk_and_pk() {
1393
        let secret1 = DlogProverInput::random();
1394
        let secret2 = DlogProverInput::random();
1395
        let pk1 = secret1.public_image();
1396
        let pk2 = secret2.public_image();
1397
        let expr: Expr = SigmaAnd::new(vec![Expr::Const(pk1.into()), Expr::Const(pk2.into())])
1398
            .unwrap()
1399
            .into();
1400
        let tree: ErgoTree = expr.try_into().unwrap();
1401
        let message = vec![0u8; 100];
1402

1403
        let prover = TestProver {
1404
            secrets: vec![secret1.into(), secret2.into()],
1405
        };
1406
        let res = prover.prove(
1407
            &tree,
1408
            &force_any_val::<Context>(),
1409
            message.as_slice(),
1410
            &HintsBag::empty(),
1411
        );
1412
        assert_ne!(res.unwrap().proof, ProofBytes::Empty);
1413
    }
1414

1415
    #[test]
1416
    fn test_prove_pk_and_or() {
1417
        let secret1 = DlogProverInput::random();
1418
        let secret2 = DlogProverInput::random();
1419
        let secret3 = DlogProverInput::random();
1420
        let pk1 = secret1.public_image();
1421
        let pk2 = secret2.public_image();
1422
        let pk3 = secret3.public_image();
1423
        let expr: Expr = SigmaAnd::new(vec![
1424
            Expr::Const(pk1.into()),
1425
            SigmaOr::new(vec![Expr::Const(pk2.into()), Expr::Const(pk3.into())])
1426
                .unwrap()
1427
                .into(),
1428
        ])
1429
        .unwrap()
1430
        .into();
1431
        let tree: ErgoTree = expr.try_into().unwrap();
1432
        let message = vec![0u8; 100];
1433

1434
        let prover = TestProver {
1435
            secrets: vec![secret1.into(), secret2.into()],
1436
        };
1437
        let res = prover.prove(
1438
            &tree,
1439
            &force_any_val::<Context>(),
1440
            message.as_slice(),
1441
            &HintsBag::empty(),
1442
        );
1443
        assert_ne!(res.unwrap().proof, ProofBytes::Empty);
1444
    }
1445

1446
    #[test]
1447
    fn test_prove_pk_or_pk() {
1448
        let secret1 = DlogProverInput::random();
1449
        let secret2 = DlogProverInput::random();
1450
        let pk1 = secret1.public_image();
1451
        let pk2 = secret2.public_image();
1452
        let expr: Expr = SigmaOr::new(vec![Expr::Const(pk1.into()), Expr::Const(pk2.into())])
1453
            .unwrap()
1454
            .into();
1455
        let tree: ErgoTree = expr.try_into().unwrap();
1456
        let message = vec![0u8; 100];
1457

1458
        let prover = TestProver {
1459
            secrets: vec![secret1.into(), secret2.into()],
1460
        };
1461
        let res = prover.prove(
1462
            &tree,
1463
            &force_any_val::<Context>(),
1464
            message.as_slice(),
1465
            &HintsBag::empty(),
1466
        );
1467
        assert_ne!(res.unwrap().proof, ProofBytes::Empty);
1468
    }
1469

1470
    #[test]
1471
    fn test_prove_pk_or_and() {
1472
        let secret1 = DlogProverInput::random();
1473
        let secret2 = DlogProverInput::random();
1474
        let secret3 = DlogProverInput::random();
1475
        let pk1 = secret1.public_image();
1476
        let pk2 = secret2.public_image();
1477
        let pk3 = secret3.public_image();
1478
        let expr: Expr = SigmaOr::new(vec![
1479
            Expr::Const(pk1.into()),
1480
            SigmaAnd::new(vec![Expr::Const(pk2.into()), Expr::Const(pk3.into())])
1481
                .unwrap()
1482
                .into(),
1483
        ])
1484
        .unwrap()
1485
        .into();
1486
        let tree: ErgoTree = expr.try_into().unwrap();
1487
        let message = vec![0u8; 100];
1488

1489
        let prover = TestProver {
1490
            secrets: vec![secret2.into(), secret3.into()],
1491
        };
1492
        let res = prover.prove(
1493
            &tree,
1494
            &force_any_val::<Context>(),
1495
            message.as_slice(),
1496
            &HintsBag::empty(),
1497
        );
1498
        assert_ne!(res.unwrap().proof, ProofBytes::Empty);
1499
    }
1500

1501
    #[test]
1502
    fn test_prove_dht_prop() {
1503
        let secret = DhTupleProverInput::random();
1504
        let pi = secret.public_image();
1505
        let tree = ErgoTree::try_from(Expr::Const(pi.clone().into())).unwrap();
1506
        let message = vec![0u8; 100];
1507

1508
        let prover = TestProver {
1509
            secrets: vec![PrivateInput::DhTupleProverInput(secret)],
1510
        };
1511
        let res = prover.prove(
1512
            &tree,
1513
            &force_any_val::<Context>(),
1514
            message.as_slice(),
1515
            &HintsBag::empty(),
1516
        );
1517
        assert!(res.is_ok());
1518
        assert_ne!(res.unwrap().proof, ProofBytes::Empty);
1519
    }
1520

1521
    #[test]
1522
    fn test_prove_inner_threshold() {
1523
        // this test constructed from https://github.com/ergoplatform/sigma-rust/issues/579#issuecomment-1259058014
1524
        let secret1 = DlogProverInput::random();
1525
        let secret2 = DlogProverInput::random();
1526
        let secret3 = DlogProverInput::random();
1527
        let pk_alice = secret1.public_image();
1528
        let pk_bob = secret2.public_image();
1529
        let pk_carol = secret3.public_image();
1530

1531
        let at_least = Expr::Atleast(Atleast {
1532
            bound: Expr::Const(2.into()).into(),
1533
            input: Expr::Collection(Collection::Exprs {
1534
                elem_tpe: SType::SSigmaProp,
1535
                items: vec![
1536
                    SigmaProp::from(pk_alice).into(),
1537
                    SigmaProp::from(pk_bob).into(),
1538
                    SigmaProp::from(pk_carol).into(),
1539
                ],
1540
            })
1541
            .into(),
1542
        });
1543

1544
        // wrap in binary OR with a false on the other side
1545
        let tree: ErgoTree = Expr::SigmaOr(
1546
            SigmaOr::new(vec![
1547
                SigmaProp::new(SigmaBoolean::TrivialProp(false)).into(),
1548
                at_least,
1549
            ])
1550
            .unwrap(),
1551
        )
1552
        .try_into()
1553
        .unwrap();
1554

1555
        // Note that the prover only has the private for Alice. This is ensure that the AtLeast prop is unproved
1556
        let prover = TestProver {
1557
            secrets: vec![secret1.into()],
1558
        };
1559

1560
        let message = vec![0u8; 100];
1561
        let ctx: Context = force_any_val::<Context>();
1562
        let res = prover.prove(&tree, &ctx, message.as_slice(), &HintsBag::empty());
1563
        assert!(res.is_err());
1564
    }
1565
}
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