Narrowing is a generalization of term rewriting that allows logical variables in terms (as in logic programming) and replaces pattern matching by unification in order to symbolically evaluate these terms with given rewrite rules. Narrowing is a simple, precise answer to the question:
Given a term with variables , what are the most general instances of that can be rewritten by the given rules?
For example, can be , and the rewrite rules can be and . The term cannot be rewritten because of the variable and finding the most general instances becomes interesting; note that if is commutative, then either or stop from being rewritten.
Narrowing was originally introduced as a mechanism for solving equational unification problems [64]. It was later generalized to solve the more general problem of symbolic reachability [100]. The narrowing mechanism has a number of important applications, including automated proofs of termination [6], execution of functional-logic programming languages [67, 32, 70, 109, 92], partial evaluation [3], verification of cryptographic protocols [100], and equational unification [74], to mention just a few.
At each rewriting step one must choose which subterm of the subject term and which rule of the specification are going to be considered. Similarly, at each narrowing step one must choose which subterm of the subject term, which rule of the specification, and which instantiation on the variables of the subject term and the rule’s lefthand side are going to be considered. The narrowing relation is formally defined as follows. Let be an order-sorted rewrite theory where is a set of unconditional rewrite rules specified with the rl keyword, is a set of unconditional equations specified with the eq keyword, and is a set of commonly occurring axioms declared in Maude as equational attributes (see Section 4.4.1) such that an -unification procedure is available in Maude.1 Let provide2 a finitary and complete set of unifiers for any pair of terms with the same top sort. The -narrowing relation on is defined as (or when are understood) if there is a non-variable position of , a (possibly renamed) rule in , and a unifier such that . We denote by (resp. ) the transitive (resp. reflexive-transitive) closure of the narrowing relation, where is obtained as the composition of the substitutions for each narrowing step in the sequence.
The difference between a rewriting step and a narrowing step is that in both cases we use a rewrite rule to rewrite at a position in , but narrowing unifies the lefthand side and the chosen subject term before actually performing the rewriting step. Also, narrowing is usually3 restricted to non-variable positions of , whereas rewriting does not require such a restriction.
Consider the following system module defining the addition function _+_ on natural numbers built from 0 and s:
mod NAT-NARROWING is sort Nat . op 0 : -> Nat [ctor] . op s : Nat -> Nat [ctor] . op _+_ : Nat Nat -> Nat . vars X Y : Nat . rl [base] : 0 + Y => Y . rl [ind] : s(X) + Y => s(X + Y) . endm
Consider the term X + s(0) and the two rules base and ind. Narrowing will instantiate variable X with 0 and s(X’) respectively in order to be able to apply each of these rules, i.e., the following two narrowing steps are generated:
Note that, for simplicity, we show only the bindings of the unifier that affect the input term. There are infinitely many narrowing derivations starting at the input expression X + s(0) (at each step the reduced subterm is underlined):
and so on.
The following infinite narrowing derivation resulting from applying rule ind infinitely many times can also be shown:
The classical application of narrowing modulo an equational theory is to perform -unification by -narrowing when the equations are oriented into sort-decreasing, confluent, terminating, and coherent modulo rules . Indeed the variant-based equational order-sorted unification algorithm of Section 14.8 is based on an -narrowing strategy, called folding variant narrowing [62], that terminates when has the finite variant property [30], even though full -narrowing typically does not terminate when contains AC axioms [30, 62].
Instead, when the rules are understood as transition rules, a completely different application of -narrowing is that of symbolic reachability analysis [100]. Specifically, we will consider the case of transition systems specified by order-sorted rewrite theories of the form where: (i) has a finite and complete -unification algorithm (see the requirements of Section 14.1), and (ii) the transition rules are -coherent and topmost (so that rewriting is always done at the top of the term). Then, narrowing is a complete deductive method [100] for symbolic reachability analysis, that is, for solving existential queries of the form in the sense that the formula holds for iff there is a narrowing sequence such that and have an -unifier.
Furthermore, in symbolic reachability analysis we may be interested in verifying properties more general than existential questions of the form . One can also generalize the above reachability question to questions of the form , with a temporal logic formula. The papers [59, 7] show how narrowing can be used (again, both at the level of transitions with rules and at the level of equations ) to perform logical model checking to verify such temporal logic formulas; this is a kind of symbolic model checking not in the binary decision diagram sense of “symbolic,” which still remains finite-state, but in a much more general sense in which possibly infinite sets of states are finitely described by patterns with logical variables. Two distinctive features are: (i) the term does not describe a single initial state, but a possibly infinite set of instances of (i.e., a possibly infinite set of initial states4 ); and (ii) the set of reachable states does not have to be finite. Therefore, standard model-checking techniques may not be usable, because of a possible double infinity: in the number of initial states, and in the number of states reachable from each of those initial states.
Due to nontermination, narrowing behaves as a semi-decision procedure for equational unification and for reachability analysis in a wide variety of theories. However, for some particular subject terms narrowing may terminate, providing a complete set of solutions. For instance, in the Maude module NAT-NARROWING above, narrowing computes the solution for the reachability problem and it terminates with no more solutions. Instead, for the reachability problem , narrowing computes the solution but it cannot terminate because of the above infinite narrowing sequence using ind. Moreover, narrowing cannot prove that the reachability problem does not have a solution, again because of the above infinite narrowing sequence using ind.
Note that for any narrowing sequence , we have a corresponding rewrite sequence , where, by definition, the rewrite relation is the relation composition: , i.e., the transition relation between states in the given rewrite theory . However, only under appropriate conditions is narrowing complete as an equational unification algorithm [74, 75], or as a procedure to solve reachability problems [100]. That is, given a reachability problem5 , completeness of narrowing for reachability analysis means that for each possible substitution such that , and for all the substitutions , provided by narrowing from , there is an index and a term such that, if , then there is an -unifier of the equation , and therefore there is a rewrite sequence , showing that an instance of reaches an instance of modulo the equations . Essentially, completeness holds under the following conditions:
As pointed out in Footnote 1, given a rewrite theory , where are the axioms, and the equations orientable as rewrite rules, if only the equations have the variant attribute, Maude will not perform narrowing with modulo , but only modulo . This gives Maude users more flexibility, since the equations may play a useful, auxiliary role in the applications they have in mind. In particular, a concrete, practical reason for using the decomposition is that may be FVP, but may not be, so that variant -unification is finitary, whereas variant -unification will be infinitary and undecidable. Note that the standard case of narrowing with modulo is just the special case , which may be particularly attractive if is FVP. The basic intuition is that, in the most general version of narrowing supported by Maude that we now explain, we narrow with modulo , and we normalize with oriented equations modulo .
Each equation in (used for variant computation) must include the variant attribute. Note that equations in do not contain the variant attribute and do not have any restriction, i.e., they can be conditional equations, with the owise attribute, etc. Each narrowing step of the form is followed by simplification using the relation , i.e., the combined relation is defined as iff , , and is -irreducible. Note that this combined relation may be incomplete, i.e., given a reachability problem of the form and a solution (i.e., ), the relation may not be able to find a more general solution. The reason is that the equations should also be executed by narrowing instead of rewriting to ensure completeness under appropriate conditions (see [100] and Section 15.3). However, the combination of narrowing using rules, equations, and axioms with simplification using additional equations can be quite helpful to allow built-in Maude functions such as addition or multiplication, which cannot be executed by narrowing in their predefined form. It can also be useful in other applications where specific combinations of narrowing and simplification are needed.
The narrowing relation is defined on top of the order-sorted variant-based unification procedure described in Section 14.8. In order to avoid clashing of algorithms and notions, we have decided that the rules used for narrowing should be identifiable, as the equations for variant unification, and clearly distinguished from standard rules in Maude. For this purpose we have defined a new attribute for rules: the keyword narrowing.
Let mod endm be an order-sorted system module where is a set of rewrite rules specified with the rl keyword and the attribute narrowing, are the remaining rewrite rules specified with the rl or crl keywords but without the attribute narrowing, is a set of commonly occurring axioms (declared in Maude as equational attributes, see Section 4.4.1), is a set of equations specified with the eq keyword and the attribute variant such that satisfies the restrictions mentioned in Section 14.8, and are the remaining equations specified with the eq or ceq keywords but without the attribute variant. Furthermore, the rules must satisfy the following extra conditions:
cannot contain conditional rules specified with the crl keyword.
The lefthand side of a rule in cannot be a variable. If a variable is needed, one should instead specify a new kind with an extra unary symbol grabbing the whole system’s state (which would before have been matched by a single variable lefthand side). In this way, the problem of having a variable lefthand side can often be solved.
The rules in must be explicitly -coherent (see Section 5.3 for system modules and [97] for coherence details).
We recall again that the rules are disregarded for narrowing modulo , and the oriented equations are disregarded for folding variant narrowing modulo in the associated task of variant unification. However, the equations are applied for simplification after each narrowing step (see Section 15.4), as it is performed in Maude for rewriting. Note, again, that this combination of one narrowing step followed by equational simplification is not complete. A full treatment of rules, equations, and axioms for narrowing is outside the scope of the present implementation and is left for future work.
Furthermore, frozen arguments (see Section 4.4.9) are allowed for narrowing. , They are given the standard meaning of not allowing any narrowing step below such frozen arguments, just as in the context-sensitive narrowing of [82].
Finally, we do not consider any narrowing strategy at all for solving reachability problems, i.e., all positions in a term with an admissible -narrowing step are explored.
Given a system module ModId, the user can give to Maude a narrowing-based search command of the form (the prefix vu denotes that it uses variant-based unification at each step):
where
{fold} (or {vfold}) are optional and are explained in Section 15.6.3;
{path} is optional and is explained in Section 15.6.5;
{filter,delay} are optional and imply that filtered variant unification of Section 14.9 is invoked;
is an optional argument providing a bound on the number of desired solutions;
is another optional argument stating the maximum depth of the search;
the module ModId where the search takes place can be omitted;
Term-1 is the starting term, which typically will contain variables (Section 15.6.6 describes how to perform narrowing from a disjunction of initial terms instead of one single term);
Term-2 is the pattern that has to be reached, which may share variables with the starting term (note that terms in the narrowing sequence will be equationally unified with this target pattern, in contrast to the search command of Section 5.4.3 that performs only matching);
SearchArrow is an arrow indicating the form of the narrowing proof from Term-1 until Term-2:
=>1 means a narrowing proof consisting of exactly one step,
=>+ means a narrowing proof consisting of one or more steps,
=>* means a proof consisting of none, one, or more steps, and
=>! indicates that only narrowing sequences ending in terms describing sets of final states are allowed. Such terms describing sets of final states are called strongly irreducible in the sense that they cannot be further narrowed; note that this is stronger than requiring states that cannot be rewritten as in the search command of Section 5.4.3.
The one step arrow =>1 is an abbreviation of the one-or-more steps arrow =>+ with the depth bound set to 1.
Consider, for example, the following new version of the vending machine to buy apples (a) or cakes (c) with dollars ($) and/or quarters (q). The reader can check that the only difference with the VARIANT-VENDING-MACHINE module in Section 14.4 is the addition of the narrowing attribute to the rules.
mod NARROWING-VENDING-MACHINE is sorts Coin Item Marking Money State . subsort Coin < Money . op empty : -> Money . op __ : Money Money -> Money [assoc comm id: empty] . subsort Money Item < Marking . op __ : Marking Marking -> Marking [assoc comm id: empty] . op <_> : Marking -> State . ops $ q : -> Coin . ops a c : -> Item . var M : Marking . rl [buy-c] : < M $ > => < M c > [narrowing] . rl [buy-a] : < M $ > => < M a q > [narrowing] . eq [change] : q q q q M = $ M [variant] . endm
We can use the narrowing search command to answer the question:
Is there any combination of one or more coins that returns exactly an apple and a cake?
This can be done by searching for states that are reachable from a term < M:Money > and match the desired pattern at the end.
Maude> vu-narrow [1] in NARROWING-VENDING-MACHINE : < M:Money > =>* < a c > . Solution 1 state: < a c #1:Money > accumulated substitution: M:Money --> $ q q q #1:Money variant unifier: #1:Money --> empty
Maude can now show the narrowing trace associated to each solution (see Section 15.6.5 below), but it is easy to see that one application of the rule buy-a happened after (or before) one application of the rule buy-c.
The narrowing-based search returns the substitution accumulated along the narrowing sequence and the variant unifier resulting from the unification of the target term and the last expression in the narrowing sequence. Indeed both unifiers must be combined by hand in order to have an actual solution to the symbolic reachability problem, as shown for the previous example.
Note that is an ACU symbol and that such an ACU symbol appears in the equation change, disallowing the basic narrowing strategy [74] to be used for equational unification and requiring the folding variant narrowing [62] to be used for equational unification.
We have restricted the previous reachability problem to just one solution. Narrowing does not terminate for this reachability problem even though the above solution is indeed the only solution (modulo the order of the two applications of buy-c and buy-a one after the other). The problem is that narrowing follows a breadth-first exploration and does not stop until the whole narrowing tree demanded by the search command is created, even though this infinite search may not yield any further solutions. The very same problem happens for the standard search command (see Section 5.4.3). If we increase the depth of the narrowing tree, we can experimentally observe that there are no more solutions than the one shown before.
Maude> vu-narrow [,3] in NARROWING-VENDING-MACHINE : < M:Money > =>* < a c > . Solution 1 state: < a c #1:Money > accumulated substitution: M:Money --> $ q q q #1:Money variant unifier: #1:Money --> empty
The command show frontier states outputs the frontier (open leaves) of the narrowing-based reachability graph. Note that this command is not available for the search command. For example, we can type this frontier command after the previous vu-narrow command to see the leaves for depth ; unfortunately, state identifiers are not shown.
Maude> show frontier states . < c c c @1:Money > \/ < q a c c @1:Money > \/ < q a c c @1:Money > \/ < a c c #1:Money > \/ < q q a a c @1:Money > \/ < q a a c #1:Money > \/ < q a c c @1:Money > \/ < a c c #1:Money > \/ < q q a a c @1:Money > \/ < q a a c #1:Money > \/ < a c c @1:Money > \/ < q a a c @1:Money > \/ < q q a a c @1:Money > \/ < a a c #1:Money > \/ < q q q a a a @1:Money > \/ < q a a a #1:Money > \/ < q a a c @1:Money > \/ < a a c %1:Money > \/ < q q a a a @1:Money > \/ < q a a a %1:Money >
In the previous reachability problem we can change the arrow =>* for reachability in zero or more steps by the arrow =>! for reachability in zero or more steps including only states that cannot be narrowed any more.
Is there any combination of four coins that returns an apple and a cake and is such that some extra money is left but that extra money cannot be used to buy anything else?
The fact that some money may be left is characterized by including a variable of sort Money in the final state, and the fact that nothing else can be bought is characterized by using the =>! arrow instead of =>*.
Maude> vu-narrow in NARROWING-VENDING-MACHINE : < C1:Coin C2:Coin C3:Coin C4:Coin > =>! < M:Money a c > . Solution 1 state: < a c > accumulated substitution: C1:Coin --> q C2:Coin --> q C3:Coin --> q C4:Coin --> $ variant unifier: M:Money --> empty Solution 2 state: < a c > accumulated substitution: C1:Coin --> q C2:Coin --> q C3:Coin --> $ C4:Coin --> q variant unifier: M:Money --> empty Solution 3 state: < a c > accumulated substitution: C1:Coin --> q C2:Coin --> $ C3:Coin --> q C4:Coin --> q variant unifier: M:Money --> empty Solution 4 state: < a c > accumulated substitution: C1:Coin --> $ C2:Coin --> q C3:Coin --> q C4:Coin --> q variant unifier: M:Money --> empty No more solutions.
Indeed, the show frontier states command shows that there are no open leaves because the narrowing tree is terminating.
Another point of interest is the occurrence of variables of the form #n:Sort or %n:Sort , which are called fresh and are described in Chapter 13. Unification modulo axioms usually introduces fresh variables; furthermore, narrowing introduces many fresh variables because the rule applied at each narrowing step is appropriately renamed so that no variable is shared by it and the current term. Indeed, the standard solution used in logic and functional-logic programming language implementations is to use a counter along each narrowing derivation to ensure that fresh variables have never been used previously in that narrowing derivation. This method is called standardized apart [5] and it is summarized by saying that a new version of a rule, equation or axiom is always generated before being used for unification or narrowing by renaming variables according to the counter, which is incremented afterwards. This method makes the result of a computation independent of the choice of variable names.
Sections 13.4 and 14.9 provide a command for computing minimal sets of, respectively, unifiers modulo axioms or modulo a convergent equational theory. Narrowing with rules modulo an equational theory has also been extended to use a minimal set of unifiers. The filter flag indicates that filtering is activated but it can be done either during equational unification (the delay flag is not included) or after all equational unifiers have been computed (the delay flag is included); the latter being the method used in the filtered variant unify command of Section 14.9.
For instance, we can use the narrowing search command to answer the question:
Can I get an apple and a cake starting with the exact money?
The answer should be an obvious yes but the symbolic reachability question gets into too many paths.
Maude> vu-narrow [, 2] in NARROWING-VENDING-MACHINE : < $ q q q M1:Money > =>* < a c M2:Money > . Solution 1 state: < $ a c %1:Money > accumulated substitution: M1:Money --> $ %1:Money variant unifier: M2:Money --> $ @1:Money %1:Money --> @1:Money ... Solution 9 state: < $ a c #1:Money > accumulated substitution: M1:Money --> q q q q #1:Money variant unifier: M2:Money --> $ @1:Money #1:Money --> @1:Money No more solutions. rewrites: 34 in 9ms cpu (10ms real) (3664 rewrites/second)
We can ask Maude to filter unifiers at each narrowing step, and the branching is, obviously, smaller.
Maude> vu-narrow {filter} [, 2] in NARROWING-VENDING-MACHINE : < $ q q q M1:Money > =>* < a c M2:Money > . Solution 1 state: < $ a c %1:Money > accumulated substitution: M1:Money --> $ %1:Money variant unifier: M2:Money --> $ @1:Money %1:Money --> @1:Money Solution 2 state: < q a c #1:Money > accumulated substitution: M1:Money --> q #1:Money variant unifier: M2:Money --> q @1:Money #1:Money --> @1:Money Solution 3 state: < a c %1:Money > accumulated substitution: M1:Money --> %1:Money variant unifier: M2:Money --> @1:Money %1:Money --> @1:Money No more solutions.
But the first solution is actually redundant, i.e., we can ask Maude to filter unifiers only after all have been generated, which is more expensive but ensures the minimal set of most general variant unifiers.
Maude> vu-narrow {filter,delay} [, 2] in NARROWING-VENDING-MACHINE : < $ q q q M1:Money > =>* < a c M2:Money > . Solution 1 state: < q a c #1:Money > accumulated substitution: M1:Money --> q #1:Money variant unifier: M2:Money --> q @1:Money #1:Money --> @1:Money Solution 2 state: < a c %1:Money > accumulated substitution: M1:Money --> %1:Money variant unifier: M2:Money --> @1:Money %1:Money --> @1:Money No more solutions.
Note that the simplest path is the second solution, since all the necessary money was given.
The vu-narrow {filter,delay} command can achieve dramatic state space reductions over the previous vu-narrow command by filtering -unifiers. This is illustrated in this section by a simple rewrite theory describing a cryptographic protocol inspired by the strand space model [63], which is the basis for the Maude-NPA protocol analyzer [57, 58]. The protocol appeared in [54, 60]. The cryptographic function’s equational properties are those of the following exclusive-or FVP theory:
fmod EXCLUSIVE-OR is sort XOR . op mt : -> XOR . op _*_ : XOR XOR -> XOR [assoc comm] . vars X Y Z U V : [XOR] . eq [nilp] : X * X = mt [variant] . eq [nilp-Coh] : X * X * Z = Z [variant] . eq [id] : X * mt = X [variant] . endfm
The strand space syntax is given in a general form.
mod PROTOCOL-SYNTAX is sorts Name Nonce Fresh Msg . subsort Name Nonce < Msg . ops a b i : -> Name . --- Alice Bob Intruder op n : Name Fresh -> Nonce . --- Nonces ops r1 r2 r3 : -> Fresh . op pk : Name Msg -> Msg . --- Public encryption sort SMsg . ops + - : Msg -> SMsg . sort SMsgList . subsort SMsg < SMsgList . op nil : -> SMsgList . op _,_ : SMsgList SMsgList -> SMsgList [assoc] . sort Strand . op [_|_] : SMsgList SMsgList -> Strand . sort StrandSet . subsort Strand < StrandSet . op mt : -> StrandSet . op _&_ : StrandSet StrandSet -> StrandSet [assoc comm id: mt] . sort IKnowledge . op mt : -> IKnowledge . op inI : Msg -> IKnowledge . --- Intruder knows op nI : Msg -> IKnowledge . --- Intruder doesn’t know op _,_ : IKnowledge IKnowledge -> IKnowledge [assoc comm id: mt] . sort State . op {_{_}} : StrandSet IKnowledge -> State . endm
The exclusive-or protocol example is defined in the following module, where nonces are combined using exclusive-or.
mod PROTOCOL-XOR is extending EXCLUSIVE-OR . extending PROTOCOL-SYNTAX . subsort Nonce < XOR < Msg . var IK : IKnowledge . var SS : StrandSet . var M : Msg . vars L1 L2 : SMsgList . --- Intruder knows receiving message of a strand rl [recv] : { (SS & [ ( L1 , -(M)) | L2 ]) { (inI(M) , IK) } } => { (SS & [ L1 | (-(M) , L2) ]) { (inI(M) , IK) } } [narrowing] . --- Intruder learns a sending message of a strand rl [snd] : { (SS & [ (L1 , +(M)) | L2 ]) { (inI(M) , IK) } } => { (SS & [ L1 | (+(M) , L2) ]) { (nI(M) , IK) } } [narrowing] . endm
Actually, due to this general strand syntax, the protocol itself is given as a term. And, due to the fact that the rewrite rules are given in a reversed way, the initial term contains the protocol with the vertical bars on the rightmost position, denoting a finished strand, and the intruder knowledge with inI operators, denoting terms learned by the intruder, whereas the target term contains the protocol with the vertical bars on the leftmost position, denoting a non-started strand, and the intruder knowledge with nI operators, denoting terms not yet learned by the intruder. The initial term of the narrowing-based reachability command is
{ [ nil, -(pk(a, X)), +(pk(b, n(a, r2))), -(X * n(a, r2)) | nil] & [ nil, +(pk(a, n(b, r1))), -(pk(b, Y )), +(Y * n(b, r1)) | nil] { inI(X * n(a, r2)), inI(pk(b, Y )), inI(pk(a, X)) } }
{ [ nil | -(pk(a, X)), +(pk(b, n(a, r2))), -(X * n(a, r2)), nil] & [ nil | +(pk(a, n(b, r1))), -(pk(b, Y )), +(Y * n(b, r1)), nil] { nI(X * n(a, r2)), nI(pk(b, Y )), nI(pk(a, X)) } }
Note that there are variables X and Y only for the unknown parts of receiving messages of Alice and Bob. This reduces the search space considerably: it becomes a finite symbolic search space with depth , so we can focus only on the difference between filtered and unfiltered variant unification. The unfiltered symbolic reachability search command without folding is as follows:
Maude> vu-narrow { [ nil, -(pk(a, X:Msg)), +(pk(b, n(a, r2))), -(X:Msg * n(a, r2)) | nil] & [ nil, +(pk(a, n(b, r1))), -(pk(b, Y:Msg)), +(Y:Msg * n(b, r1)) | nil] { inI(X:Msg * n(a, r2)), inI(pk(b, Y:Msg)), inI(pk(a, X:Msg)) } } =>* { [ nil | -(pk(a, X:Msg)), +(pk(b, n(a, r2))), -(X:Msg * n(a, r2)), nil] & [ nil | +(pk(a, n(b, r1))), -(pk(b, Y:Msg)), +(Y:Msg * n(b, r1)), nil] { nI(X:Msg * n(a, r2)), nI(pk(b, Y:Msg)), nI(pk(a, X:Msg)) } } . Solution 1 rewrites: 26528 in 1937ms cpu (2163ms real) (13694 rewrites/second) state: {[nil | +(pk(a, n(b, r1))),-(pk(b, n(a, r2))),+(n(a, r2) * n(b, r1)),nil] & [nil | -(pk(a, n(b, r1))),+(pk(b, n(a, r2))),-(n(a, r2) * n(b, r1)),nil]{nI(n(a, r2) * n(b, r1)),nI(pk(a, n(b, r1))),nI(pk(b, n(a, r2)))}} accumulated substitution: Y:Msg --> mt * n(a, r2) X:Msg --> mt * n(b, r1) variant unifier: Solution 2 rewrites: 26533 in 1938ms cpu (2164ms real) (13689 rewrites/second) state: {[nil | +(pk(a, n(b, r1))),-(pk(b, n(a, r2))),+(n(a, r2) * n(b, r1)),nil] & [nil | -(pk(a, n(b, r1))),+(pk(b, n(a, r2))),-(n(a, r2) * n(b, r1)),nil]{nI(n(a, r2) * n(b, r1)),nI(pk(a, n(b, r1))),nI(pk(b, n(a, r2)))}} accumulated substitution: Y:Msg --> n(a, r2) * n(a, r2) * n(a, r2) X:Msg --> n(a, r2) * n(a, r2) * n(b, r1) variant unifier: ... Solution 84 rewrites: 26937 in 2015ms cpu (2243ms real) (13364 rewrites/second) state: {[nil | +(pk(a, n(b, r1))),-(pk(b, n(a, r2))),+(n(a, r2) * n(b, r1)),nil] & [nil | -(pk(a, n(b, r1))),+(pk(b, n(a, r2))),-(n(a, r2) * n(b, r1)),nil]{nI(n(a, r2) * n(b, r1)),nI(pk(a, n(b, r1))),nI(pk(b, n(a, r2)))}} accumulated substitution: Y:Msg --> n(a, r2) * n(b, r1) * n(b, r1) X:Msg --> n(a, r2) * n(a, r2) * n(b, r1) variant unifier: No more solutions.
The symbolic search space is finite: nodes, depth , and solutions (all similar to the one below).8 The filtered symbolic reachability search command without folding is as follows:
Maude> vu-narrow {filter,delay} { [ nil, -(pk(a, X:Msg)), +(pk(b, n(a, r2))), -(X:Msg * n(a, r2)) | nil] & [ nil, +(pk(a, n(b, r1))), -(pk(b, Y:Msg)), +(Y:Msg * n(b, r1)) | nil] { inI(X:Msg * n(a, r2)), inI(pk(b, Y:Msg)), inI(pk(a, X:Msg)) } } =>* { [ nil | -(pk(a, X:Msg)), +(pk(b, n(a, r2))), -(X:Msg * n(a, r2)), nil] & [ nil | +(pk(a, n(b, r1))), -(pk(b, Y:Msg)), +(Y:Msg * n(b, r1)), nil] { nI(X:Msg * n(a, r2)), nI(pk(b, Y:Msg)), nI(pk(a, X:Msg)) } } . Solution 1 rewrites: 2197 in 129ms cpu (144ms real) (16937 rewrites/second) state: {[nil | +(pk(a, n(b, r1))),-(pk(b, n(a, r2))),+(n(a, r2) * n(b, r1)),nil] & [nil | -(pk(a, n(b, r1))),+(pk(b, n(a, r2))),-(n(a, r2) * n(b, r1)),nil]{nI(n(a, r2) * n(b, r1)),nI(pk(a, n(b, r1))),nI(pk(b, n(a, r2)))}} accumulated substitution: Y:Msg --> mt * n(a, r2) X:Msg --> mt * n(b, r1) variant unifier: No more solutions.
The symbolic search space is also finite but much smaller: nodes, depth , and just one solution, with and ; see Section 15.6.4 below for even bigger state space reductions. Since exclusive-or is unitary [76], each narrowing step has only one child and, for some nodes, there are two possible narrowing steps either in Alice’s or in Bob’s strands.
We have motivated in the previous sections how narrowing can be used for symbolic reachability analysis [100] using the vu-narrow command. However, narrowing builds a reachability tree rather than a reachability graph, which is what the search command of Section 5.4.3 does for rewriting with rules. We have endowed the vu-narrow command with two different flags, either {fold} or {vfold}, that fold some branches of the narrowing reachability tree in order to create a graph, in the case of fold using only axioms and in the case of vfold using variant equations and axioms. The main difference is that the search command identifies states in the rewriting-based reachability tree that are equal modulo axioms whereas the {fold/vfold} vu-narrow command identifies states in the narrowing-based reachability tree that are equal modulo axioms and equations under instantiation9 . This is similar in nature to the folding variant narrowing strategy [62] of the variant generation command of Chapter 14 but using rules instead of equations. Indeed, the logical model checking of [59, 7] is based on earlier versions of this {fold/vfold} vu-narrow command. An abbreviation fvu-narrow is provided, which expands into the full command {fold} vu-narrow but without any other option.
Consider again the NARROWING-VENDING-MACHINE module and the following question, both in Section 15.6.
Is there any combination of one or more coins that returns exactly an apple and a cake?
We can repeat, with the same result, the vu-narrow command but now with the f at the beginning.
Maude> fvu-narrow [,3] in NARROWING-VENDING-MACHINE : < M:Money > =>* < a c > . Solution 1 state: < a c #1:Money > accumulated substitution: M:Money --> $ q q q #1:Money variant unifier: #1:Money --> empty
After executing a {fold} vu-narrow command (or fvu-narrow), the new show most general states command outputs those states that have not been folded in the generated narrowing-based reachability graph. For example, we can show the most general states after the previous fvu-narrow command to see the leaves for depth . State identifiers are not shown, but this may be changed in the future.
Maude> show most general states . < #1:Money > \/ < c @1:Money > \/ < q a @1:Money > \/ < c c %1:Money > \/ < a c #1:Money > \/ < q a a #1:Money > \/ < c c c @1:Money > \/ < a c c @1:Money > \/ < a a c %1:Money > \/ < q a a a %1:Money >
There is actually no folding here, since all the combinations for depth are shown. We can also ask for the open leaves.
Maude> show frontier states . < c c c @1:Money > \/ < a c c @1:Money > \/ < a a c %1:Money > \/ < q a a a %1:Money >
Note that, if folding is used, the frontier will always be a subset of the most general states, since the folded states are no longer explored by narrowing.
Moreover, producing a reachability graph instead of a reachability tree may improve the chances of having a finite search space. If we repeat the same command but unrestricted, Maude reports the unique solution but gets into a loop, since the fvu-narrow command cannot produce a finite search graph. However, the reason is that we are giving a logical variable of sort Money and any new state containing an apple or a cake cannot be an instance of the initial state. Therefore, we can use a variable of a bigger sort Marking instead of Money and the infinite narrowing-based reachability tree is folded into a finite narrowing-based reachability graph.
Maude> fvu-narrow in NARROWING-VENDING-MACHINE : < M:Marking > =>* < a c > . Solution 1 state: < #1:Marking > accumulated substitution: M:Marking --> #1:Marking variant unifier: #1:Marking --> a c No more solutions.
But this is not the expected solution, since it is simply instantiating variable M:Marking to the apple and the cake. The graph is finite, as we wanted, but there is only one state, the initial one, and every other state is folded into it, since they are all obviously instances of it.
What is actually happening is that this specification of the vending machine is not well suited for taking advantage of this folding narrowing technique, since folding implies that a new state is an instance of a previous one and a rewrite rule where the right-hand side removes information from the left-hand side instead of adding more information may help to fold. Consider the following new version of the vending machine. The reader can check that the only difference with the NARROWING-VENDING-MACHINE module in Section 15.6 is that apples and cakes are not deposited in the bag but consumed. This is typical of a logic programming style (in the sense of Prolog), where computed answers are used because there are no normal forms.
mod FOLDING-NARROWING-VENDING-MACHINE is sorts Coin Item Marking Money State . subsort Coin < Money . op empty : -> Money . op __ : Money Money -> Money [assoc comm id: empty] . subsort Money Item < Marking . op __ : Marking Marking -> Marking [assoc comm id: empty] . op <_> : Marking -> State . ops $ q : -> Coin . ops a c : -> Item . var M : Marking . rl [buy-c] : < M $ c > => < M > [narrowing] . rl [buy-a] : < M $ a > => < M q > [narrowing] . eq [change] : q q q q M = $ M [variant] . endm
We can repeat the same command and now we get the expected result, while having a finite narrowing-based reachability graph.
Maude> fvu-narrow in FOLDING-NARROWING-VENDING-MACHINE : < M:Marking c c > =>* < empty > . Solution 1 state: < %1:Marking > accumulated substitution: M:Marking --> $ $ %1:Marking variant unifier: %1:Marking --> empty No more solutions.
The graph is finite, as we wanted, since, after two narrowing steps, a state of the form < #1:Marking > is generated and, therefore, any other further state is folded into it. Note that a similar vu-narrow command does not stop. Note also that if we use a variable of sort Money, then the narrowing-based reachability tree is finite, since no more coins can be introduced, and both the vu-narrow and fvu-narrow commands stop.
Maude> vu-narrow in FOLDING-NARROWING-VENDING-MACHINE : < M:Money c c > =>* < empty > . Solution 1 state: < %1:Money > accumulated substitution: M:Money --> $ $ %1:Money variant unifier: %1:Money --> empty No more solutions.
The difference between using the {fold} vu-narrow command and the {vfold} vu-narrow command may not seem relevant, since variant equations are used also for simplification. Even more, the combination with {filter,delay} may also seem not relevant, but combining all may have a huge impact.
For example, let us consider the system module of Section 15.6.2 describing a cryptographic protocol inspired by the strand space model. On that system module we executed the following command (see page 475) that we are going to be adapting with different options10 .
Maude> vu-narrow { [ nil, -(pk(a, X:Msg)), +(pk(b, n(a, r2))), -(X:Msg * n(a, r2)) | nil] & [ nil, +(pk(a, n(b, r1))), -(pk(b, Y:Msg)), +(Y:Msg * n(b, r1)) | nil] { inI(X:Msg * n(a, r2)), inI(pk(b, Y:Msg)), inI(pk(a, X:Msg)) } } =>* { [ nil | -(pk(a, X:Msg)), +(pk(b, n(a, r2))), -(X:Msg * n(a, r2)), nil] & [ nil | +(pk(a, n(b, r1))), -(pk(b, Y:Msg)), +(Y:Msg * n(b, r1)), nil] { nI(X:Msg * n(a, r2)), nI(pk(b, Y:Msg)), nI(pk(a, X:Msg)) } } .
vu-narrow has 916 states and 84 solutions in the reachability tree. It made 26937 steps in 1322ms.vu-narrow {filter,delay} has 20 states and 1 solution in the reachability tree, since a minimal set of most general unifiers is computed for each narrowing step and we know that there is always only one most general unifier for the exclusive-or theory. It made 1899 steps in 85ms.{fold} vu-narrow has 197 states and 1 solution in the reachability graph, since all the 84 solutions of the first command are folded into only one of them. It made 4908 steps in 268ms.{fold} vu-narrow {filter,delay} has 19 states and 1 solution in the reachability graph, since the computation of the minimal set of most general unifiers already reduces the search space. It made 1892 steps in 84ms.{vfold} vu-narrow has 68 states and 1 solution in the reachability graph, since folding using the full exclusive-or theory instead of only the axioms is more powerful and folds many more states. It made 1633 steps in 88ms.{vfold} vu-narrow {filter,delay} has 19 states and 1 solution in the reachability graph, since all the improvements are combined. It made 1892 steps in 96ms.Clearly, the best option is to compute a minimal set of most general unifiers for each narrowing step. However, we can see a difference between folding with the whole equational theory or only with the axioms.
Folding does not seem to be useful because the protocol example is terminating for narrowing, but this is not actually true in general. Let us add an idle transition rule to the system, generating a self-loop for every state, as in stuttering simulations.
mod PROTOCOL-XOR-STUTTERING is extending EXCLUSIVE-OR . extending PROTOCOL-SYNTAX . subsort Nonce < XOR < Msg . var IK : IKnowledge . var SS : StrandSet . var M : Msg . vars L1 L2 : SMsgList . --- Intruder knows receiving message of a strand rl [recv] : { (SS & [ ( L1 , -(M)) | L2 ]) { (inI(M) , IK) } } => { (SS & [ L1 | (-(M) , L2) ]) { (inI(M) , IK) } } [narrowing] . --- Intruder learns a sending message of a strand rl [snd] : { (SS & [ (L1 , +(M)) | L2 ]) { (inI(M) , IK) } } => { (SS & [ L1 | (+(M) , L2) ]) { (nI(M) , IK) } } [narrowing] . --- Stuttering action rl [idle] : { SS { IK } } => { SS { IK } } [narrowing] . endm
Now folding becomes very useful.
vu-narrow is not able to report any solution and a depth bound does not help, since the number of useless paths is overwhelming.vu-narrow {filter,delay} is able to report solutions but there is an infinite number of them due to self-loops.{fold} vu-narrow has 686 states and 1 solution in the reachability graph. It made 7557 steps in 465ms.{fold} vu-narrow {filter,delay} has 35 states and 1 solution in the reachability graph. It made 2664 steps in 126ms.{vfold} vu-narrow has 148 states and 1 solution in the reachability graph. It made 2091 steps in 119ms.{vfold} vu-narrow {filter,delay} has 35 states and 1 solution in the reachability graph. It made 2664 steps in 134ms.In conclusion, combining variant-based folding with the computation of a minimal set of most general unifiers is extremely useful.
It is now possible to obtain the narrowing trace to a specific solution in a narrowing-based reachability problem, as it happens with the search command of Section 5.4.3. However, this is an expensive feature and the option {path} must be given at the beginning of the vu-narrow command in order to store the appropriate information. When this option is given, the two standard path commands can be used
For example, let us consider again the simple rewrite theory of Section 15.6.2 describing a cryptographic protocol inspired by the strand space model. First, we add the {path} option at the beginning of the last command of Section 15.6.4.
Maude> {vfold,path} vu-narrow {filter,delay} { [ nil, -(pk(a, X:Msg)), +(pk(b, n(a, r2))), -(X:Msg * n(a, r2)) | nil] & [ nil, +(pk(a, n(b, r1))), -(pk(b, Y:Msg)), +(Y:Msg * n(b, r1)) | nil] { inI(X:Msg * n(a, r2)), inI(pk(b, Y:Msg)), inI(pk(a, X:Msg)) } } =>* { [ nil | -(pk(a, X:Msg)), +(pk(b, n(a, r2))), -(X:Msg * n(a, r2)), nil] & [ nil | +(pk(a, n(b, r1))), -(pk(b, Y:Msg)), +(Y:Msg * n(b, r1)), nil] { nI(X:Msg * n(a, r2)), nI(pk(b, Y:Msg)), nI(pk(a, X:Msg)) } } .
Maude> show path 32 . state 0, [State]: {[nil, +(pk(a, n(b, r1))), -(pk(b, #1:Msg)), +(#1:Msg * n(b, r1)) | nil] & [nil, -(pk(a, #2:Msg)), +(pk(b, n(a, r2))), -(#2:Msg * n(a, r2)) | nil] {inI(#2:Msg * n(a, r2)), inI(pk(a, #2:Msg)), inI(pk(b, #1:Msg))}} accumulated substitution: Y:Msg --> #1:Msg X:Msg --> #2:Msg ===[ rl [recv] : {SS:StrandSet & [L1:SMsgList, -(M:Msg) | L2:SMsgList]{IK:IKnowledge, inI(M:Msg)}} => {SS:StrandSet & [L1:SMsgList | -(M:Msg), L2:SMsgList]{IK:IKnowledge, inI(M:Msg)}} [narrowing] . ]===> variant unifier: SS:StrandSet --> [nil, +(pk(a, n(b, r1))), -(pk(b, @1:XOR)), +(@1:XOR * n(b, r1)) | nil] L1:SMsgList --> nil, -(pk(a, @2:XOR)), +(pk(b, n(a, r2))) M:Msg --> @2:XOR * n(a, r2) L2:SMsgList --> nil IK:IKnowledge --> inI(pk(a, @2:XOR)), inI(pk(b, @1:XOR)) #1:Msg --> @1:XOR #2:Msg --> @2:XOR state 1, State: {[nil, -(pk(a, @2:XOR)), +(pk(b, n(a, r2))) | -(@2:XOR * n(a, r2)), nil] & [nil, +(pk(a, n(b, r1))), -(pk(b, @1:XOR)), +(@1:XOR * n(b, r1)) | nil] {inI(@2:XOR * n(a, r2)), inI(pk(a, @2:XOR)), inI(pk(b, @1:XOR))}} accumulated substitution: Y:Msg --> @1:XOR X:Msg --> @2:XOR ===[ rl [snd] : {SS:StrandSet & [L1:SMsgList, +(M:Msg) | L2:SMsgList]{IK:IKnowledge, inI(M:Msg)}} => {SS:StrandSet & [L1:SMsgList | +(M:Msg), L2:SMsgList]{IK:IKnowledge, nI(M:Msg)}} [narrowing] . ]===> variant unifier: SS:StrandSet --> [nil, -(pk(a, %1:XOR * n(b, r1))), +(pk(b, n(a, r2))) | -(%1:XOR * n(a, r2) * n(b, r1)), nil] L1:SMsgList --> nil, +(pk(a, n(b, r1))), -(pk(b, %1:XOR * n(a, r2))) M:Msg --> %1:XOR * n(a, r2) * n(b, r1) L2:SMsgList --> nil IK:IKnowledge --> inI(pk(a, %1:XOR * n(b, r1))), inI(pk(b, %1:XOR * n(a, r2))) @2:XOR --> %1:XOR * n(b, r1) @1:XOR --> %1:XOR * n(a, r2) state 5, State: {[nil, +(pk(a, n(b, r1))), -(pk(b, %1:XOR * n(a, r2))) | +(%1:XOR * n(a, r2) * n(b, r1)), nil] & [nil, -(pk(a, %1:XOR * n(b, r1))), +(pk(b, n(a, r2))) | -(%1:XOR * n(a, r2) * n(b, r1)), nil] {inI(pk(a, %1:XOR * n(b, r1))), inI(pk(b, %1:XOR * n(a, r2))), nI(%1:XOR * n(a, r2) * n(b, r1))}} accumulated substitution: Y:Msg --> %1:XOR * n(a, r2) X:Msg --> %1:XOR * n(b, r1) ===[ rl [recv] : {SS:StrandSet & [L1:SMsgList, -(M:Msg) | L2:SMsgList]{IK:IKnowledge, inI(M:Msg)}} => {SS:StrandSet & [L1:SMsgList | -(M:Msg), L2:SMsgList]{IK:IKnowledge, inI(M:Msg)}} [narrowing] . ]===> variant unifier: SS:StrandSet --> [nil, -(pk(a, @1:XOR * n(b, r1))), +(pk(b, n(a, r2))) | -(@1:XOR * n(a, r2) * n(b, r1)), nil] L1:SMsgList --> nil, +(pk(a, n(b, r1))) M:Msg --> pk(b, @1:XOR * n(a, r2)) L2:SMsgList --> +(@1:XOR * n(a, r2) * n(b, r1)), nil IK:IKnowledge --> inI(pk(a, @1:XOR * n(b, r1))), nI(@1:XOR * n(a, r2) * n(b, r1)) %1:XOR --> @1:XOR state 12, State: {[nil, +(pk(a, n(b, r1))) | -(pk(b, @1:XOR * n(a, r2))), +(@1:XOR * n(a, r2) * n(b, r1)), nil] & [nil, -(pk(a, @1:XOR * n(b, r1))), +(pk(b, n(a, r2))) | -(@1:XOR * n(a, r2) * n(b, r1)), nil] {inI(pk(a, @1:XOR * n(b, r1))), inI(pk(b, @1:XOR * n(a, r2))), nI(@1:XOR * n(a, r2) * n(b, r1))}} accumulated substitution: Y:Msg --> @1:XOR * n(a, r2) X:Msg --> @1:XOR * n(b, r1) ===[ rl [snd] : {SS:StrandSet & [L1:SMsgList, +(M:Msg) | L2:SMsgList]{IK:IKnowledge, inI(M:Msg)}} => {SS:StrandSet & [L1:SMsgList | +(M:Msg), L2:SMsgList]{IK:IKnowledge, nI(M:Msg)}} [narrowing] . ]===> variant unifier: SS:StrandSet --> [nil, +(pk(a, n(b, r1))) | -(pk(b, n(a, r2))), +(n(a, r2) * n(b, r1)), nil] L1:SMsgList --> nil, -(pk(a, n(b, r1))) M:Msg --> pk(b, n(a, r2)) L2:SMsgList --> -(n(a, r2) * n(b, r1)), nil IK:IKnowledge --> inI(pk(a, n(b, r1))), nI(n(a, r2) * n(b, r1)) @1:XOR --> (mt).XOR state 22, State: {[nil, +(pk(a, n(b, r1))) | -(pk(b, n(a, r2))), +(n(a, r2) * n(b, r1)), nil] & [nil, -(pk(a, n(b, r1))) | +(pk(b, n(a, r2))), -(n(a, r2) * n(b, r1)), nil] {inI(pk(a, n(b, r1))), nI(n(a, r2) * n(b, r1)), nI(pk(b, n(a, r2)))}} accumulated substitution: Y:Msg --> mt * n(a, r2) X:Msg --> mt * n(b, r1) ===[ rl [recv] : {SS:StrandSet & [L1:SMsgList, -(M:Msg) | L2:SMsgList]{IK:IKnowledge, inI(M:Msg)}} => {SS:StrandSet & [L1:SMsgList | -(M:Msg), L2:SMsgList]{IK:IKnowledge, inI(M:Msg)}} [narrowing] . ]===> variant unifier: SS:StrandSet --> [nil, +(pk(a, n(b, r1))) | -(pk(b, n(a, r2))), +(n(a, r2) * n(b, r1)), nil] L1:SMsgList --> nil M:Msg --> pk(a, n(b, r1)) L2:SMsgList --> +(pk(b, n(a, r2))), -(n(a, r2) * n(b, r1)), nil IK:IKnowledge --> nI(n(a, r2) * n(b, r1)), nI(pk(b, n(a, r2))) state 28, State: {[nil | -(pk(a, n(b, r1))), +(pk(b, n(a, r2))), -(n(a, r2) * n(b, r1)), nil] & [nil, +(pk(a, n(b, r1))) | -(pk(b, n(a, r2))), +(n(a, r2) * n(b, r1)), nil] {inI(pk(a, n(b, r1))), nI(n(a, r2) * n(b, r1)), nI(pk(b, n(a, r2)))}} accumulated substitution: Y:Msg --> mt * n(a, r2) X:Msg --> mt * n(b, r1) ===[ rl [snd] : {SS:StrandSet & [L1:SMsgList, +(M:Msg) | L2:SMsgList]{IK:IKnowledge, inI(M:Msg)}} => {SS:StrandSet & [L1:SMsgList | +(M:Msg), L2:SMsgList]{IK:IKnowledge, nI(M:Msg)}} [narrowing] . ]===> variant unifier: SS:StrandSet --> [nil | -(pk(a, n(b, r1))), +(pk(b, n(a, r2))), -(n(a, r2) * n(b, r1)), nil] L1:SMsgList --> nil M:Msg --> pk(a, n(b, r1)) L2:SMsgList --> -(pk(b, n(a, r2))), +(n(a, r2) * n(b, r1)), nil IK:IKnowledge --> nI(n(a, r2) * n(b, r1)), nI(pk(b, n(a, r2))) state 32, State: {[nil | +(pk(a, n(b, r1))), -(pk(b, n(a, r2))), +(n(a, r2) * n(b, r1)), nil] & [nil | -(pk(a, n(b, r1))), +(pk(b, n(a, r2))), -(n(a, r2) * n(b, r1)), nil] {nI(n(a, r2) * n(b, r1)), nI(pk(a, n(b, r1))), nI(pk(b, n(a, r2)))}} accumulated substitution: Y:Msg --> mt * n(a, r2) X:Msg --> mt * n(b, r1)
Similarly to the search command, we can show the sequence of states and the label of each transition, not showing the complete rule statement.
Maude> show path states 32 . state 0, [State]: {[nil, +(pk(a, n(b, r1))), -(pk(b, #1:Msg)), +(#1:Msg * n(b, r1)) | nil] & [nil, -(pk(a, #2:Msg)), +(pk(b, n(a, r2))), -(#2:Msg * n(a, r2)) | nil] {inI(#2:Msg * n(a, r2)), inI(pk(a, #2:Msg)), inI(pk(b, #1:Msg))}} accumulated substitution: Y:Msg --> #1:Msg X:Msg --> #2:Msg --- recv ---> state 1, State: {[nil, -(pk(a, @2:XOR)), +(pk(b, n(a, r2))) | -(@2:XOR * n(a, r2)), nil] & [nil, +(pk(a, n(b, r1))), -(pk(b, @1:XOR)), +(@1:XOR * n(b, r1)) | nil] {inI(@2:XOR * n(a, r2)), inI(pk(a, @2:XOR)), inI(pk(b, @1:XOR))}} accumulated substitution: Y:Msg --> @1:XOR X:Msg --> @2:XOR --- snd ---> state 5, State: {[nil, +(pk(a, n(b, r1))), -(pk(b, %1:XOR * n(a, r2))) | +(%1:XOR * n(a, r2) * n(b, r1)), nil] & [nil, -(pk(a, %1:XOR * n(b, r1))), +(pk(b, n(a, r2))) | -(%1:XOR * n(a, r2) * n(b, r1)), nil] {inI(pk(a, %1:XOR * n(b, r1))), inI(pk(b, %1:XOR * n(a, r2))), nI(%1:XOR * n(a, r2) * n(b, r1))}} accumulated substitution: Y:Msg --> %1:XOR * n(a, r2) X:Msg --> %1:XOR * n(b, r1) --- recv ---> state 12, State: {[nil, +(pk(a, n(b, r1))) | -(pk(b, @1:XOR * n(a, r2))), +(@1:XOR * n(a, r2) * n(b, r1)), nil] & [nil, -(pk(a, @1:XOR * n(b, r1))), +(pk(b, n(a, r2))) | -(@1:XOR * n(a, r2) * n(b, r1)), nil] {inI(pk(a, @1:XOR * n(b, r1))), inI(pk(b, @1:XOR * n(a, r2))), nI(@1:XOR * n(a, r2) * n(b, r1))}} accumulated substitution: Y:Msg --> @1:XOR * n(a, r2) X:Msg --> @1:XOR * n(b, r1) --- snd ---> state 22, State: {[nil, +(pk(a, n(b, r1))) | -(pk(b, n(a, r2))), +(n(a, r2) * n(b, r1)), nil] & [nil, -(pk(a, n(b, r1))) | +(pk(b, n(a, r2))), -(n(a, r2) * n(b, r1)), nil] {inI(pk(a, n(b, r1))), nI(n(a, r2) * n(b, r1)), nI(pk(b, n(a, r2)))}} accumulated substitution: Y:Msg --> mt * n(a, r2) X:Msg --> mt * n(b, r1) --- recv ---> state 28, State: {[nil | -(pk(a, n(b, r1))), +(pk(b, n(a, r2))), -(n(a, r2) * n(b, r1)), nil] & [nil, +(pk(a, n(b, r1))) | -(pk(b, n(a, r2))), +(n(a, r2) * n(b, r1)), nil] {inI(pk(a, n(b, r1))), nI(n(a, r2) * n(b, r1)), nI(pk(b, n(a, r2)))}} accumulated substitution: Y:Msg --> mt * n(a, r2) X:Msg --> mt * n(b, r1) --- snd ---> state 32, State: {[nil | +(pk(a, n(b, r1))), -(pk(b, n(a, r2))), +(n(a, r2) * n(b, r1)), nil] & [nil | -(pk(a, n(b, r1))), +(pk(b, n(a, r2))), -(n(a, r2) * n(b, r1)), nil] {nI(n(a, r2) * n(b, r1)), nI(pk(a, n(b, r1))), nI(pk(b, n(a, r2)))}} accumulated substitution: Y:Msg --> mt * n(a, r2) X:Msg --> mt * n(b, r1)
The narrowing-based reachability command has been extended so that now an initial state can be a disjunction of state terms instead of just a single stat term. Essentially, this provides us with a forest of narrowing-based reachability trees instead of a single reachability tree. This resembles an or-parallelism approach typical of logic programming (in the sense of Prolog) with the difference that we assume the initial terms to have disjoint variables; this may be modified in the future.
Given a system module ModId, the user can give to Maude a narrowing-based search command of the following form slightly different from the command in page 469:
{fold,path} vu-narrow {filter,delay} [ , ] in ModId : Term-1 \/ \ldots \/ Term-n SearchArrow Term’ .
where the differences with the previous command are
Term-1,…,Term-n are the starting terms, which typically will contain variables but they do not share any variable;
Term’ is the pattern that has to be reached but, despite of the original command, cannot share any variable with the starting terms.
For example, given the FOLDING-NARROWING-VENDING-MACHINE rewrite theory above, we can ask whether it is possible to consume any two combinations of cookies and coffees in the same command
{fold} vu-narrow in FOLDING-NARROWING-VENDING-MACHINE : < M1:Marking a c > \/ < M2:Marking a a > \/ < M3:Marking c c > =>* < empty > . Solution 1 rewrites: 9 in 1ms cpu (1ms real) (7240 rewrites/second) state: < %1:Marking > initial state: < M3:Marking c c > accumulated substitution: M3:Marking --> $ $ %1:Marking variant unifier: %1:Marking --> empty
Indeed, there is only one solution, since any of the three combinations requires two dollars. Note that, when a disjunction of initial states is given, the information reported for each reachability solution includes the initial state from which the state that unified with the pattern was narrowed.
Note that, when the fold option is given, states from one tree may subsume states from another tree. Indeed, one initial state may subsume another. For instance, we can see that there is only one most general state and no open leaves.
Maude> show frontier states . *** frontier is empty *** Maude> show most general states . < %1:Marking >
There is no equivalent command at the meta level for this feature. This may change in the future.
Although rewriting does not allow extra variables in the righthand side of rules11 , extra variables in righthand sides pose no problem for narrowing. Since rules having extra variables in the righthand side are not allowed in Maude for rewriting purposes, the attribute nonexec (see Section 4.5.3) must be added to such rules if one wants to use them for narrowing. The nonexec attribute is not taken into account by narrowing: all unconditional rules with the narrowing attribute, regardless of whether or not they include the nonexec attribute, are used by narrowing. Extra variables in the righthand side are a common feature of programs using narrowing as the operational evaluation mechanism, as in logic programming or functional-logic programming [70]. For further details on how to write funcional-logic programs in Maude using symbolic reachability, see [55, 56]. Let us motivate this feature with an extended version of the vending machine which is able to add as many coins as we want.
mod FOLDING-NARROWING-VENDING-MACHINE-EXTRAVAR is sorts Coin Item Marking Money State . subsort Coin < Money . op empty : -> Money . op __ : Money Money -> Money [assoc comm id: empty] . subsort Money Item < Marking . op __ : Marking Marking -> Marking [assoc comm id: empty] . op <_> : Marking -> State . ops $ q : -> Coin . ops a c : -> Item . var M : Marking . var Mo : Money . rl [buy-c] : < M $ c > => < M > [narrowing] . rl [buy-a] : < M $ a > => < M q > [narrowing] . rl [add] : < M > => < M Mo > [nonexec narrowing] . eq [change] : q q q q M = $ M [variant] . endm
This system module is clearly non-terminating by narrowing, since now the extra variable Mo allows any additional coins.
Maude> vu-narrow [, 2] in FOLDING-NARROWING-VENDING-MACHINE-EXTRAVAR : < empty > =>* < M > . Solution 1 state: < empty > accumulated substitution: variant unifier: M --> empty Solution 2 state: < @1:Money > accumulated substitution: variant unifier: M --> %1:Money @1:Money --> %1:Money Solution 3 state: < %1:Money %2:Money > accumulated substitution: variant unifier: M --> @1:Money @2:Money %1:Money --> @1:Money %2:Money --> @2:Money Solution 4 state: < %1:Money %2:Money > accumulated substitution: variant unifier: M --> $ #1:Money #2:Money %1:Money --> q q q #1:Money %2:Money --> q #2:Money Solution 5 state: < %1:Money %2:Money > accumulated substitution: variant unifier: M --> $ #1:Money #2:Money %1:Money --> q q #1:Money %2:Money --> q q #2:Money Solution 6 state: < %1:Money %2:Money > accumulated substitution: variant unifier: M --> $ #1:Money #2:Money %1:Money --> q #1:Money %2:Money --> q q q #2:Money
Note that this symbolic reachability problem is not executable by rewriting, even if we start from a ground state as in the previous command, but it’s executable by narrowing without any problem.
Indeed, the symbolic reachability problem from the ground state has a finite reachability graph computed by folding.