Intuition for Simplicity's CheckSigHashAll So, I downloaded Simplicity and started a REPL using cabal new-repl Simplicity. Then I enabled type applications using :set -XTypeApplications.

Consider these invocations:

&gt; (pkwCheckSigHashAll @CommitmentRoot @() lib (Schnorr.PubKey True (read "0")) (Schnorr.Sig (read "0") (read "1")))
CommitmentRoot {commitmentRoot = Hash256 {hash256 = 
"\CAN\r\201\231\SO0:1\183\130a\DC3m4u!\193\247e\EOT\194nO\US\208\150\&amp;2\182\ACK\203\151&gt;"}}

&gt; (pkwCheckSigHashAll @CommitmentRoot @() lib (Schnorr.PubKey True (read "1")) (Schnorr.Sig (read "0") (read "1")))
CommitmentRoot {commitmentRoot = Hash256 {hash256 = 
"\215\222oj\251&amp;\134\SOZ\202@N\161(\185j2\DELt\156\147\136Nz\183\179\EOTH\166\FS\141F"}}

&gt; (pkwCheckSigHashAll @CommitmentRoot @() lib (Schnorr.PubKey True (read "1")) (Schnorr.Sig (read "0") (read "0")))
CommitmentRoot {commitmentRoot = Hash256 {hash256 = 
"\215\222oj\251&amp;\134\SOZ\202@N\161(\185j2\DELt\156\147\136Nz\183\179\EOTH\166\FS\141F"}}


If I understand correctly, the commitment root goes into an transaction output. Since the signature shouldn't play a role, the output from Haskell makes sense since I change the sig and it doesn't affect the commitment root. The example above seems consistent with this intuition. Are both my intuition and the example code correct?

Now, I can call the same method with WitnessRoot:

&gt; (pkwCheckSigHashAll @WitnessRoot @() lib (Schnorr.PubKey True (read "0")) (Schnorr.Sig (read "0") (read "0")))
WitnessRoot {witnessRoot = Hash256 {hash256 = 
"\185\FS\176\179\b{Xc2\216\n\240\186\205v\208\164NW\\\DLE\193:\ETB\bMO\211\152*I%"}}

&gt; (pkwCheckSigHashAll @WitnessRoot @() lib (Schnorr.PubKey True (read "1")) (Schnorr.Sig (read "0") (read "0")))
WitnessRoot {witnessRoot = Hash256 {hash256 = 
"\240h\NUL\188z4\ACK\200\ETX\151\DC1&amp;Y\253\t\152\176P\146\186\137\NAKm!\STX\DC3\182\148\193\246\172O"}}

&gt; (pkwCheckSigHashAll @WitnessRoot @() lib (Schnorr.PubKey True (read "0")) (Schnorr.Sig (read "0") (read "1")))
WitnessRoot {witnessRoot = Hash256 {hash256 = 
"\205\DEL\132Q\245\166!\196\178\248\136\194aO\243+\145T\200E\129I#\253F\134\173i\243K\154J"}}


Again, if I understand correctly, the witness root goes into the spending transaction input. It seems like the witness root is affected if I change either signature or public key, which corresponds to my intuition. Simplicity uses BIP-Schnorr which doesn't allow for pubkey recovery. Again, is my intuition consistent with the above code?

I assume these hash values contain mock transaction metadata like nLocktime, embedded in the lib, correct?

Now, let's say I wanted to do a 2-of-2 MAST-based multisig with Simplicity, how would that look like? There must be a way to compose two different calls to CheckSigHashAll. And the spending transaction would surely need to provide a MAST path through the script, how do I pass that to Simplicity? With oooh?
Your understanding of the commitment root is correct.  Taking advantage of Haskell's laziness, feel free to put undefined into the signature when computing the commitment root.

At the moment, the witness root is not used in the protocol.  There are some ideas on how to use it in more sophisticated protocols, but those ideas haven't been realized yet.  However, the witness root has some uses in testing and development regardless.

Simplicity doesn't use BIP-Schnorr per se.  Rather checkSigHashAll is a Simplicity expression that implements an old draft of BIP-Schnorr.  If you want a signature scheme that supports key recovery, you could implement such a scheme in Simplicity.

Neither the commitment nor witness roots contain any transaction data, mock or otherwise.  Instead the checkSigHashAll Simplicity expression contains primitives that, during Simplicity execution, return the transaction's locktime and version

To make a simple 2-of-2 multisig you could write something along the lines of 

 pkwCheckSigHashAll &lt;pubkeyA&gt; &lt;sigA&gt; &amp;&amp;&amp;
 pkwCheckSigHashAll &lt;pubkeyB&gt; &lt;sigB&gt; &gt;&gt;&gt;
unit


Since both branches are used in a 2-of-2 multisig, there are no MAST branches.

To make a 1-of-2 multisig you have some options.  One way is

 witness &lt;value of type (Bit, Sig)&gt; &gt;&gt;&gt;
 cond (scribe &lt;pubkeyA&gt; &amp;&amp;&amp; iden) (scribe &lt;pubkeyB&gt; &amp;&amp;&amp; iden) &gt;&gt;&gt;
 checkSigHashAll


However, even after pruning (turning the case within the cond into an assert), the program appear a lot like a 1-of-2 multisig.

Another implementation could be

witness &lt;value of type Bit&gt; &amp;&amp;&amp; unit &gt;&gt;&gt;
cond (pkwCheckSigHashAll &lt;pubkeyA&gt; &lt;sigA&gt;)
     (pkwCheckSigHashAll &lt;pubkeyB&gt; &lt;sigB&gt;)


In this case after pruning it looks like a 1-sig-or-something else.  Pruning transforms the above expression into something like

witness &lt;0&gt; &amp;&amp;&amp; unit &gt;&gt;&gt;
assertl (drop (pkwCheckSigHashAll &lt;pubkeyB&gt; &lt;sigB&gt;))
        (commitmentRoot (drop (pkwCheckSigHashAll &lt;pubkeyA&gt; &lt;sigA&gt;)))


where only the commitment root of the untaken branch is revealed.

Be aware that as of the time of writing this, pruning has not been implemented, nor is it the case that pruned expressions are yet enforced by the implementation.  You can still prune expressions by hand though.