diff --git a/onchain/permissionless-arbitration/src/Commitment.sol b/onchain/permissionless-arbitration/src/Commitment.sol index 58966b1f..8dede2d0 100644 --- a/onchain/permissionless-arbitration/src/Commitment.sol +++ b/onchain/permissionless-arbitration/src/Commitment.sol @@ -10,27 +10,84 @@ import "./Machine.sol"; // import "./Merkle.sol"; library Commitment { + using Tree for Tree.Node; using Commitment for Tree.Node; - function proveFinalState( - Tree.Node root, + function requireState( + Tree.Node commitment, uint64 level, - Machine.Hash finalState, + uint256 position, + Machine.Hash state, bytes32[] calldata hashProof ) internal pure { - root.proveHash( - uint64(1 << ArbitrationConstants.height(level)), - finalState, + uint64 treeHeight = ArbitrationConstants.height(level); + Tree.Node expectedCommitment = getRoot( + Machine.Hash.unwrap(state), + treeHeight, + position, hashProof ); + + require(commitment.eq(expectedCommitment), "commitment state doesn't match"); + } + + + function isEven(uint256 x) private pure returns (bool) { + return x % 2 == 0; } - function proveHash( - Tree.Node root, + function getRoot( + bytes32 leaf, + uint64 treeHeight, uint256 position, - Machine.Hash hash, + bytes32[] calldata siblings + ) internal pure returns (Tree.Node) { + uint nodesCount = treeHeight - 1; + assert(nodesCount == siblings.length); + + for (uint i = 0; i < nodesCount; i++) { + if (isEven(position >> i)) { + leaf = + keccak256(abi.encodePacked(leaf, siblings[i])); + } else { + leaf = + keccak256(abi.encodePacked(siblings[i], leaf)); + } + } + + return Tree.Node.wrap(leaf); + } + + + function requireFinalState( + Tree.Node commitment, + uint64 level, + Machine.Hash finalState, bytes32[] calldata hashProof ) internal pure { - // TODO: call Merkle library + uint64 treeHeight = ArbitrationConstants.height(level); + Tree.Node expectedCommitment = getRootForLastLeaf( + treeHeight, + Machine.Hash.unwrap(finalState), + hashProof + ); + + require(commitment.eq(expectedCommitment), "commitment last state doesn't match"); + } + + + function getRootForLastLeaf( + uint64 treeHeight, + bytes32 leaf, + bytes32[] calldata siblings + ) internal pure returns (Tree.Node) { + uint nodesCount = treeHeight - 1; + assert(nodesCount == siblings.length); + + for (uint i = 0; i < nodesCount; i++) { + leaf = keccak256(abi.encodePacked(siblings[i], leaf)); + } + + return Tree.Node.wrap(leaf); } } diff --git a/onchain/permissionless-arbitration/src/Match.sol b/onchain/permissionless-arbitration/src/Match.sol index b80eec61..09a19b10 100644 --- a/onchain/permissionless-arbitration/src/Match.sol +++ b/onchain/permissionless-arbitration/src/Match.sol @@ -6,6 +6,7 @@ pragma solidity ^0.8.17; import "./CanonicalConstants.sol"; import "./Tree.sol"; import "./Machine.sol"; +import "./Commitment.sol"; /// @notice Implements functionalities to advance a match, until the point where divergence is found. library Match { @@ -13,6 +14,15 @@ library Match { using Match for Id; using Match for IdHash; using Match for State; + using Machine for Machine.Hash; + using Commitment for Tree.Node; + + // + // Events + // + + event matchAdvanced(Match.IdHash indexed, Tree.Node parent, Tree.Node left); + // // Id @@ -91,120 +101,72 @@ library Match { return (matchId.hashFromId(), state); } - function goDownLeftTree( - State storage state, - Tree.Node newLeftNode, - Tree.Node newRightNode - ) internal { - assert(state.currentHeight > 1); - state.otherParent = state.leftNode; - state.leftNode = newLeftNode; - state.rightNode = newRightNode; - - state.currentHeight--; - } - - function goDownRightTree( + function advanceMatch( State storage state, + Id calldata id, + Tree.Node leftNode, + Tree.Node rightNode, Tree.Node newLeftNode, Tree.Node newRightNode ) internal { - assert(state.currentHeight > 1); - state.otherParent = state.rightNode; - state.leftNode = newLeftNode; - state.rightNode = newRightNode; - - state.runningLeafPosition += 1 << state.currentHeight; // TODO: verify - state.currentHeight--; - } - - function setDivergenceOnLeftLeaf( - State storage state, - Tree.Node leftLeaf - ) - internal - returns (Machine.Hash finalStateOne, Machine.Hash finalStateTwo) - { - assert(state.currentHeight == 1); - state.rightNode = leftLeaf; - state.currentHeight = 0; - - if (state.height() % 2 == 0) { - finalStateOne = state.leftNode.toMachineHash(); - finalStateTwo = state.rightNode.toMachineHash(); + if (!state.agreesOnLeftNode(leftNode)) { + // go down left in Commitment tree + leftNode.requireChildren(newLeftNode, newRightNode); + state._goDownLeftTree(newLeftNode, newRightNode); } else { - finalStateOne = state.rightNode.toMachineHash(); - finalStateTwo = state.leftNode.toMachineHash(); + // go down right in Commitment tree + rightNode.requireChildren(newLeftNode, newRightNode); + state._goDownRightTree(newLeftNode, newRightNode); } - } - - function setDivergenceOnRightLeaf( - State storage state, - Tree.Node rightLeaf - ) - internal - returns (Machine.Hash finalStateOne, Machine.Hash finalStateTwo) - { - assert(state.currentHeight == 1); - state.leftNode = rightLeaf; - state.runningLeafPosition += 1; // TODO: verify - state.currentHeight = 0; - if (state.height() % 2 == 0) { - finalStateOne = state.rightNode.toMachineHash(); - finalStateTwo = state.leftNode.toMachineHash(); - } else { - finalStateOne = state.leftNode.toMachineHash(); - finalStateTwo = state.rightNode.toMachineHash(); - } + emit matchAdvanced( + id.hashFromId(), + state.otherParent, + state.leftNode + ); } - function getDivergence( + function sealMatch( State storage state, - uint256 startCycle + Id calldata id, + Machine.Hash initialState, + Tree.Node leftLeaf, + Tree.Node rightLeaf, + Machine.Hash agreeState, + bytes32[] calldata agreeStateProof ) internal - view returns ( - Machine.Hash agreeHash, - uint256 agreeCycle, - Machine.Hash finalStateOne, - Machine.Hash finalStateTwo + Machine.Hash divergentStateOne, + Machine.Hash divergentStateTwo ) { - assert(state.currentHeight == 0); - agreeHash = Machine.Hash.wrap(Tree.Node.unwrap(state.otherParent)); - agreeCycle = state.toCycle(startCycle); + if (!state.agreesOnLeftNode(leftLeaf)) { + // Divergence is in the left leaf! + (divergentStateOne, divergentStateTwo) = state + ._setDivergenceOnLeftLeaf(leftLeaf); + } else { + // Divergence is in the right leaf! + (divergentStateOne, divergentStateTwo) = state + ._setDivergenceOnRightLeaf(rightLeaf); + } - if (state.runningLeafPosition % 2 == 0) { - // divergence was set on left leaf - if (state.height() % 2 == 0) { - finalStateOne = state.leftNode.toMachineHash(); - finalStateTwo = state.rightNode.toMachineHash(); - } else { - finalStateOne = state.rightNode.toMachineHash(); - finalStateTwo = state.leftNode.toMachineHash(); - } + // Prove initial hash is in commitment + if (state.runningLeafPosition == 0) { + require(agreeState.eq(initialState), "agree hash incorrect"); } else { - // divergence was set on right leaf - if (state.height() % 2 == 0) { - finalStateOne = state.rightNode.toMachineHash(); - finalStateTwo = state.leftNode.toMachineHash(); - } else { - finalStateOne = state.leftNode.toMachineHash(); - finalStateTwo = state.rightNode.toMachineHash(); - } + id.commitmentOne.requireState( + state.level, + state.runningLeafPosition - 1, + agreeState, + agreeStateProof + ); } - } - function setInitialState( - State storage state, - Machine.Hash initialState - ) internal { - assert(state.currentHeight == 0); - state.otherParent = Tree.Node.wrap(Machine.Hash.unwrap(initialState)); + state._setAgreeState(agreeState); } + // // View methods // @@ -246,6 +208,45 @@ library Match { return ArbitrationConstants.height(state.level); } + function getDivergence( + State memory state, + uint256 startCycle + ) + internal + pure + returns ( + Machine.Hash agreeHash, + uint256 agreeCycle, + Machine.Hash finalStateOne, + Machine.Hash finalStateTwo + ) + { + assert(state.currentHeight == 0); + agreeHash = Machine.Hash.wrap(Tree.Node.unwrap(state.otherParent)); + agreeCycle = state.toCycle(startCycle); + + if (state.runningLeafPosition % 2 == 0) { + // divergence was set on left leaf + if (state.height() % 2 == 0) { + finalStateOne = state.leftNode.toMachineHash(); + finalStateTwo = state.rightNode.toMachineHash(); + } else { + finalStateOne = state.rightNode.toMachineHash(); + finalStateTwo = state.leftNode.toMachineHash(); + } + } else { + // divergence was set on right leaf + if (state.height() % 2 == 0) { + finalStateOne = state.rightNode.toMachineHash(); + finalStateTwo = state.leftNode.toMachineHash(); + } else { + finalStateOne = state.leftNode.toMachineHash(); + finalStateTwo = state.rightNode.toMachineHash(); + } + } + } + + // // Requires // @@ -274,10 +275,87 @@ library Match { state.otherParent.requireChildren(leftNode, rightNode); } + // // Private // + function _goDownLeftTree( + State storage state, + Tree.Node newLeftNode, + Tree.Node newRightNode + ) internal { + assert(state.currentHeight > 1); + state.otherParent = state.leftNode; + state.leftNode = newLeftNode; + state.rightNode = newRightNode; + + state.currentHeight--; + } + + function _goDownRightTree( + State storage state, + Tree.Node newLeftNode, + Tree.Node newRightNode + ) internal { + assert(state.currentHeight > 1); + state.otherParent = state.rightNode; + state.leftNode = newLeftNode; + state.rightNode = newRightNode; + + state.runningLeafPosition += 1 << state.currentHeight; + state.currentHeight--; + } + + function _setDivergenceOnLeftLeaf( + State storage state, + Tree.Node leftLeaf + ) + internal + returns (Machine.Hash finalStateOne, Machine.Hash finalStateTwo) + { + assert(state.currentHeight == 1); + state.rightNode = leftLeaf; + state.currentHeight = 0; + + if (state.height() % 2 == 0) { + finalStateOne = state.leftNode.toMachineHash(); + finalStateTwo = state.rightNode.toMachineHash(); + } else { + finalStateOne = state.rightNode.toMachineHash(); + finalStateTwo = state.leftNode.toMachineHash(); + } + } + + function _setDivergenceOnRightLeaf( + State storage state, + Tree.Node rightLeaf + ) + internal + returns (Machine.Hash finalStateOne, Machine.Hash finalStateTwo) + { + assert(state.currentHeight == 1); + state.leftNode = rightLeaf; + state.runningLeafPosition += 1; + state.currentHeight = 0; + + if (state.height() % 2 == 0) { + finalStateOne = state.rightNode.toMachineHash(); + finalStateTwo = state.leftNode.toMachineHash(); + } else { + finalStateOne = state.leftNode.toMachineHash(); + finalStateTwo = state.rightNode.toMachineHash(); + } + } + + function _setAgreeState( + State storage state, + Machine.Hash initialState + ) internal { + assert(state.currentHeight == 0); + state.otherParent = Tree.Node.wrap(Machine.Hash.unwrap(initialState)); + } + function _toCycle( State memory state, uint256 base, @@ -285,6 +363,6 @@ library Match { ) internal pure returns (uint256) { uint256 step = 1 << log2step; uint256 leafPosition = state.runningLeafPosition; - return base + (leafPosition * step); // TODO verify + return base + (leafPosition * step); } } diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol index 8212535e..c336ef88 100644 --- a/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/LeafTournament.sol @@ -5,7 +5,6 @@ pragma solidity ^0.8.17; import "./Tournament.sol"; import "../../Commitment.sol"; -import "../../Merkle.sol"; import "step/ready_src/UArchStep.sol"; @@ -24,47 +23,32 @@ abstract contract LeafTournament is Tournament { Match.Id calldata _matchId, Tree.Node _leftLeaf, Tree.Node _rightLeaf, - Machine.Hash _initialHash, - bytes32[] calldata _initialHashProof + Machine.Hash _agreeHash, + bytes32[] calldata _agreeHashProof ) external tournamentNotFinished { Match.State storage _matchState = matches[_matchId.hashFromId()]; _matchState.requireExist(); _matchState.requireCanBeFinalized(); _matchState.requireParentHasChildren(_leftLeaf, _rightLeaf); - Machine.Hash _finalStateOne; - Machine.Hash _finalStateTwo; - - if (!_matchState.agreesOnLeftNode(_leftLeaf)) { - // Divergence is in the left leaf! - (_finalStateOne, _finalStateTwo) = _matchState - .setDivergenceOnLeftLeaf(_leftLeaf); - } else { - // Divergence is in the right leaf! - (_finalStateOne, _finalStateTwo) = _matchState - .setDivergenceOnRightLeaf(_rightLeaf); - } - // Unpause clocks - Clock.State storage _clock1 = clocks[_matchId.commitmentOne]; - Clock.State storage _clock2 = clocks[_matchId.commitmentTwo]; - _clock1.setPaused(); - _clock1.advanceClock(); - _clock2.setPaused(); - _clock2.advanceClock(); - - // Prove initial hash is in commitment - if (_matchState.runningLeafPosition == 0) { - require(_initialHash.eq(initialHash), "initial hash incorrect"); - } else { - _matchId.commitmentOne.proveHash( - _matchState.runningLeafPosition, - _initialHash, - _initialHashProof - ); + { + Clock.State storage _clock1 = clocks[_matchId.commitmentOne]; + Clock.State storage _clock2 = clocks[_matchId.commitmentTwo]; + _clock1.setPaused(); + _clock1.advanceClock(); + _clock2.setPaused(); + _clock2.advanceClock(); } - _matchState.setInitialState(_initialHash); + _matchState.sealMatch( + _matchId, + initialHash, + _leftLeaf, + _rightLeaf, + _agreeHash, + _agreeHashProof + ); } function winLeafMatch( diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol index 282583bb..b5b59c0c 100644 --- a/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/NonLeafTournament.sol @@ -59,26 +59,13 @@ abstract contract NonLeafTournament is Tournament { Match.Id calldata _matchId, Tree.Node _leftLeaf, Tree.Node _rightLeaf, - Machine.Hash _initialHash, - bytes32[] calldata _initialHashProof + Machine.Hash _agreeHash, + bytes32[] calldata _agreeHashProof ) external tournamentNotFinished { Match.State storage _matchState = matches[_matchId.hashFromId()]; _matchState.requireCanBeFinalized(); _matchState.requireParentHasChildren(_leftLeaf, _rightLeaf); - Machine.Hash _finalStateOne; - Machine.Hash _finalStateTwo; - - if (!_matchState.agreesOnLeftNode(_leftLeaf)) { - // Divergence is in the left leaf! - (_finalStateOne, _finalStateTwo) = _matchState - .setDivergenceOnLeftLeaf(_leftLeaf); - } else { - // Divergence is in the right leaf! - (_finalStateOne, _finalStateTwo) = _matchState - .setDivergenceOnRightLeaf(_rightLeaf); - } - // Pause clocks Time.Duration _maxDuration; { @@ -89,19 +76,20 @@ abstract contract NonLeafTournament is Tournament { _maxDuration = Clock.max(_clock1, _clock2); } - // Prove initial hash is in commitment - if (_matchState.runningLeafPosition == 0) { - require(_initialHash.eq(initialHash), "initial hash incorrect"); - } else { - _matchId.commitmentOne.proveHash( - _matchState.runningLeafPosition - 1, - _initialHash, - _initialHashProof - ); - } + ( + Machine.Hash _finalStateOne, + Machine.Hash _finalStateTwo + ) = _matchState.sealMatch( + _matchId, + initialHash, + _leftLeaf, + _rightLeaf, + _agreeHash, + _agreeHashProof + ); NonRootTournament _inner = instantiateInner( - _initialHash, + _agreeHash, _matchId.commitmentOne, _finalStateOne, _matchId.commitmentTwo, diff --git a/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol b/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol index 3e4d7af1..d4ca1bf4 100644 --- a/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol +++ b/onchain/permissionless-arbitration/src/tournament/abstracts/Tournament.sol @@ -68,7 +68,6 @@ abstract contract Tournament { Tree.Node leftOfTwo ); - event matchAdvanced(Match.IdHash indexed, Tree.Node parent, Tree.Node left); // // Modifiers @@ -125,7 +124,7 @@ abstract contract Tournament { Tree.Node _commitmentRoot = _leftNode.join(_rightNode); // Prove final state is in commitmentRoot - _commitmentRoot.proveFinalState(level, _finalState, _proof); + _commitmentRoot.requireFinalState(level, _finalState, _proof); // Verify whether finalState is one of the two allowed of tournament if nested requireValidContestedFinalState(_finalState); @@ -152,26 +151,17 @@ abstract contract Tournament { _matchState.requireCanBeAdvanced(); _matchState.requireParentHasChildren(_leftNode, _rightNode); - if (!_matchState.agreesOnLeftNode(_leftNode)) { - // go down left in Commitment tree - _leftNode.requireChildren(_newLeftNode, _newRightNode); - _matchState.goDownLeftTree(_newLeftNode, _newRightNode); - } else { - // go down right in Commitment tree - _rightNode.requireChildren(_newLeftNode, _newRightNode); - _matchState.goDownRightTree(_newLeftNode, _newRightNode); - } + _matchState.advanceMatch( + _matchId, + _leftNode, + _rightNode, + _newLeftNode, + _newRightNode + ); // advance clocks clocks[_matchId.commitmentOne].advanceClock(); clocks[_matchId.commitmentTwo].advanceClock(); - - // TODO move event to lib? - emit matchAdvanced( - _matchId.hashFromId(), - _matchState.otherParent, - _matchState.leftNode - ); } function winMatchByTimeout(