Edit: I’ve tried to capture the whole idea here https://gist.github.com/phyro/ef42ce95cfbad05964f8be2f5d8f9466
Say we have a transaction T
and we want to prove it was added in block B
. We can do that by either:
- Checking the
utxo_mmr_root
of the blockB
and then have a merkle proof for inclusion of the outputs ofT
. (Edit: I’ve been corrected that this does not prove inclusion ofT
) - Checking the position of the transaction kernel and computing from
kernel_mmr_size
in to get the block in which it was added (a bit weaker because it does not commit to utxos)
There’s another simple way to prove it which is basically the standard “global excess validation” where you subtract the transaction and check that the block is still valid (it should be if outputs were 0*H
and if they were not, you can adjust them by adding v*H + 0*G
outputs and check it then).
Let’s assume we take part in a PayJoin transaction that adds one input and one output to it I1->O1
but we don’t know the rest of the transaction. If O1
was generated from I1
AND both had a form 0*H + r*G
we could, given O1
, compute also I1
which means that we would know O1.r - I1.r
. This means that if this ‘subset transaction’ was included in a block B
, then if we have:
-
the sum of kernels at block
B
we can compute the sum based on thekernel_mmr_size
) -
the total kernel offset
blocks have a headertotal_kernel_offset
-
the total UTXO set sum
we don’t have this, but imagine a block header commits to thetotal_utxo_set
We could check if I1->O1
was a part of block B
by adjusting the total_kernel_offset and the total_utxo_set at that block. If it can be validated as “a valid chain” after the change, then we know that the difference of these blinding factors was a part of the transaction (I think).
This could seem useless because why would you keep the subset of a transaction instead of keeping it all, but you might not need to keep even the subset if O1
is derived from I1
. Suppose you have a chain of PayJoin txs to which you added pairs Ox->Oy
:
X -> A, O1 # + O1.r
O1 -> O2 # - O1.r + O2.r
O2 -> O3 # - O2.r + O3.r
O3 -> O4 # - O3.r + O4.r
O4 -> O5 # - O4.r + O5.r
If we restore a wallet and see O5
, then we can compute O4
which means that we know O5.r - O4.r
and can thus check every block from head towards genesis which block would be valid if this ‘subset transaction’ would be removed. Note that if it was included in a block, such a block MUST exist because the block would need to be valid without this 1-1 part as well because they had 0*H
.
Once we have found O4
we can again figure out O3
and then repeat the check for blocks prior to the block where O4
was included.
This way, we can prove a subgraph (chain) from O1->O2->O3->O4->O5
exists on the blockchain without the need to actually keep the transaction data around.
Whether this is useful or not is still not clear to me, but I thought I’d still share. I guess the outcome here is that if we had the total_utxo_set
commitment on the header, some new options might be possible. I’m also sharing this in case someone else sees anything usefull that could come from this.
Note: A 2-2 PayJoin can be broken down into 2 transactions, 1 that is known to the sender (sender’s input and output) and 1 to receiver (receiver’s input and output), so we have a transaction a transaction we want. If input and output are generated deterministically, we can generate them and don’t really need to save them anywhere. We’ve shown above (hopefully) that only a transaction is needed to compute in which block it was added.