Repository: ergoplatform/sigma-rust (language: rust)

Issue:
Check polynomial in Cthreshold* has correct size
### Motivation
`polynomial` in `CthresholdUnproven` and `CthresholdUnchecked` when [parsed]( https://github.com/ergoplatform/sigma-rust/blob/a7dee7a013662677771385d228931efe5215d25d/ergotree-interpreter/src/sigma_protocol/sig_serializer.rs#L219-L222 ) in `sig_serialized` is expected to be `(ct.children.len() - ct.k) * SOUNDNESS_BYTES` bytes long. 
To ensure proper parsing we should check that `polynomial` has the correct size. 

### Implementation details
I believe a failable ctor is the best way to ensure `Cthreshold*` correctness, i.e. fail early. Thus, forcing the call site to handle the possible error. Also, `with_polynomial` method should be updated as well. The check should be similar to the temporary assert I put in `sig_serializer` - https://github.com/ergoplatform/sigma-rust/blob/a7dee7a013662677771385d228931efe5215d25d/ergotree-interpreter/src/sigma_protocol/sig_serializer.rs#L96-L101



Discussion / hints:
Found some cases involving `AtLeast` that will fail this assertion. Self-contained code follows:
```rust
use std::{convert::TryInto, rc::Rc};

use ergotree_interpreter::{
    eval::{context::Context, env::Env},
    sigma_protocol::{
        private_input::DlogProverInput,
        prover::{hint::HintsBag, Prover, TestProver},
        verifier::{TestVerifier, Verifier},
    },
};
use ergotree_ir::{
    ergo_tree::ErgoTree,
    mir::{atleast::Atleast, collection::Collection, expr::Expr, sigma_or::SigmaOr},
    sigma_protocol::sigma_boolean::{SigmaBoolean, SigmaProp},
    types::stype::SType,
};
use sigma_test_util::force_any_val;

#[test]
fn test_at_least_multi_sig() {
    prove_tree(|multi_sig| multi_sig.try_into().unwrap());
}

#[test]
fn test_at_least_multi_sig_wrapped() {
    prove_tree(|multi_sig| {
        Expr::SigmaOr(SigmaOr::new(vec![multi_sig]).unwrap())
            .try_into()
            .unwrap()
    });
}

#[test]
fn test_at_least_multi_sig_or_with_true_first() {
    prove_tree(|multi_sig| {
        Expr::SigmaOr(
            SigmaOr::new(vec![
                SigmaProp::new(SigmaBoolean::TrivialProp(true)).into(),
                multi_sig,
            ])
            .unwrap(),
        )
        .try_into()
        .unwrap()
    });
}

#[test]
fn test_at_least_multi_sig_or_with_true_second() {
    prove_tree(|multi_sig| {
        Expr::SigmaOr(
            SigmaOr::new(vec![
                multi_sig,
                SigmaProp::new(SigmaBoolean::TrivialProp(true)).into(),
            ])
            .unwrap(),
        )
        .try_into()
        .unwrap()
    });
}

#[test]
fn test_at_least_multi_sig_or_with_false() {
    prove_tree(|multi_sig| {
        Expr::SigmaOr(
            SigmaOr::new(vec![
                SigmaProp::new(SigmaBoolean::TrivialProp(false)).into(),
                multi_sig,
            ])
            .unwrap(),
        )
        .try_into()
        .unwrap()
    });
}

fn prove_tree<F>(func: F)
where
    F: Fn(Expr) -> ErgoTree,
{
    let secret1 = DlogProverInput::random();
    let secret2 = DlogProverInput::random();
    let secret3 = DlogProverInput::random();
    let pk_alice = secret1.public_image();
    let pk_bob = secret2.public_image();
    let pk_carol = secret3.public_image();

    let multi_sig = Expr::Atleast(Atleast {
        bound: Expr::Const(2.into()).into(),
        input: Expr::Collection(Collection::Exprs {
            elem_tpe: SType::SSigmaProp,
            items: vec![
                SigmaProp::from(pk_alice).into(),
                SigmaProp::from(pk_bob).into(),
                SigmaProp::from(pk_carol).into(),
            ],
        })
        .into(),
    });

    let tree: ErgoTree = func(multi_sig);

    // Note that the prover only has the private for Alice. This is ensure that the AtLeast prop is unproved
    let prover = TestProver {
        secrets: vec![secret1.into()],
    };

    let message = vec![0u8; 100];
    let ctx: Rc<Context> = force_any_val::<Context>().into();
    let res = prover
        .prove(
            &tree,
            &Env::empty(),
            ctx.clone(),
            message.as_slice(),
            &HintsBag::empty(),
        )
        .unwrap()
        .proof;
    let verifier = TestVerifier;
    assert!(
        verifier
            .verify(&tree, &Env::empty(), ctx, res, message.as_slice())
            .unwrap()
            .result,
    );
}
```

## `multi_sig` by itself
`test_at_least_multi_sig` fails the assertion, ~~but `test_at_least_multi_sig_wrapped` returns `InvalidArgumentError("LowerBoundError { lower_bound: 2, got: 1 }"` which I think is the more appropriate error.~~ this is a non-issue since `SigmaOr` must have at least 2 arms.

## `SigmaOr`ing `multi_sig` with `true|false`

`test_at_least_multi_sig_or_with_true_[first|second]` passes fine but `test_at_least_multi_sig_or_with_false` trips up the assertion.

Fix patch:
diff --git a/ergotree-interpreter/src/sigma_protocol/proof_tree.rs b/ergotree-interpreter/src/sigma_protocol/proof_tree.rs
index 9c0ce77e6..cef7af695 100644
--- a/ergotree-interpreter/src/sigma_protocol/proof_tree.rs
+++ b/ergotree-interpreter/src/sigma_protocol/proof_tree.rs
@@ -144,9 +144,106 @@ pub(crate) enum ProofTreeKind<'a> {
     Conjecture(&'a dyn ProofTreeConjecture),
 }
 
+/// Traverses the tree in the bottom-up manner, calling `f` for every node/leaf and setting
+/// it's returned value (if `Some`) as new node/leaf or do nothing if it's returned `None`
+pub(crate) fn rewrite_bu<F>(tree: ProofTree, f: &F) -> Result<ProofTree, ProverError>
+where
+    F: Fn(&ProofTree) -> Result<Option<ProofTree>, ProverError>,
+{
+    let cast_to_ust = |children: SigmaConjectureItems<ProofTree>| {
+        children.try_mapped(|c| {
+            if let ProofTree::UncheckedTree(ust) = c {
+                Ok(ust)
+            } else {
+                Err(ProverError::Unexpected(
+                    "rewrite: expected UncheckedSigmaTree got UnprovenTree",
+                ))
+            }
+        })
+    };
+
+    let tree_with_updated_children = match &tree {
+        ProofTree::UnprovenTree(unp_tree) => match unp_tree {
+            UnprovenTree::UnprovenLeaf(_) => tree,
+            UnprovenTree::UnprovenConjecture(conj) => match conj {
+                UnprovenConjecture::CandUnproven(cand) => UnprovenTree::UnprovenConjecture(
+                    UnprovenConjecture::CandUnproven(CandUnproven {
+                        children: cand.children.clone().try_mapped(|c| rewrite_bu(c, f))?,
+                        ..cand.clone()
+                    }),
+                )
+                .into(),
+                UnprovenConjecture::CorUnproven(cor) => {
+                    UnprovenTree::UnprovenConjecture(UnprovenConjecture::CorUnproven(CorUnproven {
+                        children: cor.children.clone().try_mapped(|c| rewrite_bu(c, f))?,
+                        ..cor.clone()
+                    }))
+                    .into()
+                }
+                UnprovenConjecture::CthresholdUnproven(ct) => {
+                    UnprovenTree::UnprovenConjecture(UnprovenConjecture::CthresholdUnproven(
+                        ct.clone()
+                            .with_children(ct.clone().children.try_mapped(|c| rewrite_bu(c, f))?),
+                    ))
+                    .into()
+                }
+            },
+        },
+        ProofTree::UncheckedTree(unch_tree) => match unch_tree {
+            UncheckedTree::UncheckedLeaf(_) => tree,
+            UncheckedTree::UncheckedConjecture(conj) => match conj {
+                UncheckedConjecture::CandUnchecked {
+                    challenge,
+                    children,
+                } => {
+                    let rewritten_children =
+                        children.clone().try_mapped(|c| rewrite_bu(c.into(), f))?;
+                    let casted_children = cast_to_ust(rewritten_children)?;
+                    UncheckedConjecture::CandUnchecked {
+                        children: casted_children,
+                        challenge: challenge.clone(),
+                    }
+                    .into()
+                }
+                UncheckedConjecture::CorUnchecked {
+                    challenge,
+                    children,
+                } => {
+                    let rewritten_children =
+                        children.clone().try_mapped(|c| rewrite_bu(c.into(), f))?;
+                    let casted_children = cast_to_ust(rewritten_children)?;
+                    UncheckedConjecture::CorUnchecked {
+                        children: casted_children,
+                        challenge: challenge.clone(),
+                    }
+                    .into()
+                }
+                UncheckedConjecture::CthresholdUnchecked {
+                    challenge,
+                    children,
+                    k,
+                    polynomial: polynomial_opt,
+                } => {
+                    let rewritten_children =
+                        children.clone().try_mapped(|c| rewrite_bu(c.into(), f))?;
+                    let casted_children = cast_to_ust(rewritten_children)?;
+                    UncheckedConjecture::CthresholdUnchecked {
+                        children: casted_children,
+                        challenge: challenge.clone(),
+                        k: *k,
+                        polynomial: polynomial_opt.clone(),
+                    }
+                    .into()
+                }
+            },
+        },
+    };
+    Ok(f(&tree_with_updated_children)?.unwrap_or(tree_with_updated_children))
+}
+
 /// Traverses the tree in the top-down manner, calling `f` for every node/leaf and setting
 /// it's returned value (if `Some`) as new node/leaf or do nothing if it's returned `None`
-pub(crate) fn rewrite<F>(tree: ProofTree, f: &F) -> Result<ProofTree, ProverError>
+pub(crate) fn rewrite_td<F>(tree: ProofTree, f: &F) -> Result<ProofTree, ProverError>
 where
     F: Fn(&ProofTree) -> Result<Option<ProofTree>, ProverError>,
 {
@@ -169,25 +266,25 @@ where
             UnprovenTree::UnprovenConjecture(conj) => match conj {
                 UnprovenConjecture::CandUnproven(cand) => UnprovenTree::UnprovenConjecture(
                     UnprovenConjecture::CandUnproven(CandUnproven {
-                        children: cand.children.clone().try_mapped(|c| rewrite(c, f))?,
+                        children: cand.children.clone().try_mapped(|c| rewrite_td(c, f))?,
                         ..cand.clone()
                     }),
                 )
                 .into(),
                 UnprovenConjecture::CorUnproven(cor) => {
                     UnprovenTree::UnprovenConjecture(UnprovenConjecture::CorUnproven(CorUnproven {
-                        children: cor.children.clone().try_mapped(|c| rewrite(c, f))?,
+                        children: cor.children.clone().try_mapped(|c| rewrite_td(c, f))?,
                         ..cor.clone()
                     }))
                     .into()
                 }
-                UnprovenConjecture::CthresholdUnproven(ct) => UnprovenTree::UnprovenConjecture(
-                    UnprovenConjecture::CthresholdUnproven(CthresholdUnproven {
-                        children: ct.children.clone().try_mapped(|c| rewrite(c, f))?,
-                        ..ct.clone()
-                    }),
-                )
-                .into(),
+                UnprovenConjecture::CthresholdUnproven(ct) => {
+                    UnprovenTree::UnprovenConjecture(UnprovenConjecture::CthresholdUnproven(
+                        ct.clone()
+                            .with_children(ct.clone().children.try_mapped(|c| rewrite_td(c, f))?),
+                    ))
+                    .into()
+                }
             },
         },
         ProofTree::UncheckedTree(unch_tree) => match unch_tree {
@@ -198,7 +295,7 @@ where
                     children,
                 } => {
                     let rewritten_children =
-                        children.clone().try_mapped(|c| rewrite(c.into(), f))?;
+                        children.clone().try_mapped(|c| rewrite_td(c.into(), f))?;
                     let casted_children = cast_to_ust(rewritten_children)?;
                     UncheckedConjecture::CandUnchecked {
                         children: casted_children,
@@ -211,7 +308,7 @@ where
                     children,
                 } => {
                     let rewritten_children =
-                        children.clone().try_mapped(|c| rewrite(c.into(), f))?;
+                        children.clone().try_mapped(|c| rewrite_td(c.into(), f))?;
                     let casted_children = cast_to_ust(rewritten_children)?;
                     UncheckedConjecture::CorUnchecked {
                         children: casted_children,
@@ -226,7 +323,7 @@ where
                     polynomial: polynomial_opt,
                 } => {
                     let rewritten_children =
-                        children.clone().try_mapped(|c| rewrite(c.into(), f))?;
+                        children.clone().try_mapped(|c| rewrite_td(c.into(), f))?;
                     let casted_children = cast_to_ust(rewritten_children)?;
                     UncheckedConjecture::CthresholdUnchecked {
                         children: casted_children,
diff --git a/ergotree-interpreter/src/sigma_protocol/prover.rs b/ergotree-interpreter/src/sigma_protocol/prover.rs
index 50f282300..297907952 100644
--- a/ergotree-interpreter/src/sigma_protocol/prover.rs
+++ b/ergotree-interpreter/src/sigma_protocol/prover.rs
@@ -245,7 +245,7 @@ fn mark_real<P: Prover + ?Sized>(
     unproven_tree: UnprovenTree,
     hints_bag: &HintsBag,
 ) -> Result<UnprovenTree, ProverError> {
-    proof_tree::rewrite(unproven_tree.into(), &|tree| {
+    proof_tree::rewrite_bu(unproven_tree.into(), &|tree| {
         Ok(match tree {
             ProofTree::UnprovenTree(unp) => match unp {
                 UnprovenTree::UnprovenLeaf(unp_leaf) => {
@@ -293,13 +293,7 @@ fn mark_real<P: Prover + ?Sized>(
                             .filter(|c| c.is_real())
                             .count()
                             < ct.k as usize;
-                        Some(
-                            CthresholdUnproven {
-                                simulated,
-                                ..ct.clone()
-                            }
-                            .into(),
-                        )
+                        Some(ct.clone().with_simulated(simulated).into())
                     }
                 },
             },
@@ -374,7 +368,7 @@ fn polish_simulated<P: Prover + ?Sized>(
     _prover: &P,
     unproven_tree: UnprovenTree,
 ) -> Result<UnprovenTree, ProverError> {
-    proof_tree::rewrite(unproven_tree.into(), &|tree| match tree {
+    proof_tree::rewrite_td(unproven_tree.into(), &|tree| match tree {
         ProofTree::UnprovenTree(ut) => match ut {
             UnprovenTree::UnprovenLeaf(_) => Ok(None),
             UnprovenTree::UnprovenConjecture(conj) => match conj {
@@ -407,11 +401,10 @@ fn polish_simulated<P: Prover + ?Sized>(
                 UnprovenConjecture::CthresholdUnproven(ct) => {
                     // If the node is marked "simulated", mark all of its children "simulated"
                     let t: CthresholdUnproven = if ct.simulated {
-                        CthresholdUnproven {
-                            children: cast_to_unp(ct.children.clone())?
+                        ct.clone().with_children(
+                            cast_to_unp(ct.children.clone())?
                                 .mapped(|c| c.with_simulated(true).into()),
-                            ..ct.clone()
-                        }
+                        )
                     } else {
                         // If the node is THRESHOLD(k) marked "real", mark all but k of its children "simulated"
                         // (the node is guaranteed, by the previous step, to have at least k "real" children).
@@ -430,17 +423,15 @@ fn polish_simulated<P: Prover + ?Sized>(
                                 };
                             };
                         }
-                        CthresholdUnproven {
-                            children: unproven_children.enumerated().mapped(|(idx, c)| {
+                        ct.clone()
+                            .with_children(unproven_children.enumerated().mapped(|(idx, c)| {
                                 if children_indices_to_be_marked_simulated.contains(&idx) {
                                     c.with_simulated(true)
                                 } else {
                                     c
                                 }
                                 .into()
-                            }),
-                            ..ct.clone()
-                        }
+                            }))
                     };
                     Ok(Some(set_positions(t.into())?.into()))
                 }
@@ -586,7 +577,7 @@ fn step4_simulated_threshold_conj(
             })
             .mapped(|c| c.into());
         Ok(Some(
-            ct.with_polynomial(q).with_children(new_children).into(),
+            ct.with_children(new_children).with_polynomial(q)?.into(),
         ))
     } else {
         Err(ProverError::Unexpected(
@@ -726,7 +717,7 @@ fn simulate_and_commit(
     unproven_tree: UnprovenTree,
     hints_bag: &HintsBag,
 ) -> Result<UnprovenTree, ProverError> {
-    proof_tree::rewrite(unproven_tree.into(), &|tree| {
+    proof_tree::rewrite_td(unproven_tree.into(), &|tree| {
         match tree {
             // Step 4 part 1: If the node is marked "real", jhen each of its simulated children gets a fresh uniformly
             // random challenge in {0,1}^t.
@@ -861,7 +852,7 @@ fn step9_real_threshold(ct: CthresholdUnproven) -> Result<Option<ProofTree>, Pro
             }
         });
         Ok(Some(
-            ct.with_polynomial(q).with_children(new_children).into(),
+            ct.with_children(new_children).with_polynomial(q)?.into(),
         ))
     } else {
         Err(ProverError::Unexpected(
@@ -1033,7 +1024,7 @@ fn proving<P: Prover + ?Sized>(
     proof_tree: ProofTree,
     hints_bag: &HintsBag,
 ) -> Result<ProofTree, ProverError> {
-    proof_tree::rewrite(proof_tree, &|tree| {
+    proof_tree::rewrite_td(proof_tree, &|tree| {
         match &tree {
             ProofTree::UncheckedTree(unch) => match unch {
                 UncheckedTree::UncheckedLeaf(_) => Ok(None),
@@ -1126,17 +1117,15 @@ fn convert_to_unproven(sb: SigmaBoolean) -> Result<UnprovenTree, ProverError> {
                 position: NodePosition::crypto_tree_prefix(),
             }
             .into(),
-            SigmaConjecture::Cthreshold(ct) => CthresholdUnproven {
-                proposition: ct.clone(),
-                k: ct.k,
-                children: ct
-                    .children
+            SigmaConjecture::Cthreshold(ct) => CthresholdUnproven::new(
+                ct.clone(),
+                ct.k,
+                ct.children
                     .try_mapped(|it| convert_to_unproven(it).map(Into::into))?,
-                polinomial_opt: None,
-                challenge_opt: None,
-                simulated: false,
-                position: NodePosition::crypto_tree_prefix(),
-            }
+                None,
+                false,
+                NodePosition::crypto_tree_prefix(),
+            )
             .into(),
         },
         SigmaBoolean::TrivialProp(_) => {
@@ -1181,7 +1170,7 @@ fn convert_to_unchecked(tree: ProofTree) -> Result<UncheckedTree, ProverError> {
                         })?,
                         children: ct.children.clone().try_mapped(convert_to_unchecked)?,
                         k: ct.k,
-                        polynomial: ct.polinomial_opt.clone().ok_or({
+                        polynomial: ct.polinomial_opt().ok_or({
                             ProverError::Unexpected("no polynomial in CthresholdUnproven")
                         })?,
                     }
@@ -1214,11 +1203,14 @@ mod tests {
     use super::*;
     use crate::sigma_protocol::private_input::DhTupleProverInput;
     use crate::sigma_protocol::private_input::DlogProverInput;
+    use ergotree_ir::mir::atleast::Atleast;
+    use ergotree_ir::mir::collection::Collection;
     use ergotree_ir::mir::constant::Constant;
     use ergotree_ir::mir::constant::Literal;
     use ergotree_ir::mir::expr::Expr;
     use ergotree_ir::mir::sigma_and::SigmaAnd;
     use ergotree_ir::mir::sigma_or::SigmaOr;
+    use ergotree_ir::sigma_protocol::sigma_boolean::SigmaProp;
     use ergotree_ir::types::stype::SType;
     use sigma_test_util::force_any_val;
     use std::convert::TryFrom;
@@ -1420,4 +1412,55 @@ mod tests {
         assert!(res.is_ok());
         assert_ne!(res.unwrap().proof, ProofBytes::Empty);
     }
+
+    #[test]
+    fn test_prove_inner_threshold() {
+        // this test constructed from https://github.com/ergoplatform/sigma-rust/issues/579#issuecomment-1259058014
+        let secret1 = DlogProverInput::random();
+        let secret2 = DlogProverInput::random();
+        let secret3 = DlogProverInput::random();
+        let pk_alice = secret1.public_image();
+        let pk_bob = secret2.public_image();
+        let pk_carol = secret3.public_image();
+
+        let at_least = Expr::Atleast(Atleast {
+            bound: Expr::Const(2.into()).into(),
+            input: Expr::Collection(Collection::Exprs {
+                elem_tpe: SType::SSigmaProp,
+                items: vec![
+                    SigmaProp::from(pk_alice).into(),
+                    SigmaProp::from(pk_bob).into(),
+                    SigmaProp::from(pk_carol).into(),
+                ],
+            })
+            .into(),
+        });
+
+        // wrap in binary OR with a false on the other side
+        let tree: ErgoTree = Expr::SigmaOr(
+            SigmaOr::new(vec![
+                SigmaProp::new(SigmaBoolean::TrivialProp(false)).into(),
+                at_least,
+            ])
+            .unwrap(),
+        )
+        .try_into()
+        .unwrap();
+
+        // Note that the prover only has the private for Alice. This is ensure that the AtLeast prop is unproved
+        let prover = TestProver {
+            secrets: vec![secret1.into()],
+        };
+
+        let message = vec![0u8; 100];
+        let ctx: Rc<Context> = force_any_val::<Context>().into();
+        let res = prover.prove(
+            &tree,
+            &Env::empty(),
+            ctx,
+            message.as_slice(),
+            &HintsBag::empty(),
+        );
+        assert!(res.is_err());
+    }
 }
diff --git a/ergotree-interpreter/src/sigma_protocol/unproven_tree.rs b/ergotree-interpreter/src/sigma_protocol/unproven_tree.rs
index 9787cdbf7..d07b61f05 100644
--- a/ergotree-interpreter/src/sigma_protocol/unproven_tree.rs
+++ b/ergotree-interpreter/src/sigma_protocol/unproven_tree.rs
@@ -5,9 +5,11 @@ use super::proof_tree::ConjectureType;
 use super::proof_tree::ProofTree;
 use super::proof_tree::ProofTreeConjecture;
 use super::proof_tree::ProofTreeKind;
+use super::prover::ProverError;
 use super::wscalar::Wscalar;
 use super::{dlog_protocol::FirstDlogProverMessage, Challenge, FirstProverMessage};
 use crate::sigma_protocol::proof_tree::ProofTreeLeaf;
+use crate::sigma_protocol::SOUNDNESS_BYTES;
 use ergotree_ir::sigma_protocol::sigma_boolean::cand::Cand;
 use ergotree_ir::sigma_protocol::sigma_boolean::cor::Cor;
 use ergotree_ir::sigma_protocol::sigma_boolean::cthreshold::Cthreshold;
@@ -502,21 +504,48 @@ pub(crate) struct CthresholdUnproven {
     pub(crate) proposition: Cthreshold,
     pub(crate) k: u8,
     pub(crate) children: SigmaConjectureItems<ProofTree>,
-    pub(crate) polinomial_opt: Option<Gf2_192Poly>,
+    polinomial_opt: Option<Gf2_192Poly>,
     pub(crate) challenge_opt: Option<Challenge>,
     pub(crate) simulated: bool,
     pub(crate) position: NodePosition,
 }
 
 impl CthresholdUnproven {
+    pub(crate) fn new(
+        proposition: Cthreshold,
+        k: u8,
+        children: SigmaConjectureItems<ProofTree>,
+        challenge_opt: Option<Challenge>,
+        simulated: bool,
+        position: NodePosition,
+    ) -> Self {
+        Self {
+            proposition,
+            k,
+            children,
+            polinomial_opt: None,
+            challenge_opt,
+            simulated,
+            position,
+        }
+    }
+
     pub(crate) fn with_children(self, children: SigmaConjectureItems<ProofTree>) -> Self {
         Self { children, ..self }
     }
 
-    pub(crate) fn with_polynomial(self, q: Gf2_192Poly) -> Self {
-        Self {
-            polinomial_opt: Some(q),
-            ..self
+    #[allow(clippy::panic)]
+    pub(crate) fn with_polynomial(self, q: Gf2_192Poly) -> Result<Self, ProverError> {
+        let bytes = q.to_bytes();
+        if bytes.len() == (self.proposition.children.len() - self.k as usize) * SOUNDNESS_BYTES {
+            Ok(Self {
+                polinomial_opt: Some(q),
+                ..self
+            })
+        } else {
+            Err(ProverError::Unexpected(
+                "Invalid polynomial length in CthresholdUnproven (children.len() - k) * SOUNDNESS_BYTES != polynomial.len()"
+            ))
         }
     }
 
@@ -534,11 +563,15 @@ impl CthresholdUnproven {
         }
     }
 
-    fn with_simulated(self, simulated: bool) -> Self {
+    pub(crate) fn with_simulated(self, simulated: bool) -> Self {
         Self { simulated, ..self }
     }
 
     pub(crate) fn is_real(&self) -> bool {
         !self.simulated
     }
+
+    pub(crate) fn polinomial_opt(&self) -> Option<Gf2_192Poly> {
+        self.polinomial_opt.clone()
+    }
 }


Test patch: