ergo_lib/wallet/
multi_sig.rs

1//! multi sig prove crate::chain::ergo_state_context::ErgoStateContext;
2use crate::chain::ergo_state_context::ErgoStateContext;
3use crate::chain::transaction::unsigned::UnsignedTransaction;
4use crate::chain::transaction::Transaction;
5use crate::ergotree_interpreter::eval::reduce_to_crypto;
6use crate::ergotree_interpreter::sigma_protocol::dht_protocol::interactive_prover as dht_interactive_prover;
7use crate::ergotree_interpreter::sigma_protocol::dlog_protocol::interactive_prover as dlog_interactive_prover;
8use crate::ergotree_interpreter::sigma_protocol::proof_tree::ProofTreeLeaf;
9use crate::ergotree_interpreter::sigma_protocol::prover::hint::{
10    CommitmentHint, Hint, HintsBag, OwnCommitment, RealCommitment, RealSecretProof, SecretProven,
11    SimulatedCommitment, SimulatedSecretProof,
12};
13use crate::ergotree_interpreter::sigma_protocol::prover::ProverError;
14use crate::ergotree_interpreter::sigma_protocol::sig_serializer::SigParsingError;
15use crate::ergotree_interpreter::sigma_protocol::unproven_tree::NodePosition;
16use crate::ergotree_interpreter::sigma_protocol::FirstProverMessage;
17use crate::ergotree_ir::sigma_protocol::sigma_boolean::SigmaBoolean;
18use crate::ergotree_ir::sigma_protocol::sigma_boolean::SigmaConjecture;
19use crate::ergotree_ir::sigma_protocol::sigma_boolean::SigmaConjectureItems;
20use crate::ergotree_ir::sigma_protocol::sigma_boolean::SigmaProofOfKnowledgeTree;
21use crate::wallet::signing::{make_context, TransactionContext, TxSigningError};
22use ergotree_interpreter::sigma_protocol::sig_serializer::parse_sig_compute_challenges;
23use ergotree_interpreter::sigma_protocol::unchecked_tree::UncheckedTree;
24use ergotree_interpreter::sigma_protocol::verifier::compute_commitments;
25use std::collections::HashMap;
26
27use super::signing::update_context;
28use super::tx_context::TransactionContextError;
29
30/// TransactionHintsBag
31#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
32#[cfg_attr(
33    feature = "json",
34    serde(
35        try_from = "crate::chain::json::hint::TransactionHintsBagJson",
36        into = "crate::chain::json::hint::TransactionHintsBagJson"
37    )
38)]
39#[cfg_attr(feature = "arbitrary", derive(proptest_derive::Arbitrary))]
40#[derive(PartialEq, Debug, Clone)]
41pub struct TransactionHintsBag {
42    #[cfg_attr(
43        feature = "arbitrary",
44        proptest(
45            strategy = "proptest::collection::hash_map(proptest::prelude::any::<usize>(), proptest::prelude::any::<HintsBag>(), 0..5)"
46        )
47    )]
48    pub(crate) secret_hints: HashMap<usize, HintsBag>,
49    #[cfg_attr(
50        feature = "arbitrary",
51        proptest(
52            strategy = "proptest::collection::hash_map(proptest::prelude::any::<usize>(), proptest::prelude::any::<HintsBag>(), 0..5)"
53        )
54    )]
55    pub(crate) public_hints: HashMap<usize, HintsBag>,
56}
57
58impl TransactionHintsBag {
59    /// Empty TransactionHintsBag
60    pub fn empty() -> Self {
61        TransactionHintsBag {
62            secret_hints: HashMap::new(),
63            public_hints: HashMap::new(),
64        }
65    }
66
67    /// Replacing Hints for an input index
68    pub fn replace_hints_for_input(&mut self, index: usize, hints_bag: HintsBag) {
69        let public: Vec<Hint> = hints_bag
70            .hints
71            .clone()
72            .into_iter()
73            .filter(|hint| matches!(hint, Hint::CommitmentHint(_)))
74            .collect();
75        let secret: Vec<Hint> = hints_bag
76            .hints
77            .into_iter()
78            .filter(|hint| matches!(hint, Hint::SecretProven(_)))
79            .collect();
80
81        self.secret_hints.insert(index, HintsBag { hints: secret });
82        self.public_hints.insert(index, HintsBag { hints: public });
83    }
84
85    /// Adding hints for a input index
86    pub fn add_hints_for_input(&mut self, index: usize, hints_bag: HintsBag) {
87        let mut public: Vec<Hint> = hints_bag
88            .hints
89            .clone()
90            .into_iter()
91            .filter(|hint| matches!(hint, Hint::CommitmentHint(_)))
92            .collect();
93        let mut secret: Vec<Hint> = hints_bag
94            .hints
95            .into_iter()
96            .filter(|hint| matches!(hint, Hint::SecretProven(_)))
97            .collect();
98        let secret_bag = HintsBag::empty();
99        let public_bag = HintsBag::empty();
100        let old_secret: &Vec<Hint> = &self.secret_hints.get(&index).unwrap_or(&secret_bag).hints;
101        for hint in old_secret {
102            secret.push(hint.clone());
103        }
104
105        let old_public: &Vec<Hint> = &self.public_hints.get(&index).unwrap_or(&public_bag).hints;
106        for hint in old_public {
107            public.push(hint.clone());
108        }
109        self.secret_hints.insert(index, HintsBag { hints: secret });
110        self.public_hints.insert(index, HintsBag { hints: public });
111    }
112
113    /// Outputting HintsBag corresponding for an index
114    pub fn all_hints_for_input(&self, index: usize) -> HintsBag {
115        let mut hints: Vec<Hint> = Vec::new();
116        let secret_bag = HintsBag::empty();
117        let public_bag = HintsBag::empty();
118        let secrets: &Vec<Hint> = &self.secret_hints.get(&index).unwrap_or(&secret_bag).hints;
119        for hint in secrets {
120            hints.push(hint.clone());
121        }
122        let public: &Vec<Hint> = &self.public_hints.get(&index).unwrap_or(&public_bag).hints;
123        for hint in public {
124            hints.push(hint.clone());
125        }
126        let hints_bag: HintsBag = HintsBag { hints };
127        hints_bag
128    }
129}
130
131/// A method which is extracting partial proofs of secret knowledge for particular secrets with their
132/// respective public images given. Useful for distributed signature applications.
133/// See DistributedSigSpecification for examples of usage.
134pub fn bag_for_multi_sig(
135    sigma_tree: &SigmaBoolean,
136    real_propositions: &[SigmaBoolean],
137    simulated_propositions: &[SigmaBoolean],
138    proof: &[u8],
139) -> Result<HintsBag, SigParsingError> {
140    if let SigmaBoolean::TrivialProp(_) = sigma_tree {
141        return Ok(HintsBag::empty());
142    }
143    let ut = compute_commitments(parse_sig_compute_challenges(sigma_tree, proof.to_owned())?);
144    // Traversing node of sigma tree
145    fn traverse_node(
146        tree: UncheckedTree,
147        real_propositions: &[SigmaBoolean],
148        simulated_propositions: &[SigmaBoolean],
149        position: NodePosition,
150        bag: &mut HintsBag,
151    ) -> Result<(), SigParsingError> {
152        match tree {
153            UncheckedTree::UncheckedConjecture(unchecked_conjecture) => {
154                let items: SigmaConjectureItems<UncheckedTree> =
155                    unchecked_conjecture.children_ust();
156                items
157                    .iter()
158                    .enumerate()
159                    .try_for_each(|(i, x)| -> Result<(), SigParsingError> {
160                        traverse_node(
161                            x.clone(),
162                            real_propositions,
163                            simulated_propositions,
164                            position.child(i),
165                            bag,
166                        )?;
167                        Ok(())
168                    })?;
169            }
170            UncheckedTree::UncheckedLeaf(leaf) => {
171                match leaf.commitment_opt() {
172                    Some(commitment) => {
173                        let real_found = real_propositions.contains(&leaf.proposition());
174                        let simulated_found = simulated_propositions.contains(&leaf.proposition());
175                        if real_found || simulated_found {
176                            if real_found {
177                                let real_commitment: Hint = Hint::CommitmentHint(
178                                    CommitmentHint::RealCommitment(RealCommitment {
179                                        image: leaf.proposition(),
180                                        commitment,
181                                        position: position.clone(),
182                                    }),
183                                );
184                                let real_secret_proof: Hint = Hint::SecretProven(
185                                    SecretProven::RealSecretProof(RealSecretProof {
186                                        image: leaf.proposition(),
187                                        challenge: leaf.challenge(),
188                                        unchecked_tree: UncheckedTree::UncheckedLeaf(leaf),
189                                        position,
190                                    }),
191                                );
192                                bag.add_hint(real_commitment);
193                                bag.add_hint(real_secret_proof);
194                            } else {
195                                let simulated_commitment: Hint = Hint::CommitmentHint(
196                                    CommitmentHint::SimulatedCommitment(SimulatedCommitment {
197                                        image: leaf.proposition(),
198                                        commitment,
199                                        position: position.clone(),
200                                    }),
201                                );
202                                let simulated_secret_proof: Hint = Hint::SecretProven(
203                                    SecretProven::SimulatedSecretProof(SimulatedSecretProof {
204                                        image: leaf.proposition(),
205                                        challenge: leaf.challenge(),
206                                        unchecked_tree: UncheckedTree::UncheckedLeaf(leaf),
207                                        position,
208                                    }),
209                                );
210                                bag.add_hint(simulated_commitment);
211                                bag.add_hint(simulated_secret_proof);
212                            }
213                        }
214                    }
215                    None => {
216                        return Err(SigParsingError::EmptyCommitment(leaf.proposition()));
217                    }
218                };
219            }
220        }
221        Ok(())
222    }
223
224    let mut bag: HintsBag = HintsBag::empty();
225
226    traverse_node(
227        ut,
228        real_propositions,
229        simulated_propositions,
230        NodePosition::crypto_tree_prefix(),
231        &mut bag,
232    )?;
233    Ok(bag)
234}
235
236/// A method which is generating commitments to randomness. A commitment is about a first step
237/// of a zero-knowledge proof-of-knowledge knowledge protocol.
238/// the commitments are generated for every input box of transaction
239/// and return as `TransactionHintsBag`.
240/// generated commitments corresponds to `public_keys` that prepared in function input
241pub fn generate_commitments(
242    tx_context: TransactionContext<UnsignedTransaction>,
243    state_context: &ErgoStateContext,
244    public_keys: &[SigmaBoolean],
245) -> Result<TransactionHintsBag, TxSigningError> {
246    let tx = tx_context.spending_tx.clone();
247    let mut hints_bag = TransactionHintsBag::empty();
248    let mut ctx = make_context(state_context, &tx_context, 0)?;
249    for (i, input) in tx.inputs.iter().enumerate() {
250        update_context(&mut ctx, &tx_context, i)?;
251        let input_box = tx_context
252            .get_input_box(&input.box_id)
253            .ok_or(TransactionContextError::InputBoxNotFound(i))?;
254        let tree = input_box.ergo_tree.clone();
255        let exp = tree
256            .proposition()
257            .map_err(ProverError::ErgoTreeError)
258            .map_err(|e| TxSigningError::ProverError(e, i))?;
259        let reduction_result = reduce_to_crypto(&exp, &ctx)
260            .map_err(ProverError::EvalError)
261            .map_err(|e| TxSigningError::ProverError(e, i))?;
262
263        let sigma_tree = reduction_result.sigma_prop;
264        hints_bag.add_hints_for_input(i, generate_commitments_for(&sigma_tree, public_keys));
265    }
266    Ok(hints_bag)
267}
268
269/// Extracting hints from a transaction and outputs it's corresponding TransactionHintsBag
270pub fn extract_hints(
271    tx_context: &TransactionContext<Transaction>,
272    state_context: &ErgoStateContext,
273    real_secrets_to_extract: Vec<SigmaBoolean>,
274    simulated_secrets_to_extract: Vec<SigmaBoolean>,
275) -> Result<TransactionHintsBag, TxSigningError> {
276    let mut hints_bag = TransactionHintsBag::empty();
277    let mut ctx = make_context(state_context, tx_context, 0)?;
278    for (i, input) in tx_context.spending_tx.inputs.iter().enumerate() {
279        update_context(&mut ctx, tx_context, i)?;
280        let input_box = tx_context
281            .get_input_box(&input.box_id)
282            .ok_or(TransactionContextError::InputBoxNotFound(i))?;
283        let tree = input_box.ergo_tree.clone();
284        let exp = tree
285            .proposition()
286            .map_err(ProverError::ErgoTreeError)
287            .map_err(|e| TxSigningError::ProverError(e, i))?;
288        let reduction_result = reduce_to_crypto(&exp, &ctx)
289            .map_err(ProverError::EvalError)
290            .map_err(|e| TxSigningError::ProverError(e, i))?;
291        let sigma_tree = reduction_result.sigma_prop;
292        let bag = bag_for_multi_sig(
293            &sigma_tree,
294            real_secrets_to_extract.as_slice(),
295            simulated_secrets_to_extract.as_slice(),
296            input.spending_proof.proof.as_ref(),
297        )?;
298        hints_bag.add_hints_for_input(i, bag);
299    }
300
301    Ok(hints_bag)
302}
303
304/// Generating commitment for ergo-tree that is reduced to crypto
305/// corresponds to an input box and with public key that prepared in input as
306/// `generate_for`
307pub fn generate_commitments_for(
308    sigma_tree: &SigmaBoolean,
309    generate_for: &[SigmaBoolean],
310) -> HintsBag {
311    fn traverse_node(
312        sb: &SigmaBoolean,
313        bag: &mut HintsBag,
314        position: NodePosition,
315        generate_for: &[SigmaBoolean],
316    ) {
317        let sb_clone = sb.clone();
318        match sb {
319            SigmaBoolean::SigmaConjecture(sc) => match sc {
320                SigmaConjecture::Cand(c_and) => {
321                    c_and.items.iter().enumerate().for_each(|(i, x)| {
322                        traverse_node(x, bag, position.child(i), generate_for);
323                    })
324                }
325                SigmaConjecture::Cor(cor) => cor.items.iter().enumerate().for_each(|(i, x)| {
326                    traverse_node(x, bag, position.child(i), generate_for);
327                }),
328                SigmaConjecture::Cthreshold(c_threshold) => {
329                    c_threshold.children.iter().enumerate().for_each(|(i, x)| {
330                        traverse_node(x, bag, position.child(i), generate_for);
331                    })
332                }
333            },
334            SigmaBoolean::ProofOfKnowledge(kt) => {
335                if generate_for.contains(&sb_clone) {
336                    let kt_clone = kt.clone();
337                    if let SigmaProofOfKnowledgeTree::ProveDlog(_pdl) = kt {
338                        let (r, a) = dlog_interactive_prover::first_message();
339                        let own_commitment: Hint =
340                            Hint::CommitmentHint(CommitmentHint::OwnCommitment(OwnCommitment {
341                                image: SigmaBoolean::ProofOfKnowledge(kt_clone.clone()),
342                                secret_randomness: r,
343                                commitment: FirstProverMessage::FirstDlogProverMessage(a.clone()),
344                                position: position.clone(),
345                            }));
346                        let real_commitment: Hint =
347                            Hint::CommitmentHint(CommitmentHint::RealCommitment(RealCommitment {
348                                image: SigmaBoolean::ProofOfKnowledge(kt_clone),
349                                commitment: FirstProverMessage::FirstDlogProverMessage(a),
350                                position,
351                            }));
352                        bag.add_hint(real_commitment);
353                        bag.add_hint(own_commitment);
354                    } else if let SigmaProofOfKnowledgeTree::ProveDhTuple(pdht) = kt {
355                        let (a, b) = dht_interactive_prover::first_message(pdht);
356                        let own_commitment: Hint =
357                            Hint::CommitmentHint(CommitmentHint::OwnCommitment(OwnCommitment {
358                                image: SigmaBoolean::ProofOfKnowledge(kt_clone.clone()),
359                                secret_randomness: a,
360                                commitment: FirstProverMessage::FirstDhtProverMessage(b.clone()),
361                                position: position.clone(),
362                            }));
363                        let real_commitment: Hint =
364                            Hint::CommitmentHint(CommitmentHint::RealCommitment(RealCommitment {
365                                image: SigmaBoolean::ProofOfKnowledge(kt_clone),
366                                commitment: FirstProverMessage::FirstDhtProverMessage(b),
367                                position,
368                            }));
369                        bag.add_hint(real_commitment);
370                        bag.add_hint(own_commitment);
371                    }
372                }
373            }
374            _ => (),
375        }
376    }
377    let mut bag = HintsBag::empty();
378    traverse_node(
379        sigma_tree,
380        &mut bag,
381        NodePosition::crypto_tree_prefix(),
382        generate_for,
383    );
384    bag
385}
386
387#[allow(clippy::unwrap_used)]
388#[cfg(test)]
389mod tests {
390    use super::*;
391    use crate::chain::transaction::Transaction;
392    use crate::ergotree_interpreter::eval::context::Context;
393    use crate::ergotree_interpreter::eval::reduce_to_crypto;
394    use crate::ergotree_interpreter::sigma_protocol::private_input::{
395        DlogProverInput, PrivateInput,
396    };
397    use crate::ergotree_interpreter::sigma_protocol::prover::{ProofBytes, Prover, TestProver};
398    use crate::ergotree_interpreter::sigma_protocol::verifier::{TestVerifier, Verifier};
399    use crate::ergotree_ir::chain::address::AddressEncoder;
400    use crate::ergotree_ir::chain::address::{Address, NetworkPrefix};
401    use crate::ergotree_ir::ergo_tree::ErgoTree;
402    use crate::ergotree_ir::mir::expr::Expr;
403    use crate::ergotree_ir::mir::sigma_and::SigmaAnd;
404    use crate::ergotree_ir::serialization::SigmaSerializable;
405    use crate::ergotree_ir::sigma_protocol::sigma_boolean::cand::Cand;
406    use ergo_chain_types::Base16DecodedBytes;
407    use ergotree_interpreter::sigma_protocol::private_input::DhTupleProverInput;
408    use ergotree_interpreter::sigma_protocol::wscalar::Wscalar;
409    use ergotree_ir::mir::atleast::Atleast;
410    use ergotree_ir::mir::constant::{Constant, Literal};
411    use ergotree_ir::mir::sigma_or::SigmaOr;
412    use ergotree_ir::mir::value::CollKind;
413    use ergotree_ir::sigma_protocol::sigma_boolean::SigmaProp;
414    use ergotree_ir::types::stype::SType;
415    use sigma_test_util::force_any_val;
416    use std::convert::{TryFrom, TryInto};
417
418    #[test]
419    fn extract_hint() {
420        let signed_tx = r#"{
421          "id": "6e32d1710816be34fd9710148b73f017bf8e71115cd2d3cf5758f80c2e3010ca",
422          "inputs": [
423            {
424              "boxId": "4c1155fca9bf7785f82eb43f74ef6a24164bac18f6cc35137e0ebf5a08abb8f7",
425              "spendingProof": {
426                "proofBytes": "77d85667cbb360c7ddad4d94c5e50930f3e72f6bdbd576bcce0098ab6547221d6943a49d4f6daaf06b37efc29884337701c16f4a0b9797db3061332b09849274beaa0609f146a468937338792bfe422425dd604b399df221",
427                "extension": {}
428              }
429            }
430          ],
431          "dataInputs": [],
432          "outputs": [
433            {
434              "boxId": "ad3e07a89bd0ec1161c1da54316ceb8efc6734ed08d3f005ade1184bf26d088d",
435              "value": 1000000,
436              "ergoTree": "0008cd039c8404d33f85dd4012e4f3d0719951eeea0015b13b940d67d4990e13de28b154",
437              "assets": [],
438              "additionalRegisters": {},
439              "creationHeight": 0,
440              "transactionId": "6e32d1710816be34fd9710148b73f017bf8e71115cd2d3cf5758f80c2e3010ca",
441              "index": 0
442            },
443            {
444              "boxId": "bcf15dbfd2b7d5e4688cb28d1393356bd1c96d6ef94c2942a2958545a51d2501",
445              "value": 2300000,
446              "ergoTree": "0008cd039c8404d33f85dd4012e4f3d0719951eeea0015b13b940d67d4990e13de28b154",
447              "assets": [],
448              "additionalRegisters": {},
449              "creationHeight": 0,
450              "transactionId": "6e32d1710816be34fd9710148b73f017bf8e71115cd2d3cf5758f80c2e3010ca",
451              "index": 1
452            },
453            {
454              "boxId": "6e473151a782e68cff7fd4f0127eeb43cf71e7a6d7fddf1b25ad4814fb451292",
455              "value": 1100000,
456              "ergoTree": "1005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304",
457              "assets": [],
458              "additionalRegisters": {},
459              "creationHeight": 0,
460              "transactionId": "6e32d1710816be34fd9710148b73f017bf8e71115cd2d3cf5758f80c2e3010ca",
461              "index": 2
462            }
463          ]
464        }"#;
465        let bytes_m = Base16DecodedBytes::try_from("100208cd03c847c306a2f9a8087b4ae63261cc5acea9034000ba8d033b0fb033247e8aade908cd02f4b05f44eb9703db7fcf9c94b89566787a7188c7e48964821d485d9ef2f9e4c4ea0273007301").unwrap();
466        let tree_m: ErgoTree = ErgoTree::sigma_parse_bytes(&bytes_m.0).unwrap();
467
468        let contx = force_any_val::<Context>();
469        let exp = tree_m.proposition().unwrap();
470        let reduction_result = reduce_to_crypto(&exp, &contx).unwrap();
471        let sigma_tree = reduction_result.sigma_prop;
472        let stx: Transaction = serde_json::from_str(signed_tx).unwrap();
473        let test: ProofBytes = stx.inputs.first().clone().spending_proof.proof;
474        let proof: Vec<u8> = Vec::from(test);
475        let mut real_proposition: Vec<SigmaBoolean> = Vec::new();
476        let mut simulated_proposition: Vec<SigmaBoolean> = Vec::new();
477        let mut bag: HintsBag = bag_for_multi_sig(
478            &sigma_tree,
479            real_proposition.as_slice(),
480            simulated_proposition.as_slice(),
481            proof.as_slice(),
482        )
483        .unwrap();
484        assert!(bag.hints.is_empty(), "{}", "{}");
485
486        let address_encoder = AddressEncoder::new(NetworkPrefix::Mainnet);
487        let first_address: Address = address_encoder
488            .parse_address_from_str("9hz1anoLGZGf88hjjrQ3y3oSpSE2Kkk15CcfFqCNYXDPWKV6F4i")
489            .unwrap();
490        let second_address = address_encoder
491            .parse_address_from_str("9gNpmNsivNnAKb7EtGVLGF24wRUJWnEqycCnWCeYxwSFZxkKyTZ")
492            .unwrap();
493
494        if let Address::P2Pk(pk) = first_address {
495            {
496                real_proposition.push(SigmaBoolean::ProofOfKnowledge(
497                    SigmaProofOfKnowledgeTree::ProveDlog(pk),
498                ));
499            }
500        }
501        if let Address::P2Pk(pk) = second_address {
502            {
503                simulated_proposition.push(SigmaBoolean::ProofOfKnowledge(
504                    SigmaProofOfKnowledgeTree::ProveDlog(pk),
505                ));
506            }
507        }
508        bag = bag_for_multi_sig(
509            &sigma_tree,
510            real_proposition.as_slice(),
511            simulated_proposition.as_slice(),
512            proof.as_slice(),
513        )
514        .unwrap();
515        assert!(!bag.hints.is_empty(), "{}", "{}");
516    }
517
518    #[test]
519    fn generating_commitment_two_signer() {
520        let secret1 = DlogProverInput::random();
521        let secret2 = DhTupleProverInput::random();
522        let pk1 = secret1.public_image();
523        let pk2 = secret2.public_image();
524        let mut generate_for: Vec<SigmaBoolean> = vec![SigmaBoolean::ProofOfKnowledge(
525            SigmaProofOfKnowledgeTree::ProveDhTuple(pk2.clone()),
526        )];
527
528        assert_eq!(
529            generate_commitments_for(
530                &SigmaBoolean::ProofOfKnowledge(SigmaProofOfKnowledgeTree::ProveDlog(pk1.clone())),
531                generate_for.as_slice(),
532            )
533            .hints
534            .len(),
535            0
536        );
537        generate_for.clear();
538        let cand = Cand::normalized(
539            vec![
540                pk1.clone().into(),
541                SigmaBoolean::ProofOfKnowledge(SigmaProofOfKnowledgeTree::ProveDhTuple(
542                    pk2.clone(),
543                )),
544            ]
545            .try_into()
546            .unwrap(),
547        );
548        generate_for.push(cand.clone());
549        assert!(
550            generate_commitments_for(
551                &SigmaBoolean::ProofOfKnowledge(SigmaProofOfKnowledgeTree::ProveDlog(pk1.clone())),
552                generate_for.as_slice(),
553            )
554            .hints
555            .is_empty(),
556            "{}",
557            "{}"
558        );
559        assert!(
560            generate_commitments_for(&cand, generate_for.as_slice())
561                .hints
562                .is_empty(),
563            "{}",
564            "{}"
565        );
566        generate_for.clear();
567        generate_for.push(SigmaBoolean::ProofOfKnowledge(
568            SigmaProofOfKnowledgeTree::ProveDlog(pk1.clone()),
569        ));
570
571        assert!(
572            !generate_commitments_for(
573                &SigmaBoolean::ProofOfKnowledge(SigmaProofOfKnowledgeTree::ProveDlog(pk1.clone())),
574                generate_for.as_slice(),
575            )
576            .hints
577            .is_empty(),
578            "{}",
579            "{}"
580        );
581        generate_for.clear();
582        generate_for.push(SigmaBoolean::ProofOfKnowledge(
583            SigmaProofOfKnowledgeTree::ProveDlog(pk1.clone()),
584        ));
585        let mut bag = generate_commitments_for(
586            &SigmaBoolean::ProofOfKnowledge(SigmaProofOfKnowledgeTree::ProveDlog(pk1)),
587            generate_for.as_slice(),
588        );
589        assert!(!bag.hints.is_empty(), "{}", "{}");
590        let mut hint = bag.hints[0].clone();
591        let mut a: Option<FirstProverMessage> = None;
592        let mut r: Option<Wscalar> = None;
593        if let Hint::CommitmentHint(CommitmentHint::RealCommitment(comm)) = hint {
594            assert_eq!(comm.position, NodePosition::crypto_tree_prefix());
595            a = Some(comm.commitment);
596        }
597        hint = bag.hints[1].clone();
598        if let Hint::CommitmentHint(CommitmentHint::OwnCommitment(comm)) = hint {
599            assert_eq!(comm.position, NodePosition::crypto_tree_prefix());
600            r = Some(comm.secret_randomness);
601        }
602        use ergo_chain_types::ec_point::{exponentiate, generator};
603        let g_to_r = exponentiate(&generator(), r.unwrap().as_scalar_ref());
604        assert_eq!(
605            FirstProverMessage::FirstDlogProverMessage(g_to_r.into()),
606            a.unwrap()
607        );
608
609        bag = generate_commitments_for(&cand, generate_for.as_slice());
610        assert_eq!(bag.hints.len(), 2);
611        hint = bag.hints[0].clone();
612        if let Hint::CommitmentHint(CommitmentHint::RealCommitment(comm)) = hint {
613            assert_eq!(
614                comm.position,
615                NodePosition {
616                    positions: vec![0, 0]
617                }
618            );
619        }
620        hint = bag.hints[1].clone();
621        if let Hint::CommitmentHint(CommitmentHint::OwnCommitment(comm)) = hint {
622            assert_eq!(
623                comm.position,
624                NodePosition {
625                    positions: vec![0, 0]
626                }
627            );
628        }
629        generate_for.clear();
630        generate_for.push(SigmaBoolean::ProofOfKnowledge(
631            SigmaProofOfKnowledgeTree::ProveDhTuple(pk2.clone()),
632        ));
633        bag = generate_commitments_for(
634            &SigmaBoolean::ProofOfKnowledge(SigmaProofOfKnowledgeTree::ProveDhTuple(pk2.clone())),
635            generate_for.as_slice(),
636        );
637        assert_ne!(bag.hints.len(), 0);
638        hint = bag.hints[0].clone();
639        if let Hint::CommitmentHint(CommitmentHint::RealCommitment(comm)) = hint {
640            assert_eq!(comm.position, NodePosition::crypto_tree_prefix(),);
641        }
642        hint = bag.hints[1].clone();
643        if let Hint::CommitmentHint(CommitmentHint::OwnCommitment(comm)) = hint {
644            assert_eq!(comm.position, NodePosition::crypto_tree_prefix(),);
645        }
646        hint = bag.hints[0].clone();
647        if let Hint::CommitmentHint(CommitmentHint::RealCommitment(real_commitment)) = hint {
648            if let Hint::CommitmentHint(CommitmentHint::OwnCommitment(own_commitment)) =
649                bag.hints[1].clone()
650            {
651                assert_eq!(real_commitment.commitment, own_commitment.commitment,);
652            }
653        }
654    }
655
656    #[test]
657    fn multi_sig_2() {
658        let ctx = force_any_val::<Context>();
659
660        let secret1 = DlogProverInput::random();
661        let secret2 = DlogProverInput::random();
662        let pk1 = secret1.public_image();
663        let pk2 = secret2.public_image();
664        let prover1 = TestProver {
665            secrets: vec![PrivateInput::DlogProverInput(secret1)],
666        };
667        let prover2 = TestProver {
668            secrets: vec![PrivateInput::DlogProverInput(secret2)],
669        };
670        let expr: Expr = SigmaAnd::new(vec![
671            Expr::Const(pk1.clone().into()),
672            Expr::Const(pk2.clone().into()),
673        ])
674        .unwrap()
675        .into();
676        let tree_and = ErgoTree::try_from(expr.clone()).unwrap();
677
678        let cand = reduce_to_crypto(&expr, &ctx).unwrap().sigma_prop;
679        let generate_for: Vec<SigmaBoolean> = vec![SigmaBoolean::ProofOfKnowledge(
680            SigmaProofOfKnowledgeTree::ProveDlog(pk2),
681        )];
682        let hints_from_bob: HintsBag = generate_commitments_for(&cand, &generate_for);
683        let bag1 = hints_from_bob.real_commitments();
684        let own = hints_from_bob.own_commitments();
685        let message = vec![0u8; 100];
686        let mut bag_a = HintsBag { hints: vec![] };
687        bag_a.add_hint(Hint::CommitmentHint(CommitmentHint::RealCommitment(
688            bag1.first().unwrap().clone(),
689        )));
690
691        let proof1 = prover1
692            .prove(&tree_and, &ctx, message.as_slice(), &bag_a)
693            .unwrap();
694        let proof: Vec<u8> = Vec::from(proof1.proof);
695        let real_proposition: Vec<SigmaBoolean> = vec![SigmaBoolean::ProofOfKnowledge(
696            SigmaProofOfKnowledgeTree::ProveDlog(pk1),
697        )];
698        let simulated_proposition: Vec<SigmaBoolean> = Vec::new();
699        let mut bag_b =
700            bag_for_multi_sig(&cand, &real_proposition, &simulated_proposition, &proof).unwrap();
701        bag_b.add_hint(Hint::CommitmentHint(CommitmentHint::OwnCommitment(
702            own.first().unwrap().clone(),
703        )));
704        let proof2 = prover2
705            .prove(&tree_and, &ctx, message.as_slice(), &bag_b)
706            .unwrap();
707        let proof_byte: ProofBytes = proof2.proof;
708        let verifier = TestVerifier;
709
710        assert!(
711            verifier
712                .verify(&tree_and, &ctx, proof_byte, message.as_slice(),)
713                .unwrap()
714                .result,
715            "{}",
716            "{}"
717        );
718    }
719
720    #[test]
721    fn multi_sig_and_3() {
722        let ctx = force_any_val::<Context>();
723
724        let secret1 = DlogProverInput::random();
725        let secret2 = DlogProverInput::random();
726        let secret3 = DlogProverInput::random();
727        let pk1 = secret1.public_image();
728        let pk2 = secret2.public_image();
729        let pk3 = secret3.public_image();
730        let prover1 = TestProver {
731            secrets: vec![PrivateInput::DlogProverInput(secret1)],
732        };
733        let prover2 = TestProver {
734            secrets: vec![PrivateInput::DlogProverInput(secret2)],
735        };
736        let prover3 = TestProver {
737            secrets: vec![PrivateInput::DlogProverInput(secret3)],
738        };
739
740        let expr: Expr = SigmaAnd::new(vec![
741            Expr::Const(pk1.clone().into()),
742            Expr::Const(pk2.clone().into()),
743            Expr::Const(pk3.clone().into()),
744        ])
745        .unwrap()
746        .into();
747
748        let tree_expr = ErgoTree::try_from(expr.clone()).unwrap();
749
750        let expr_reduced = reduce_to_crypto(&expr, &ctx).unwrap().sigma_prop;
751        let mut generate_for: Vec<SigmaBoolean> = vec![SigmaBoolean::ProofOfKnowledge(
752            SigmaProofOfKnowledgeTree::ProveDlog(pk2),
753        )];
754        let hints_from_bob: HintsBag = generate_commitments_for(&expr_reduced, &generate_for);
755        let bag2 = hints_from_bob.real_commitments();
756        let bob_secret_commitment = hints_from_bob.own_commitments();
757        generate_for = vec![SigmaBoolean::ProofOfKnowledge(
758            SigmaProofOfKnowledgeTree::ProveDlog(pk3.clone()),
759        )];
760        let hints_from_carol: HintsBag = generate_commitments_for(&expr_reduced, &generate_for);
761        let bag3 = hints_from_carol.real_commitments();
762        let carol_secret_commitment = hints_from_carol.own_commitments();
763        let message = vec![0u8; 100];
764        let mut bag_a = HintsBag { hints: vec![] };
765        bag_a.add_hint(Hint::CommitmentHint(CommitmentHint::RealCommitment(
766            bag2.first().unwrap().clone(),
767        )));
768        bag_a.add_hint(Hint::CommitmentHint(CommitmentHint::RealCommitment(
769            bag3.first().unwrap().clone(),
770        )));
771        let proof1 = prover1
772            .prove(&tree_expr, &ctx, message.as_slice(), &bag_a)
773            .unwrap();
774        let mut proof: Vec<u8> = Vec::from(proof1.proof.clone());
775        let proof_byte1: ProofBytes = proof1.proof;
776        let mut real_proposition: Vec<SigmaBoolean> = vec![SigmaBoolean::ProofOfKnowledge(
777            SigmaProofOfKnowledgeTree::ProveDlog(pk1.clone()),
778        )];
779        let simulated_proposition: Vec<SigmaBoolean> = Vec::new();
780        let mut bag_c = bag_for_multi_sig(
781            &expr_reduced,
782            &real_proposition,
783            &simulated_proposition,
784            &proof,
785        )
786        .unwrap();
787        bag_c.add_hint(Hint::CommitmentHint(CommitmentHint::OwnCommitment(
788            carol_secret_commitment.first().unwrap().clone(),
789        )));
790        bag_c.add_hint(Hint::CommitmentHint(CommitmentHint::RealCommitment(
791            bag2.first().unwrap().clone(),
792        )));
793        let proof3 = prover3
794            .prove(&tree_expr, &ctx, message.as_slice(), &bag_c)
795            .unwrap();
796        proof = Vec::from(proof3.proof.clone());
797        let proof_byte3: ProofBytes = proof3.proof;
798        real_proposition = vec![
799            SigmaBoolean::ProofOfKnowledge(SigmaProofOfKnowledgeTree::ProveDlog(pk1)),
800            SigmaBoolean::ProofOfKnowledge(SigmaProofOfKnowledgeTree::ProveDlog(pk3)),
801        ];
802        let mut bag_b = bag_for_multi_sig(
803            &expr_reduced,
804            &real_proposition,
805            &simulated_proposition,
806            &proof,
807        )
808        .unwrap();
809        bag_b.add_hint(Hint::CommitmentHint(CommitmentHint::OwnCommitment(
810            bob_secret_commitment.first().unwrap().clone(),
811        )));
812        let proof2 = prover2
813            .prove(&tree_expr, &ctx, message.as_slice(), &bag_b)
814            .unwrap();
815        let proof_byte2: ProofBytes = proof2.proof;
816        let verifier = TestVerifier;
817
818        assert!(
819            !verifier
820                .verify(&tree_expr, &ctx, proof_byte1, message.as_slice(),)
821                .unwrap()
822                .result,
823            "{}",
824            "{}"
825        );
826
827        assert!(
828            !verifier
829                .verify(&tree_expr, &ctx, proof_byte3, message.as_slice(),)
830                .unwrap()
831                .result,
832            "{}",
833            "{}"
834        );
835
836        assert!(
837            verifier
838                .verify(&tree_expr, &ctx, proof_byte2, message.as_slice(),)
839                .unwrap()
840                .result,
841            "{}",
842            "{}"
843        );
844    }
845
846    #[test]
847    fn multi_dlog_dht() {
848        let ctx = force_any_val::<Context>();
849
850        let secret_alice = DlogProverInput::random();
851        let secret_bob = DlogProverInput::random();
852        let secret_carol = DhTupleProverInput::random();
853        let secret_dave = DhTupleProverInput::random();
854
855        let pk_alice = secret_alice.public_image();
856        let pk_bob = secret_bob.public_image();
857        let pk_carol = secret_carol.public_image();
858        let pk_dave = secret_dave.public_image();
859        let prover_a = TestProver {
860            secrets: vec![PrivateInput::DlogProverInput(secret_alice)],
861        };
862        let _prover_b = TestProver {
863            secrets: vec![PrivateInput::DlogProverInput(secret_bob)],
864        };
865        let _prover_c = TestProver {
866            secrets: vec![PrivateInput::DhTupleProverInput(secret_carol.clone())],
867        };
868        let _prover_d = TestProver {
869            secrets: vec![PrivateInput::DhTupleProverInput(secret_dave.clone())],
870        };
871        let first_expr: Expr = SigmaOr::new(vec![
872            Expr::Const(pk_alice.clone().into()),
873            Expr::Const(pk_bob.clone().into()),
874        ])
875        .unwrap()
876        .into();
877        let second_expr: Expr = SigmaOr::new(vec![
878            Expr::Const(pk_carol.clone().into()),
879            Expr::Const(pk_dave.clone().into()),
880        ])
881        .unwrap()
882        .into();
883        let exp: Expr = SigmaAnd::new(vec![first_expr, second_expr]).unwrap().into();
884        let tree = ErgoTree::try_from(exp.clone()).unwrap();
885        let ctree = reduce_to_crypto(&exp, &ctx).unwrap().sigma_prop;
886        let mut generate_for: Vec<SigmaBoolean> = vec![SigmaBoolean::ProofOfKnowledge(
887            SigmaProofOfKnowledgeTree::ProveDlog(pk_alice.clone()),
888        )];
889        let alice_hints: HintsBag = generate_commitments_for(&ctree, &generate_for);
890        let secret_commitment_alice = alice_hints.own_commitments();
891        generate_for = vec![SigmaBoolean::ProofOfKnowledge(
892            SigmaProofOfKnowledgeTree::ProveDhTuple(pk_dave.clone()),
893        )];
894        let dave_hints: HintsBag = generate_commitments_for(&ctree, &generate_for);
895        let dave_known = dave_hints.real_commitments();
896        let _dave_secret_commitment = dave_hints.own_commitments();
897        let message = vec![0u8; 100];
898        let mut bag_a = HintsBag::empty();
899        bag_a.add_hint(Hint::CommitmentHint(CommitmentHint::OwnCommitment(
900            secret_commitment_alice.first().unwrap().clone(),
901        )));
902        bag_a.add_hint(Hint::CommitmentHint(CommitmentHint::RealCommitment(
903            dave_known.first().unwrap().clone(),
904        )));
905        let proof_a = prover_a
906            .prove(&tree, &ctx, message.as_slice(), &bag_a)
907            .unwrap();
908        let proof: Vec<u8> = Vec::from(proof_a.proof.clone());
909        let proof_byte_a: ProofBytes = proof_a.proof;
910        let verifier = TestVerifier;
911
912        assert!(
913            !verifier
914                .verify(&tree, &ctx, proof_byte_a, message.as_slice(),)
915                .unwrap()
916                .result,
917            "{}",
918            "{}"
919        );
920        let real_proposition: Vec<SigmaBoolean> = vec![SigmaBoolean::ProofOfKnowledge(
921            SigmaProofOfKnowledgeTree::ProveDlog(pk_alice),
922        )];
923        let simulated_proposition: Vec<SigmaBoolean> = vec![
924            SigmaBoolean::ProofOfKnowledge(SigmaProofOfKnowledgeTree::ProveDlog(pk_bob.clone())),
925            SigmaBoolean::ProofOfKnowledge(SigmaProofOfKnowledgeTree::ProveDhTuple(
926                pk_carol.clone(),
927            )),
928        ];
929        println!(
930            "{:?}",
931            SigmaBoolean::ProofOfKnowledge(SigmaProofOfKnowledgeTree::ProveDlog(pk_bob))
932        );
933        let mut bag =
934            bag_for_multi_sig(&ctree, &real_proposition, &simulated_proposition, &proof).unwrap();
935        bag.add_hint(Hint::CommitmentHint(CommitmentHint::OwnCommitment(
936            _dave_secret_commitment.first().unwrap().clone(),
937        )));
938
939        let proof_d = _prover_d
940            .prove(&tree, &ctx, message.as_slice(), &bag)
941            .unwrap();
942        let proof_byte_d: ProofBytes = proof_d.proof;
943
944        assert!(
945            verifier
946                .verify(&tree, &ctx, proof_byte_d, message.as_slice())
947                .unwrap()
948                .result,
949            "{}",
950            "{}"
951        );
952    }
953
954    #[test]
955    fn multi_sig_atleast_2_out_of_3() {
956        // from https://github.com/ScorexFoundation/sigmastate-interpreter/blob/78dd1e715038c2f95c518fb56977c6591b76e20c/sc/src/test/scala/sigmastate/utxo/DistributedSigSpecification.scala#L124
957        let ctx = force_any_val::<Context>();
958
959        let alice_secret = DlogProverInput::random();
960        let bob_secret = DlogProverInput::random();
961        let carol_secret = DlogProverInput::random();
962        let alice_pk = alice_secret.public_image();
963        let bob_pk = bob_secret.public_image();
964        let carol_pk = carol_secret.public_image();
965        let alice_prover = TestProver {
966            secrets: vec![PrivateInput::DlogProverInput(alice_secret)],
967        };
968        let bob_prover = TestProver {
969            secrets: vec![PrivateInput::DlogProverInput(bob_secret)],
970        };
971        let _carol_prover = TestProver {
972            secrets: vec![PrivateInput::DlogProverInput(carol_secret)],
973        };
974
975        let bound = Expr::Const(2i32.into());
976        let inputs = Literal::Coll(
977            CollKind::from_collection(
978                SType::SSigmaProp,
979                [
980                    SigmaProp::from(alice_pk.clone()).into(),
981                    SigmaProp::from(bob_pk.clone()).into(),
982                    SigmaProp::from(carol_pk.clone()).into(),
983                ],
984            )
985            .unwrap(),
986        );
987        let input = Constant {
988            tpe: SType::SColl(SType::SSigmaProp.into()),
989            v: inputs,
990        }
991        .into();
992        let expr: Expr = Atleast::new(bound, input).unwrap().into();
993
994        let tree_expr = ErgoTree::try_from(expr.clone()).unwrap();
995
996        let expr_reduced = reduce_to_crypto(&expr, &ctx).unwrap().sigma_prop;
997        let message = vec![0u8; 100];
998
999        let hints_from_bob: HintsBag = generate_commitments_for(&expr_reduced, &[bob_pk.into()]);
1000
1001        let bob_real_commitment = hints_from_bob.real_commitments();
1002
1003        let mut bag_a = HintsBag { hints: vec![] };
1004        bag_a.add_hint(Hint::CommitmentHint(CommitmentHint::RealCommitment(
1005            bob_real_commitment.first().unwrap().clone(),
1006        )));
1007
1008        let proof_alice = alice_prover
1009            .prove(&tree_expr, &ctx, message.as_slice(), &bag_a)
1010            .unwrap();
1011
1012        let mut bag_b = bag_for_multi_sig(
1013            &expr_reduced,
1014            &[alice_pk.into()],
1015            &[carol_pk.into()],
1016            proof_alice.proof.as_ref(),
1017        )
1018        .unwrap();
1019        bag_b.add_hint(Hint::CommitmentHint(CommitmentHint::OwnCommitment(
1020            hints_from_bob.own_commitments().first().unwrap().clone(),
1021        )));
1022
1023        let proof_bob = bob_prover
1024            .prove(&tree_expr, &ctx, message.as_slice(), &bag_b)
1025            .unwrap();
1026
1027        let verifier = TestVerifier;
1028
1029        assert!(
1030            !verifier
1031                .verify(&tree_expr, &ctx, proof_alice.proof, message.as_slice())
1032                .unwrap()
1033                .result,
1034            "Proof generated by Alice without getting Bob's part is not correct"
1035        );
1036
1037        assert!(
1038            verifier
1039                .verify(&tree_expr, &ctx, proof_bob.proof, message.as_slice())
1040                .unwrap()
1041                .result,
1042            "Compound proof from Bob is correct"
1043        );
1044    }
1045
1046    #[test]
1047    fn multi_sig_atleast_3_out_of_4() {
1048        // from https://github.com/ScorexFoundation/sigmastate-interpreter/blob/78dd1e715038c2f95c518fb56977c6591b76e20c/sc/src/test/scala/sigmastate/utxo/DistributedSigSpecification.scala#L160-L205
1049
1050        let ctx = force_any_val::<Context>();
1051
1052        let alice_secret = DlogProverInput::random();
1053        let bob_secret = DlogProverInput::random();
1054        let carol_secret = DlogProverInput::random();
1055        let dave_secret = DlogProverInput::random();
1056        let alice_pk = alice_secret.public_image();
1057        let bob_pk = bob_secret.public_image();
1058        let carol_pk = carol_secret.public_image();
1059        let dave_pk = dave_secret.public_image();
1060        let alice_prover = TestProver {
1061            secrets: vec![PrivateInput::DlogProverInput(alice_secret)],
1062        };
1063        let bob_prover = TestProver {
1064            secrets: vec![PrivateInput::DlogProverInput(bob_secret)],
1065        };
1066        let _carol_prover = TestProver {
1067            secrets: vec![PrivateInput::DlogProverInput(carol_secret)],
1068        };
1069
1070        let bound = Expr::Const(3i32.into());
1071        let inputs = Literal::Coll(
1072            CollKind::from_collection(
1073                SType::SSigmaProp,
1074                [
1075                    SigmaProp::from(alice_pk.clone()).into(),
1076                    SigmaProp::from(bob_pk.clone()).into(),
1077                    SigmaProp::from(carol_pk.clone()).into(),
1078                    SigmaProp::from(dave_pk.clone()).into(),
1079                ],
1080            )
1081            .unwrap(),
1082        );
1083        let input = Constant {
1084            tpe: SType::SColl(SType::SSigmaProp.into()),
1085            v: inputs,
1086        }
1087        .into();
1088        let expr: Expr = Atleast::new(bound, input).unwrap().into();
1089
1090        let tree_expr = ErgoTree::try_from(expr.clone()).unwrap();
1091
1092        let expr_reduced = reduce_to_crypto(&expr, &ctx).unwrap().sigma_prop;
1093        let message = vec![0u8; 100];
1094
1095        let bob_hints: HintsBag = generate_commitments_for(&expr_reduced, &[bob_pk.into()]);
1096        let dl_b_known = bob_hints.real_commitments().first().unwrap().clone();
1097
1098        let carol_hints: HintsBag =
1099            generate_commitments_for(&expr_reduced, &[carol_pk.clone().into()]);
1100        let dl_c_known = carol_hints.real_commitments().first().unwrap().clone();
1101
1102        let bag_a = HintsBag {
1103            hints: vec![dl_b_known.clone().into(), dl_c_known.into()],
1104        };
1105
1106        let proof_alice = alice_prover
1107            .prove(&tree_expr, &ctx, message.as_slice(), &bag_a)
1108            .unwrap();
1109
1110        let mut bag_c = bag_for_multi_sig(
1111            &expr_reduced,
1112            &[alice_pk.clone().into()],
1113            &[dave_pk.clone().into()],
1114            proof_alice.proof.as_ref(),
1115        )
1116        .unwrap();
1117        bag_c.hints.push(dl_b_known.into());
1118        bag_c.hints.push(
1119            carol_hints
1120                .own_commitments()
1121                .first()
1122                .unwrap()
1123                .clone()
1124                .into(),
1125        );
1126
1127        let proof_carol = _carol_prover
1128            .prove(&tree_expr, &ctx, message.as_slice(), &bag_c)
1129            .unwrap();
1130
1131        let bag_b_1 = bag_for_multi_sig(
1132            &expr_reduced,
1133            &[alice_pk.into()],
1134            &[dave_pk.into()],
1135            proof_alice.proof.as_ref(),
1136        )
1137        .unwrap();
1138
1139        let bag_b_2 = bag_for_multi_sig(
1140            &expr_reduced,
1141            &[carol_pk.into()],
1142            &[],
1143            proof_carol.proof.as_ref(),
1144        )
1145        .unwrap();
1146
1147        let mut bag_b = HintsBag::from_bags(vec![bag_b_1, bag_b_2]);
1148
1149        bag_b.add_hint(bob_hints.own_commitments().first().unwrap().clone().into());
1150
1151        let proof_bob = bob_prover
1152            .prove(&tree_expr, &ctx, message.as_slice(), &bag_b)
1153            .unwrap();
1154
1155        let verifier = TestVerifier;
1156
1157        assert!(
1158            !verifier
1159                .verify(&tree_expr, &ctx, proof_alice.proof, message.as_slice(),)
1160                .unwrap()
1161                .result,
1162            "Proof generated by Alice without getting Bob's part is not correct"
1163        );
1164
1165        assert!(
1166            !verifier
1167                .verify(&tree_expr, &ctx, proof_carol.proof, message.as_slice(),)
1168                .unwrap()
1169                .result,
1170            "Proof generated by Carol without getting Bob's part is not correct"
1171        );
1172
1173        assert!(
1174            verifier
1175                .verify(&tree_expr, &ctx, proof_bob.proof, message.as_slice())
1176                .unwrap()
1177                .result,
1178            "Compound proof from Bob is correct"
1179        );
1180    }
1181
1182    #[test]
1183    fn multi_sig_atleast_7_out_of_10_i692() {
1184        // based on
1185        // https://github.com/ScorexFoundation/sigmastate-interpreter/blob/78dd1e715038c2f95c518fb56977c6591b76e20c/sc/src/test/scala/sigmastate/utxo/DistributedSigSpecification.scala#L299-L389
1186        let ctx = force_any_val::<Context>();
1187
1188        let sk1 = DlogProverInput::random();
1189        let pk1 = sk1.public_image();
1190        let sk2 = DlogProverInput::random();
1191        let pk2 = sk2.public_image();
1192        let sk3 = DlogProverInput::random();
1193        let pk3 = sk3.public_image();
1194        let sk4 = DlogProverInput::random();
1195        let pk4 = sk4.public_image();
1196        let sk5 = DlogProverInput::random();
1197        let pk5 = sk5.public_image();
1198        let sk6 = DlogProverInput::random();
1199        let pk6 = sk6.public_image();
1200        let sk7 = DlogProverInput::random();
1201        let pk7 = sk7.public_image();
1202        let sk8 = DlogProverInput::random();
1203        let pk8 = sk8.public_image();
1204        let sk9 = DlogProverInput::random();
1205        let pk9 = sk9.public_image();
1206        let sk10 = DlogProverInput::random();
1207        let pk10 = sk10.public_image();
1208
1209        let prover1 = TestProver {
1210            secrets: vec![sk1.into()],
1211        };
1212        let prover2 = TestProver {
1213            secrets: vec![sk2.into()],
1214        };
1215        let prover3 = TestProver {
1216            secrets: vec![sk3.into()],
1217        };
1218        let prover4 = TestProver {
1219            secrets: vec![sk4.into()],
1220        };
1221        let prover5 = TestProver {
1222            secrets: vec![sk5.into()],
1223        };
1224        let prover6 = TestProver {
1225            secrets: vec![sk6.into()],
1226        };
1227        let prover7 = TestProver {
1228            secrets: vec![sk7.into()],
1229        };
1230        // let prover8 = TestProver {
1231        //     secrets: vec![sk8.into()],
1232        // };
1233        // let prover9 = TestProver {
1234        //     secrets: vec![sk9.into()],
1235        // };
1236        // let prover10 = TestProver {
1237        //     secrets: vec![sk10.into()],
1238        // };
1239
1240        let bound = Expr::Const(7i32.into());
1241        let input = Constant {
1242            tpe: SType::SColl(SType::SSigmaProp.into()),
1243            v: Literal::Coll(
1244                CollKind::from_collection(
1245                    SType::SSigmaProp,
1246                    [
1247                        SigmaProp::from(pk1.clone()).into(),
1248                        SigmaProp::from(pk2.clone()).into(),
1249                        SigmaProp::from(pk3.clone()).into(),
1250                        SigmaProp::from(pk4.clone()).into(),
1251                        SigmaProp::from(pk5.clone()).into(),
1252                        SigmaProp::from(pk6.clone()).into(),
1253                        SigmaProp::from(pk7.clone()).into(),
1254                        SigmaProp::from(pk8.clone()).into(),
1255                        SigmaProp::from(pk9.clone()).into(),
1256                        SigmaProp::from(pk10.clone()).into(),
1257                    ],
1258                )
1259                .unwrap(),
1260            ),
1261        }
1262        .into();
1263        let expr: Expr = Atleast::new(bound, input).unwrap().into();
1264        let tree_expr = ErgoTree::try_from(expr.clone()).unwrap();
1265        let expr_reduced = reduce_to_crypto(&expr, &ctx).unwrap().sigma_prop;
1266        let message = vec![0u8; 100];
1267
1268        // only actors 1, 2, 3, 4, 5, 6, 7 are signing, others are simulated (see bag_one below)
1269
1270        let hints_1 = generate_commitments_for(&expr_reduced, &[pk1.clone().into()]);
1271        let dl_1_known = hints_1.real_commitments().first().unwrap().clone();
1272        let secret_cmt_1 = hints_1.own_commitments().first().unwrap().clone();
1273
1274        let hints_2 = generate_commitments_for(&expr_reduced, &[pk2.clone().into()]);
1275        let dl_2_known = hints_2.real_commitments().first().unwrap().clone();
1276        let secret_cmt_2 = hints_2.own_commitments().first().unwrap().clone();
1277
1278        let hints_3 = generate_commitments_for(&expr_reduced, &[pk3.clone().into()]);
1279        let dl_3_known = hints_3.real_commitments().first().unwrap().clone();
1280        let secret_cmt_3 = hints_3.own_commitments().first().unwrap().clone();
1281
1282        let hints_4 = generate_commitments_for(&expr_reduced, &[pk4.clone().into()]);
1283        let dl_4_known = hints_4.real_commitments().first().unwrap().clone();
1284        let secret_cmt_4 = hints_4.own_commitments().first().unwrap().clone();
1285
1286        let hints_5 = generate_commitments_for(&expr_reduced, &[pk5.clone().into()]);
1287        let dl_5_known = hints_5.real_commitments().first().unwrap().clone();
1288        let secret_cmt_5 = hints_5.own_commitments().first().unwrap().clone();
1289
1290        let hints_6 = generate_commitments_for(&expr_reduced, &[pk6.clone().into()]);
1291        let dl_6_known = hints_6.real_commitments().first().unwrap().clone();
1292        let secret_cmt_6 = hints_6.own_commitments().first().unwrap().clone();
1293
1294        let hints_7 = generate_commitments_for(&expr_reduced, &[pk7.clone().into()]);
1295        let secret_cmt_7 = hints_7.own_commitments().first().unwrap().clone();
1296
1297        let bag_7 = HintsBag {
1298            hints: vec![
1299                dl_1_known.clone().into(),
1300                dl_2_known.clone().into(),
1301                dl_3_known.clone().into(),
1302                dl_4_known.clone().into(),
1303                dl_5_known.clone().into(),
1304                dl_6_known.clone().into(),
1305                secret_cmt_7.clone().into(),
1306            ],
1307        };
1308
1309        let proof_7 = prover7.prove(&tree_expr, &ctx, &message, &bag_7).unwrap();
1310
1311        let verifier = TestVerifier;
1312
1313        assert!(
1314            !verifier
1315                .verify(&tree_expr, &ctx, proof_7.proof.clone(), message.as_slice(),)
1316                .unwrap()
1317                .result,
1318            "Proof generated by Prover7 only is not correct"
1319        );
1320
1321        //hints after the first real proof done.
1322        let bag_one = bag_for_multi_sig(
1323            &expr_reduced,
1324            &[pk7.into()],
1325            &[pk8.into(), pk9.into(), pk10.into()],
1326            proof_7.proof.as_ref(),
1327        )
1328        .unwrap();
1329
1330        //now real proofs can be done in any order
1331        let mut bag_2 = bag_one.clone();
1332        bag_2.add_hint(secret_cmt_2.clone().into());
1333        bag_2.add_hint(dl_1_known.clone().into());
1334        bag_2.add_hint(dl_3_known.clone().into());
1335        bag_2.add_hint(dl_4_known.clone().into());
1336        bag_2.add_hint(dl_5_known.clone().into());
1337        bag_2.add_hint(dl_6_known.clone().into());
1338        let proof_2 = prover2.prove(&tree_expr, &ctx, &message, &bag_2).unwrap();
1339        let partial_proof_2 =
1340            bag_for_multi_sig(&expr_reduced, &[pk2.into()], &[], proof_2.proof.as_ref())
1341                .unwrap()
1342                .real_proofs()
1343                .first()
1344                .unwrap()
1345                .clone();
1346
1347        let mut bag_1 = bag_one.clone();
1348        bag_1.add_hint(secret_cmt_1.clone().into());
1349        bag_1.add_hint(dl_2_known.clone().into());
1350        bag_1.add_hint(dl_3_known.clone().into());
1351        bag_1.add_hint(dl_4_known.clone().into());
1352        bag_1.add_hint(dl_5_known.clone().into());
1353        bag_1.add_hint(dl_6_known.clone().into());
1354
1355        let proof_1 = prover1.prove(&tree_expr, &ctx, &message, &bag_1).unwrap();
1356        let partial_proof_1 =
1357            bag_for_multi_sig(&expr_reduced, &[pk1.into()], &[], proof_1.proof.as_ref())
1358                .unwrap()
1359                .real_proofs()
1360                .first()
1361                .unwrap()
1362                .clone();
1363
1364        let mut bag_3 = bag_one.clone();
1365        bag_3.add_hint(secret_cmt_3.clone().into());
1366        bag_3.add_hint(dl_1_known.clone().into());
1367        bag_3.add_hint(dl_2_known.clone().into());
1368        bag_3.add_hint(dl_4_known.clone().into());
1369        bag_3.add_hint(dl_5_known.clone().into());
1370        bag_3.add_hint(dl_6_known.clone().into());
1371        let proof_3 = prover3.prove(&tree_expr, &ctx, &message, &bag_3).unwrap();
1372        let partial_proof_3 =
1373            bag_for_multi_sig(&expr_reduced, &[pk3.into()], &[], proof_3.proof.as_ref())
1374                .unwrap()
1375                .real_proofs()
1376                .first()
1377                .unwrap()
1378                .clone();
1379
1380        let mut bag_4 = bag_one.clone();
1381        bag_4.add_hint(secret_cmt_4.clone().into());
1382        bag_4.add_hint(dl_1_known.clone().into());
1383        bag_4.add_hint(dl_2_known.clone().into());
1384        bag_4.add_hint(dl_3_known.clone().into());
1385        bag_4.add_hint(dl_5_known.clone().into());
1386        bag_4.add_hint(dl_6_known.clone().into());
1387        let proof_4 = prover4.prove(&tree_expr, &ctx, &message, &bag_4).unwrap();
1388        let partial_proof_4 =
1389            bag_for_multi_sig(&expr_reduced, &[pk4.into()], &[], proof_4.proof.as_ref())
1390                .unwrap()
1391                .real_proofs()
1392                .first()
1393                .unwrap()
1394                .clone();
1395
1396        let mut bag_5 = bag_one.clone();
1397        bag_5.add_hint(secret_cmt_5.clone().into());
1398        bag_5.add_hint(dl_1_known.clone().into());
1399        bag_5.add_hint(dl_2_known.clone().into());
1400        bag_5.add_hint(dl_3_known.clone().into());
1401        bag_5.add_hint(dl_4_known.clone().into());
1402        bag_5.add_hint(dl_6_known.clone().into());
1403        let proof_5 = prover5.prove(&tree_expr, &ctx, &message, &bag_5).unwrap();
1404        let partial_proof_5 =
1405            bag_for_multi_sig(&expr_reduced, &[pk5.into()], &[], proof_5.proof.as_ref())
1406                .unwrap()
1407                .real_proofs()
1408                .first()
1409                .unwrap()
1410                .clone();
1411
1412        let mut bag_6 = bag_one.clone();
1413        bag_6.add_hint(secret_cmt_6.clone().into());
1414        bag_6.add_hint(dl_1_known.clone().into());
1415        bag_6.add_hint(dl_2_known.clone().into());
1416        bag_6.add_hint(dl_3_known.clone().into());
1417        bag_6.add_hint(dl_4_known.clone().into());
1418        bag_6.add_hint(dl_5_known.clone().into());
1419        let proof_6 = prover6.prove(&tree_expr, &ctx, &message, &bag_6).unwrap();
1420        let partial_proof_6 =
1421            bag_for_multi_sig(&expr_reduced, &[pk6.into()], &[], proof_6.proof.as_ref())
1422                .unwrap()
1423                .real_proofs()
1424                .first()
1425                .unwrap()
1426                .clone();
1427
1428        let mut bag = bag_one;
1429        bag.add_hint(partial_proof_1.into());
1430        bag.add_hint(partial_proof_2.into());
1431        bag.add_hint(partial_proof_3.into());
1432        bag.add_hint(partial_proof_4.into());
1433        bag.add_hint(partial_proof_5.into());
1434        bag.add_hint(partial_proof_6.into());
1435        bag.add_hint(dl_1_known.into());
1436        bag.add_hint(dl_2_known.into());
1437        bag.add_hint(dl_3_known.into());
1438        bag.add_hint(dl_4_known.into());
1439        bag.add_hint(dl_5_known.into());
1440        bag.add_hint(dl_6_known.into());
1441
1442        let mut valid_bag_1 = bag.clone();
1443        valid_bag_1.add_hint(secret_cmt_1.into());
1444        let valid_proof_1 = prover1
1445            .prove(&tree_expr, &ctx, &message, &valid_bag_1)
1446            .unwrap();
1447
1448        assert!(
1449            verifier
1450                .verify(
1451                    &tree_expr,
1452                    &ctx,
1453                    valid_proof_1.proof.clone(),
1454                    message.as_slice(),
1455                )
1456                .unwrap()
1457                .result,
1458        );
1459
1460        let mut valid_bag_2 = bag.clone();
1461        valid_bag_2.add_hint(secret_cmt_2.into());
1462        let valid_proof_2 = prover2
1463            .prove(&tree_expr, &ctx, &message, &valid_bag_2)
1464            .unwrap();
1465        assert!(
1466            verifier
1467                .verify(
1468                    &tree_expr,
1469                    &ctx,
1470                    valid_proof_2.proof.clone(),
1471                    message.as_slice(),
1472                )
1473                .unwrap()
1474                .result,
1475        );
1476
1477        let mut valid_bag_3 = bag.clone();
1478        valid_bag_3.add_hint(secret_cmt_3.into());
1479        let valid_proof_3 = prover3
1480            .prove(&tree_expr, &ctx, &message, &valid_bag_3)
1481            .unwrap();
1482        assert!(
1483            verifier
1484                .verify(
1485                    &tree_expr,
1486                    &ctx,
1487                    valid_proof_3.proof.clone(),
1488                    message.as_slice()
1489                )
1490                .unwrap()
1491                .result
1492        );
1493
1494        let mut valid_bag_4 = bag.clone();
1495        valid_bag_4.add_hint(secret_cmt_4.into());
1496        let valid_proof_4 = prover4
1497            .prove(&tree_expr, &ctx, &message, &valid_bag_4)
1498            .unwrap();
1499        assert!(
1500            verifier
1501                .verify(
1502                    &tree_expr,
1503                    &ctx,
1504                    valid_proof_4.proof.clone(),
1505                    message.as_slice()
1506                )
1507                .unwrap()
1508                .result
1509        );
1510
1511        let mut valid_bag_5 = bag.clone();
1512        valid_bag_5.add_hint(secret_cmt_5.into());
1513        let valid_proof_5 = prover5
1514            .prove(&tree_expr, &ctx, &message, &valid_bag_5)
1515            .unwrap();
1516        assert!(
1517            verifier
1518                .verify(
1519                    &tree_expr,
1520                    &ctx,
1521                    valid_proof_5.proof.clone(),
1522                    message.as_slice()
1523                )
1524                .unwrap()
1525                .result
1526        );
1527
1528        let mut valid_bag_6 = bag.clone();
1529        valid_bag_6.add_hint(secret_cmt_6.into());
1530        let valid_proof_6 = prover6
1531            .prove(&tree_expr, &ctx, &message, &valid_bag_6)
1532            .unwrap();
1533        assert!(
1534            verifier
1535                .verify(
1536                    &tree_expr,
1537                    &ctx,
1538                    valid_proof_6.proof.clone(),
1539                    message.as_slice()
1540                )
1541                .unwrap()
1542                .result
1543        );
1544
1545        let mut valid_bag_7 = bag.clone();
1546        valid_bag_7.add_hint(secret_cmt_7.into());
1547        let valid_proof_7 = prover7
1548            .prove(&tree_expr, &ctx, &message, &valid_bag_7)
1549            .unwrap();
1550        assert!(
1551            verifier
1552                .verify(
1553                    &tree_expr,
1554                    &ctx,
1555                    valid_proof_7.proof.clone(),
1556                    message.as_slice()
1557                )
1558                .unwrap()
1559                .result
1560        );
1561
1562        assert_eq!(valid_proof_1.proof, valid_proof_2.proof);
1563        assert_eq!(valid_proof_2.proof, valid_proof_3.proof);
1564        assert_eq!(valid_proof_3.proof, valid_proof_4.proof);
1565        assert_eq!(valid_proof_4.proof, valid_proof_5.proof);
1566        assert_eq!(valid_proof_5.proof, valid_proof_6.proof);
1567        assert_eq!(valid_proof_6.proof, valid_proof_7.proof);
1568    }
1569}