Hi Johan,
Thanks for your message.
I think games where all the possible futures can be enumerated are
not ideal to showcase MATT, as one could just fully represent them
with just CTV or COCV, and not use the "data embedding" at all.
Perhaps rock-paper-scissors could be a better academic example. [1]
I'm not sure this will fully address your question; however I think
it's quite an instructive example, and I wanted to work it out for
quite some time.
It would be interesting to explore some contracts where the size
of the embedded data is substantially larger, and that could be
a natural next step to think about.
### Rock paper scissors
We want a protocol between Alice and Bob, where they bet 1 coin each:
1. Alice chooses and publishes her move;
2. Bob chooses his move, and the pot is adjudicated as per the rules.
Of course, if implemented naively, this wouldn't be a very fun game:
Bob would just wait to see Alice's move and play accordingly.
That's easy to fix, though:
1. Alice publishes a commitment to her move
2. Bob publishes his move in clear
3. Alice reveals her move, and the pot is adjudicated.
We can encode Rock = 0, Paper = 1, Scissors = 2. Let m_A, m_B be
Alice's and Bob's move, respectively. Then, it's easy to verify that:
− m_B - m_A == 0 (mod 3) ==> it's a tie
− m_B - m_A == 1 (mod 3) ==> Bob wins
− m_B - m_A == 2 (mod 3) ==> Alice wins
In order to create a hiding commitment for Alice, she can choose a
256-bit random number r_A, and compute:
c_A = SHA256(m_A || r_A)
With that in mind, the full protocol can go like this:
1. Alice chooses her move m_A and a large random number r_A;
she posts c_A computed as above;
2. Bob chooses m_B and publishes it;
3. Alice publishes m_A and r_A, then the winner is adjudicated.
### MATT playing RPS
To implement this with CICV/COCV, we can use just 3 transactions: in
fact, Alice can already compute c_A and share it with Bob before they
both commit their coins into an encumbered UTXO. That also means that
c_A can actually be hardcoded in the Scripts, rather than taking
space in the UTXO's embedded data.
Therefore, they both put one coin each, and they send to an output
whose script is the state S0 described below.
We assume that the keypath in the P2TR defined below is either a NUMS
point, or perhaps a Musig2 aggregate key that can be used to settle
the game collaboratively.
Note that there are 3 possible payout options that are fully known
when the game starts: either Alice takes all the money, or they split
evenly, or Bob takes all the money.
Similarly to the vault implementation [2], this seems to be another
case where CTV fits very well, as it allows to very efficiently
describe the three possible outcomes by their CTV hashes. Let them
be <ctv-alice-wins>, <ctv-split>, <ctv-bob-wins>, respectively.
Therefore, this avoids the need for 64-bit maths, and explicit amount
introspection − at least for these contracts.
[State S0] (Start of the game, Alice moved; Bob's turn)
Spending conditions:
- after <forfait-delay>, Alice takes the money // (Bob forfaits)
- Bob posts m_B (0, 1 or 2); the next output is [S1] with data m_B
The first script is:
// witness: []
<forfait-delay>
OP_CHECKSEQUENCEVERIFY
OP_DROP
<ctv-alice-wins>
OP_CHECKTEMPLATEVERIFY
The second is
// witness: [<bob_sig> <m_B>]
OP_DUP 0 3 OP_WITHIN // check that m_B is 0, 1 or 2
<internal_pubkey> OP_SWAP
<S1's taptree>
OP_CHECKOUTPUTCONTRACTVERIFY // check that the output is correct
<bob_pubkey>
OP_CHECKSIG
[State S1] (Alice reveals m_A and adjudicates)
- after <forfait-timeout>, Bob takes the money // (Alice forfaits)
- Alice posts correct m_A and r_A compatible with c_A;
The first script is symmetric to Bob's forfait script above.
The second condition can be split into three leaf scripts, one for
each possible value of m_B - m_A (mod 3):
// witness: [<m_B> <m_A> <r_A>]
OP_OVER OP_DUP OP_TOALTSTACK // save m_A
0 3 OP_WITHIN OP_VERIFY // check that m_A is 0, 1 or 2
// check that SHA256(m_A || r_A) equals c_A
OP_2DUP
OP_CAT OP_SHA256
<c_A>
OP_EQUALVERIFY
OP_DUP
<internal_pubkey>, OP_SWAP
OP_CHECKINPUTCONTRACTVERIFY
OP_FROMALTSTACK
OP_SUB // stack now contains m_B - m_A
OP_DUP // if the result is negative, add 3
0 OP_LESSTHAN
OP_IF
3
OP_ADD
OP_ENDIF
{0, 1, 2} // draw / Bob wins / Alice wins, respectively
OP_EQUALVERIFY
{<ctv-split>, <ctv-bob-wins>, <ctv-alice-wins>} // respectively
OP_CHECKTEMPLATEVERIFY
### Comments
In general, we would have to worry about the possible
malleability of the witness elements, when they are not signatures
or preimages themselves. Here, in particular, it might seem that's
an issue when <m_B> is provided while spending the state [S0].
However, here the value of <m_B> is also committed to in the output
thanks to COCV; therefore, Bob's signature prevents malleability
also for m_B.
In general, it seems to be the case in MATT contracts that one would
want the signature of the authorized party performing a transition to
some other state of the smart contract with contains embedded data;
this makes the malleability issue less of a problem in practice than
I initially thought.
If the internal_pubkey is a musig-aggregated key of Alice and Bob,
the game can be settled entirely offline after the first transaction.
Simply, Bob communicates his move to Alice, Alice reveals her move to
Bob, and they can settle the bet. The game would be played without
any script being executed, therefore all transactions could look like
any other P2TR, with the only possible fingerprinting being due to the