Closed smartcontracts closed 6 years ago
An error while creating my tests also revealed another problem. Replacing proof2
with proof1
(a completely unrelated Merkle proof) does not throw during challengeExit
, suggesting that Merkle proofs aren't being correctly validated.
# Challenge the exit with an unrelated spend
root_chain.challengeExit(exitId2, exitId1, tx_bytes3, proof1, sigs2, confirmSig2)
Here's a quick fix for the first case - there's probably a better way to do this but this is a PoC. This still doesn't verify the case where the challenging tx comes before the exiting tx and they share an input.
function txHasInput(bytes txBytes, uint256 utxoPos)
internal
view
returns (bool)
{
var txList = txBytes.toRLPItem().toList(11);
uint256 blknum = utxoPos / 1000000000;
uint256 txindex = (utxoPos % 1000000000) / 10000;
uint256 oindex = utxoPos - blknum * 1000000000 - txindex * 10000;
bool check1 = txList[0].toUint() == blknum && txList[1].toUint() == txindex && txList[2].toUint() == oindex;
bool check2 = txList[3].toUint() == blknum && txList[4].toUint() == txindex && txList[5].toUint() == oindex;
return check1 || check2;
}
// @dev Allows anyone to challenge an exiting transaction by submitting proof of a double spend on the child chain
// @param cUtxoPos The position of the challenging utxo
// @param eUtxoPos The position of the exiting utxo
// @param txBytes The challenging transaction in bytes RLP form
// @param proof Proof of inclusion for the transaction used to challenge
// @param sigs Signatures for the transaction used to challenge
// @param confirmationSig The confirmation signature for the transaction used to challenge
function challengeExit(uint256 cUtxoPos, uint256 eUtxoPos, bytes txBytes, bytes proof, bytes sigs, bytes confirmationSig)
public
{
uint256 cTxindex = (cUtxoPos % 1000000000) / 10000;
bytes32 root = childChain[cUtxoPos / 1000000000].root;
uint256 priority = exitIds[eUtxoPos];
var txHash = keccak256(txBytes);
var confirmationHash = keccak256(txHash, root);
var merkleHash = keccak256(txHash, sigs);
address owner = exits[priority].owner;
require(owner == ECRecovery.recover(confirmationHash, confirmationSig));
require(merkleHash.checkMembership(cTxindex, root, proof));
require(txHasInput(txBytes, eUtxoPos));
delete exits[priority];
delete exitIds[eUtxoPos];
}
I moved the txHasInput
check out of challengeExit
because you'll get a StackTooDeepException
otherwise.
challengeExit()
, reproduced below, is only checking that a later spend by a UTXO's owner exists and does not check that the spend is actually spending the exiting UTXO.https://github.com/omisego/plasma-mvp/blob/4b119a80dd20e2dd8bb4b4cb2dda67d3b88d6f40/plasma/root_chain/contracts/RootChain/RootChain.sol#L167-L182
Challenges are valid in the case that a UTXO has either already been exited, that the UTXO was spent in a later transaction, or that the UTXO was created from an input that was already spent. So we really need to verify that there exists a valid transaction that spends the UTXO. Currently we're just verifying that there exists a valid transaction spent by the owner of the UTXO. This process should look like:
Here's a test case that demonstrates the problem: