def test_substituter_conditions(self): x = Symbol("x") y = Symbol("y") and_x_x = And(x, x) ftype = FunctionType(BOOL, [BOOL]) f = Symbol("f", ftype) # 1. All arguments must be terms args_good = {x: y} args_bad = {x: f} substitute(and_x_x, args_good) with self.assertRaisesRegex(TypeError, " substitutions"): substitute(and_x_x, args_bad) # 2. All arguments belong to the manager of the substituter. new_mgr = FormulaManager(get_env()) new_x = new_mgr.Symbol("x") self.assertNotEqual(x, new_x) args_1 = {x: new_x} args_2 = {new_x: new_x} with self.assertRaisesRegex(TypeError, "Formula Manager"): substitute(and_x_x, args_1) with self.assertRaisesRegex(TypeError, "Formula Manager."): substitute(and_x_x, args_2) with self.assertRaisesRegex(TypeError, "substitute()"): substitute(f, {x: x})
def test_substituter_conditions(self): x = Symbol("x") y = Symbol("y") and_x_x = And(x, x) ftype = FunctionType(BOOL, [BOOL]) f = Symbol("f", ftype) # 1. All arguments must be terms args_good = {x:y} args_bad = {x:f} substitute(and_x_x, args_good) with self.assertRaisesRegex(TypeError, " substitutions"): substitute(and_x_x, args_bad) # 2. All arguments belong to the manager of the substituter. new_mgr = FormulaManager(get_env()) new_x = new_mgr.Symbol("x") self.assertNotEqual(x, new_x) args_1 = {x: new_x} args_2 = {new_x: new_x} with self.assertRaisesRegex(TypeError, "Formula Manager" ): substitute(and_x_x, args_1) with self.assertRaisesRegex(TypeError, "Formula Manager."): substitute(and_x_x, args_2) with self.assertRaisesRegex(TypeError, "substitute()"): substitute(f, {x:x})
def relax_edge(self, edge): u, v = min(edge), max(edge) # lexicographic order ucopy = self.copy_name(u, v) assert (edge in self.primal.edges()), f"{edge} not in G" assert ((u, v) not in self.relaxations), f"{edge} already relaxed" # create a new pysmt var old_var = self.primal.nodes()[u]['var'] new_var = Symbol(ucopy, REAL) # copy univariate clauses and potentials for u_c new_uniclauses = { substitute(c, {old_var: new_var}) for c in self.primal.nodes()[u]['clauses'] } new_unipotentials = [] # are these lit in potentials all univariate? for lit, w in self.primal.nodes()[u]['potentials']: new_unipotentials.append( (substitute(lit, {old_var: new_var}), w.subs(u, ucopy))) # add the node u_c to the primal graph self.primal.G.add_node(ucopy, var=new_var, clauses=new_uniclauses, potentials=new_unipotentials) # copy bivariate clauses and potentials for (u_c, v) new_biclauses = { substitute(c, {old_var: new_var}) for c in self.primal.edges()[(u, v)]['clauses'] } new_bipotentials = [] for lit, w in self.primal.edges()[(u, v)]['potentials']: new_bipotentials.append( (substitute(lit, {old_var: new_var}), w.subs(u, ucopy))) self.primal.G.add_edge(ucopy, v, clauses=new_biclauses, potentials=new_bipotentials) # store the relaxation (s.t. we can invert it) self.relaxations[(u, v)] = { 'copy_name': ucopy, 'old_edge': dict(self.primal.edges()[(u, v)]), 'new_edge': dict(self.primal.edges()[(ucopy, v)]) } if (u, ) not in self.relaxations: self.relaxations[(u, )] = {'copies': [ucopy]} else: self.relaxations[(u, )]['copies'].append(ucopy) # finally remove the (u, v) dependency self.primal.G.remove_edge(u, v)
def test_substitution_on_quantifiers(self): x, y = FreshSymbol(), FreshSymbol() # y /\ Forall x. x /\ y. f = And(y, ForAll([x], And(x, y))) subs = {y: Bool(True)} f_subs = substitute(f, subs).simplify() self.assertEqual(f_subs, ForAll([x], x)) subs = {x: Bool(True)} f_subs = substitute(f, subs).simplify() self.assertEqual(f_subs, f)
def test_subst(self): varA = Symbol("At", INT) varB = Symbol("Bt", INT) f = And(LT(varA, Plus(varB, Int(1))), GT(varA, Minus(varB, Int(1)))) g = Equals(varA, varB) h = Iff(f, g) res = substitute(h, subs={varA: varB}) self.assertEqual(res, h.substitute({varA: varB})) res = substitute(h, subs={varA: Int(1)}) self.assertEqual(res, h.substitute({varA: Int(1)}))
def test_substitution_on_functions(self): i, r = FreshSymbol(INT), FreshSymbol(REAL) f = Symbol("f", FunctionType(BOOL, [INT, REAL])) phi = Function(f, [Plus(i, Int(1)), Minus(r, Real(2))]) phi_sub = substitute(phi, {i: Int(0)}).simplify() self.assertEqual(phi_sub, Function(f, [Int(1), Minus(r, Real(2))])) phi_sub = substitute(phi, {r: Real(0)}).simplify() self.assertEqual(phi_sub, Function(f, [Plus(i, Int(1)), Real(-2)])) phi_sub = substitute(phi, {r: Real(0), i: Int(0)}).simplify() self.assertEqual(phi_sub, Function(f, [Int(1), Real(-2)]))
def test_substitution_on_quantifiers(self): x, y = FreshSymbol(), FreshSymbol() # y /\ Forall x. x /\ y. f = And(y, ForAll([x], And(x, y))) # Symbols within the quantified formula are not free symbols # and should not be substituted. subs = {y: TRUE()} f_subs = substitute(f, subs).simplify() self.assertEqual(f_subs, ForAll([x], x)) subs = {x: TRUE()} f_subs = substitute(f, subs).simplify() self.assertEqual(f_subs, f)
def get_formula_at_i(self, vars, formula, i, prefix="bmc_"): """Change formula replacing every variable var in vars with a variable named <prefix>_var_i and every variable var_next with a variable named <prefix>_var_j, where j is i+1. Example for i = 0, prefix = bmc_ Input: (v & v_next) | p Output: (bmc_v_0 & bmc_v_1) | bmc_p_0 """ if i in self.time_memo: time_i_map = self.time_memo[i] else: time_i_map = {} Helper.get_new_variables(vars, self.env.formula_manager, time_i_map, prefix, "_%d" % i) app_map = {} Helper.get_new_variables(vars, self.env.formula_manager, app_map, prefix, "_%d" % (i + 1)) for k, v in app_map.iteritems(): next_var = Helper.get_next_var(k, self.env.formula_manager) time_i_map[next_var] = v app_map = None self.time_memo[i] = time_i_map f_at_i = substitute(formula, time_i_map) return f_at_i
def add_dip_checker(self, dis_boolean, dis_out): for d in range(self.unroll_depth + 1): c0, c1 = self.attack_formulas.obf_ckt_at_frame(d) subs = {} c2 = [] if d > 0: dip_out = dis_out[d - 1] for i in range(len(self.obf_cir.output_wires)): subs[pystm.Symbol(self.obf_cir.output_wires[i] + '_0@{}'.format(d))] = dip_out[i] subs[pystm.Symbol(self.obf_cir.output_wires[i] + '_1@{}'.format(d))] = dip_out[i] dip_boolean = dis_boolean[d - 1] for i in range(len(self.obf_cir.input_wires)): subs[pystm.Symbol(self.obf_cir.input_wires[i] + '@{}'.format(d))] = dip_boolean[i] for i in range(len(self.obf_cir.next_state_wires)): subs[pystm.Symbol(self.obf_cir.next_state_wires[i] + '_0@{}'.format(d - 1))] = pystm.Symbol(self.obf_cir.next_state_wires[i] + '_0_{}@{}'.format(self.iteration, d - 1)) subs[pystm.Symbol(self.obf_cir.next_state_wires[i] + '_1@{}'.format(d - 1))] = pystm.Symbol(self.obf_cir.next_state_wires[i] + '_1_{}@{}'.format(self.iteration, d - 1)) subs[pystm.Symbol(self.obf_cir.next_state_wires[i] + '_0@{}'.format(d))] = pystm.Symbol(self.obf_cir.next_state_wires[i] + '_0_{}@{}'.format(self.iteration, d)) subs[pystm.Symbol(self.obf_cir.next_state_wires[i] + '_1@{}'.format(d))] = pystm.Symbol(self.obf_cir.next_state_wires[i] + '_1_{}@{}'.format(self.iteration, d)) subs[pystm.Symbol(self.obf_cir.state_wires[i] + '_0@{}'.format(d))] = pystm.Symbol(self.obf_cir.state_wires[i] + '_0_{}@{}'.format(self.iteration, d)) subs[pystm.Symbol(self.obf_cir.state_wires[i] + '_1@{}'.format(d))] = pystm.Symbol(self.obf_cir.state_wires[i] + '_1_{}@{}'.format(self.iteration, d)) else: for i in range(len(self.obf_cir.next_state_wires)): subs[pystm.Symbol(self.obf_cir.next_state_wires[i] + '_0@{}'.format(d))] = pystm.Symbol(self.obf_cir.next_state_wires[i] + '_0_{}@{}'.format(self.iteration, d)) subs[pystm.Symbol(self.obf_cir.next_state_wires[i] + '_1@{}'.format(d))] = pystm.Symbol(self.obf_cir.next_state_wires[i] + '_1_{}@{}'.format(self.iteration, d)) c = pystm.substitute(pystm.And(c0 + c1 + c2), subs) self.solver_obf.add_assertion(c) self.solver_key.add_assertion(c)
def resize_bvs(self, formula): """ Resize the bitvector variables wrt to the maximum domain size """ subs_map = {} for (fvar, max_value) in self.fvars_maxval.iteritems(): old_enc_var = self.fvars2encvars.lookup_a(fvar) bv_size = self.get_size(max_value+1) new_enc_var = FreshSymbol(BVType(bv_size)) self.fvars2encvars.add(fvar, new_enc_var) val2val = self.fvars2values[fvar] for i in range(max_value + 1): old_value = BV(i, SymbolicGrounding.MAX_BV) new_value = BV(i, bv_size) fval = val2val.lookup_b(old_value) val2val.add(fval, new_value) old_f = Equals(old_enc_var, old_value) new_f = Equals(new_enc_var, new_value) subs_map[old_f] = new_f formula = substitute(formula, subs_map) return formula
def clause_to_intervals(clause: FNode, sender_symbol: FNode, test_point: float): """ turn clause into a list of intervals, where there are at most two intervals :param clause: OR FNode or a single atom :param sender_symbol: :param test_point: an initiation value for recipient variable :return: list of tuples of form [lower, upper] """ upper_bounds, lower_bounds, recipient_atoms = categorize_bounds( clause, sender_symbol) if recipient_atoms: parent_symbol = list(get_real_variables(recipient_atoms[0])) for atom in recipient_atoms: bound = simplify( substitute(atom, {parent_symbol[0]: Real(test_point)})) if bound.is_true(): return [] num_uppers = [initiate_bound(upper, test_point) for upper in upper_bounds] num_lowers = [initiate_bound(lower, test_point) for lower in lower_bounds] if not num_uppers and not num_lowers: return [] if not num_uppers: return [[min(num_lowers), float('inf')]] if not num_lowers: return [[float('-inf'), max(num_uppers)]] max_upper, min_lower = max(num_uppers), min(num_lowers) if max_upper < min_lower: return [[float('-inf'), max_upper], [min_lower, float('inf')]] else: return []
def get_next_formula(self, vars, formula): """Given a formula returns the same formula where all the variables in vars are renamed to var_next""" next_map = {} Helper.get_new_variables(vars, self.env.formula_manager, next_map, "", "_next") next_formula = substitute(formula, next_map) return next_formula
def __init__(self, oracle_cir, obf_cir): self.orcl_cir = oracle_cir self.obf_cir = obf_cir self.key_subs = [{}, {}] orcl_wires = self.orcl_cir.wire_objs obf_wires = self.obf_cir.wire_objs # generate solver formulas self.gen_wire_formulas(self.orcl_cir) self.gen_wire_formulas(self.obf_cir) # create key substitution dictionary for w in self.obf_cir.key_wires: self.key_subs[0][Symbol(w)] = Symbol(w + '_0') self.key_subs[1][Symbol(w)] = Symbol(w + '_1') # generate formulas for two copies of obfuscated circuit ckt1 = [] ckt2 = [] for w in self.obf_cir.output_wires: ckt1.append(substitute(obf_wires[w].formula, self.key_subs[0])) ckt2.append(substitute(obf_wires[w].formula, self.key_subs[1])) output_xors = [] for i in range(len(self.obf_cir.output_wires)): output_xors.append(Xor(ckt1[i], ckt2[i])) self.dip_gen_ckt = Or(output_xors) # key inequality circuit key_symbols1 = [] key_symbols2 = [] for w in self.obf_cir.key_wires: key_symbols1.append(Symbol(w + '_0')) key_symbols2.append(Symbol(w + '_1')) output_xors = [] for i in range(len(key_symbols1)): output_xors.append(Xor(key_symbols1[i], key_symbols2[i])) self.key_inequality_ckt = Or(output_xors) # dip checker circuit self.dip_chk1 = [] self.dip_chk2 = [] for w in self.obf_cir.output_wires: self.dip_chk1.append(substitute(obf_wires[w].formula, self.key_subs[0])) self.dip_chk2.append(substitute(obf_wires[w].formula, self.key_subs[1]))
def test_substitution_complex(self): x, y = FreshSymbol(REAL), FreshSymbol(REAL) # y = 0 /\ (Forall x. x > 3 /\ y < 2) f = And(Equals(y, Real(0)), ForAll([x], And(GT(x, Real(3)), LT(y, Real(2))))) subs = {y: Real(0), ForAll([x], And(GT(x, Real(3)), LT(y, Real(2)))): TRUE()} f_subs = substitute(f, subs).simplify() self.assertEqual(f_subs, TRUE())
def test_substitution_term(self): x, y = FreshSymbol(REAL), FreshSymbol(REAL) # y = 0 /\ Forall x. x > 3 f = And(Equals(y, Real(0)), ForAll([x], GT(x, Real(3)))) subs = {GT(x, Real(3)): TRUE()} f_subs = substitute(f, subs) # Since 'x' is quantified, we cannot replace the term # therefore the substitution does not yield any result. self.assertEqual(f_subs, f)
def test_substitution_complex(self): x, y = FreshSymbol(REAL), FreshSymbol(REAL) # y = 0 /\ (Forall x. x > 3 /\ y < 2) f = And(Equals(y, Real(0)), ForAll([x], And(GT(x, Real(3)), LT(y, Real(2))))) subs = { y: Real(0), ForAll([x], And(GT(x, Real(3)), LT(y, Real(2)))): TRUE() } f_subs = substitute(f, subs).simplify() self.assertEqual(f_subs, TRUE())
def initiate_bound(bound: FNode, initiation: float) -> float: """ initiate an atom with only one variable with value initiation :param bound: FNode atom :param initiation: initiation of variable, type float :return: initiated bound, type float """ real_variables = list(get_real_variables(bound)) assert len(real_variables) < 2 if real_variables: # print(real_variables[0], Real(initiation)) bound = simplify( substitute(bound, {real_variables[0]: Real(initiation)})) return float(bound.constant_value())
def test_substitution_complex(self): x, y = Symbol("x", REAL), Symbol("y", REAL) # y = 0 /\ (Forall x. x > 3 /\ y < 2) f = And(Equals(y, Real(0)), ForAll([x], And(GT(x, Real(3)), LT(y, Real(2))))) subs = { y: Real(0), ForAll([x], And(GT(x, Real(3)), LT(y, Real(2)))): TRUE() } f_subs = substitute(f, subs).simplify() if self.env.SubstituterClass == MGSubstituter: self.assertEqual(f_subs, TRUE()) else: # In the MSS the y=0 substitution is performed first, # therefore, the overall quantified expression does not # match the one defined in the substitution map. # See test_substitution_complex_mss for a positive example. self.assertEqual(f_subs, ForAll([x], GT(x, Real(3))))
def compensating_potentials(self, n_comp_lits): winit = lambda x: safeexp(self.rand_gen.random()) comp_pots = {} init_w = 1 for k in self.relaxations: if len(k) == 1: continue x, y = k xc = self.relaxations[(x,y)]['copy_name'] var_x = self.primal.nodes()[x]['var'] var_xc = self.primal.nodes()[xc]['var'] if x not in comp_pots: # REM model for 'x' rem = MP2WMI(self.primal.get_univariate_formula(x), Real(1), n_processes=self.n_processes) # sampling compensating potentials for 'x' # original = [[self.sample_comp_literal(var_x), winit(None)] # for _ in range(n_comp_lits)] # try uniform weights original = [[self.sample_comp_literal(var_x), init_w] for _ in range(n_comp_lits)] print(f"%%%%%%% var {x} compensating literals:") for lit, w in original: print(f"%%%%%%% {lit}") comp_pots[x] = (rem, original, {}) else: original = comp_pots[x][1] # using the same comp lits for the copies of 'x' # comp_pots[x][2][xc] = [ # [substitute(lit, {var_x: var_xc}), winit(None)] # for lit, _ in original] comp_pots[x][2][xc] = [ [substitute(lit, {var_x: var_xc}), init_w] for lit, _ in original] return comp_pots
def comb_attack(self): # dis generator solver_name = 'btor' solver_obf = Solver(name=solver_name) solver_key = Solver(name=solver_name) solver_oracle = Solver(name=solver_name) attack_formulas = FormulaGenerator(self.oracle_cir, self.obf_cir) f = attack_formulas.dip_gen_ckt # f = simplify(f) solver_obf.add_assertion(f) f = attack_formulas.key_inequality_ckt # f = simplify(f) solver_obf.add_assertion(f) iteration = 0 while 1: # query dip generator if solver_obf.solve(): dip_formula = [] dip_boolean = [] for l in self.obf_cir.input_wires: t = Symbol(l) if solver_obf.get_py_value(t): dip_formula.append(t) dip_boolean.append(TRUE()) else: dip_formula.append(Not(t)) dip_boolean.append(FALSE()) logging.info(dip_formula) # query oracle dip_out = [] for l in self.oracle_cir.output_wires: t = self.oracle_cir.wire_objs[l].formula solver_oracle.reset_assertions() solver_oracle.add_assertion(t) if solver_oracle.solve(dip_formula): dip_out.append(TRUE()) else: dip_out.append(FALSE()) logging.info(dip_out) # add dip checker f = [] for i in range(len(attack_formulas.dip_chk1)): f.append(And(Iff(dip_out[i], attack_formulas.dip_chk1[i]), Iff(dip_out[i], attack_formulas.dip_chk2[i]))) f = And(f) subs = {} for i in range(len(self.obf_cir.input_wires)): subs[Symbol(self.obf_cir.input_wires[i])] = dip_boolean[i] # f = simplify(f) f = substitute(f, subs) solver_obf.add_assertion(f) solver_key.add_assertion(f) iteration += 1 logging.warning('iteration: {}'.format(iteration)) else: logging.warning('print keys') if solver_key.solve(): key = '' for i in range(len(self.obf_cir.key_wires)): k = 'keyinput{}_0'.format(i) if solver_key.get_py_value(Symbol(k)): key += '1' else: key += '0' print("key=%s" % key) else: logging.critical('key solver returned UNSAT') return
def _compute_WMI_PA(self, formula, weights): """Computes WMI using the Predicate Abstraction (PA) algorithm. Args: formula (FNode): The formula on whick to compute WMI. weights (Weight): The corresponding weight. Returns: real: The final volume of the integral computed by summing up all the integrals' results. int: The number of problems that have been computed. """ problems = [] boolean_variables = get_boolean_variables(formula) if len(boolean_variables) == 0: # Enumerate partial TA over theory atoms lab_formula, pa_vars, labels = self.label_formula(formula, formula.get_atoms()) # Predicate abstraction on LRA atoms with minimal models for assignments in self._compute_WMI_PA_no_boolean(lab_formula, pa_vars, labels): problem = self._create_problem(assignments, weights) problems.append(problem) else: solver = Solver(name="msat") converter = solver.converter solver.add_assertion(formula) boolean_models = [] # perform AllSAT on the Boolean variables mathsat.msat_all_sat( solver.msat_env(), [converter.convert(v) for v in boolean_variables], lambda model : WMI._callback(model, converter, boolean_models)) logger.debug("n_boolean_models: {}".format(len(boolean_models))) # for each boolean assignment mu^A of F for model in boolean_models: atom_assignments = {} boolean_assignments = WMI._get_assignments(model) atom_assignments.update(boolean_assignments) subs = {k : Bool(v) for k, v in boolean_assignments.items()} f_next = formula # iteratively simplify F[A<-mu^A], getting (possibily part.) mu^LRA while True: f_before = f_next f_next = simplify(substitute(f_before, subs)) lra_assignments, over = WMI._parse_lra_formula(f_next) subs = {k : Bool(v) for k, v in lra_assignments.items()} atom_assignments.update(lra_assignments) if over or lra_assignments == {}: break if not over: # predicate abstraction on LRA atoms with minimal models lab_formula, pa_vars, labels = self.label_formula(f_next, f_next.get_atoms()) expressions = [] for k, v in atom_assignments.items(): if k.is_theory_relation(): if v: expressions.append(k) else: expressions.append(Not(k)) lab_formula = And([lab_formula] + expressions) for assignments in self._compute_WMI_PA_no_boolean(lab_formula, pa_vars, labels, atom_assignments): problem = self._create_problem(assignments, weights) problems.append(problem) else: # integrate over mu^A & mu^LRA problem = self._create_problem(atom_assignments, weights) problems.append(problem) results, cached = self.integrator.integrate_batch(problems, self.cache) volume = fsum(results) return volume, len(problems)-cached, cached
def substitution_to_argument_tuple(pysmt_program_variables: List, sub: Dict): return tuple([substitute(var, sub) for var in pysmt_program_variables])
def apply_substitution_to_argument_tuple(argument, sub): return tuple([substitute(arg, sub) for arg in argument])