Repository: simple-crypto/SCALib (language: python)

Issue:
Factorgraph sanity_check fails for OR Properties
**Describe the bug**
When trying to sanity check a Factorgraph with an OR property, the check gives exactly the wrong result

**To Reproduce**

```py
import scalib.attacks 

fg = scalib.attacks.FactorGraph('''
NC 2
VAR SINGLE A
VAR SINGLE B
VAR SINGLE C
PROPERTY A = B | C
''')

fg.sanity_check({}, {'A':0,'B':0,'C':0})
fg.sanity_check({}, {'A':1,'B':0,'C':1})
fg.sanity_check({}, {'A':1,'B':1,'C':0})
fg.sanity_check({}, {'A':1,'B':1,'C':1})
```

all those checks fail

```
ValueError: Failure at factor ANONYMOUS_0. Expected result 1, got 0.
```

**Expected behavior**
Checks should pass

**Environment (please complete the following information):**
 - OS: Debian GNU/Linux 11 (bullseye)
- Python version: 3.8
- numpy version: 1.23.5
- SCALib version: 0.5.7
- How did you install SCALib? Build from source
- What CPU do you use? 2x Intel(R) Xeon(R) Gold 5317

**Additional context**
--


Discussion / hints:

Fix patch:
diff --git a/src/scalib_ext/scalib/src/sasca/factor_graph.rs b/src/scalib_ext/scalib/src/sasca/factor_graph.rs
index 40d3c0ab..38a7da34 100644
--- a/src/scalib_ext/scalib/src/sasca/factor_graph.rs
+++ b/src/scalib_ext/scalib/src/sasca/factor_graph.rs
@@ -298,11 +298,20 @@ impl FactorGraph {
                         .skip(skip_res)
                         .map(|v_id| &var_assignments[*v_id]);
                     let res = match expr {
-                        ExprFactor::AND { vars_neg } => self.merge_pubs(
-                            expr,
-                            ops.zip(vars_neg.iter().cloned())
-                                .chain(std::iter::once((&cst, false))),
-                        ),
+                        ExprFactor::AND { vars_neg } => {
+                            let x = self.merge_pubs(
+                                expr,
+                                ops.zip(vars_neg.iter().cloned())
+                                    .chain(std::iter::once((&cst, false))),
+                            );
+                            // invert if we are doing an OR
+                            match x {
+                                PublicValue::Single(cv) if vars_neg[0] => {
+                                    PublicValue::Single(self.not(cv))
+                                }
+                                _ => x,
+                            }
+                        }
                         ExprFactor::XOR | ExprFactor::ADD | ExprFactor::MUL => self.merge_pubs(
                             expr,
                             ops.zip(std::iter::repeat(false))


Test patch:
diff --git a/tests/test_factorgraph.py b/tests/test_factorgraph.py
index cf1ab107..aa4d81c2 100644
--- a/tests/test_factorgraph.py
+++ b/tests/test_factorgraph.py
@@ -1381,3 +1381,30 @@ def test_clear_beliefs():
     assert bp.get_belief_from_var("x", "s1") is not None
     assert bp.get_belief_from_var("a", "s1") is not None
     assert bp.get_belief_from_var("b", "s1") is not None
+
+
+def test_sanity_or():
+    graph = """
+    NC 2
+    PROPERTY s1: x = a | b
+    VAR SINGLE x
+    VAR SINGLE a
+    VAR SINGLE b
+    """
+    fg = FactorGraph(graph)
+    fg.sanity_check({}, {"x": 0, "a": 0, "b": 0})
+    fg.sanity_check({}, {"x": 1, "a": 1, "b": 0})
+    fg.sanity_check({}, {"x": 1, "a": 0, "b": 1})
+    fg.sanity_check({}, {"x": 1, "a": 1, "b": 1})
+    graph = """
+    NC 2
+    PROPERTY s1: x = a & b
+    VAR SINGLE x
+    VAR SINGLE a
+    VAR SINGLE b
+    """
+    fg = FactorGraph(graph)
+    fg.sanity_check({}, {"x": 0, "a": 0, "b": 0})
+    fg.sanity_check({}, {"x": 0, "a": 1, "b": 0})
+    fg.sanity_check({}, {"x": 0, "a": 0, "b": 1})
+    fg.sanity_check({}, {"x": 1, "a": 1, "b": 1})