diff --git a/contracts/src/ProtocolAdapter.sol b/contracts/src/ProtocolAdapter.sol index 8997520a..7b16d6d2 100644 --- a/contracts/src/ProtocolAdapter.sol +++ b/contracts/src/ProtocolAdapter.sol @@ -45,6 +45,7 @@ contract ProtocolAdapter is using RiscZeroUtils for Logic.VerifierInput; using Logic for Logic.VerifierInput[]; using Delta for uint256[2]; + using Compliance for Compliance.VerifierInput[]; RiscZeroVerifierRouter internal immutable _TRUSTED_RISC_ZERO_VERIFIER_ROUTER; bytes4 internal immutable _RISC_ZERO_VERIFIER_SELECTOR; @@ -135,7 +136,7 @@ contract ProtocolAdapter is // Check the consumed resource. // slither-disable-next-line reentrancy-benign _processResourceLogicContext({ - input: action.logicVerifierInputs.lookup(nf), + input: action.logicVerifierInputs[action.logicVerifierInputs.lookup(nf)], logicRef: complianceVerifierInput.instance.consumed.logicRef, actionTreeRoot: actionTreeRoot, consumed: true @@ -144,7 +145,7 @@ contract ProtocolAdapter is // Check the created resource. // slither-disable-next-line reentrancy-benign _processResourceLogicContext({ - input: action.logicVerifierInputs.lookup(cm), + input: action.logicVerifierInputs[action.logicVerifierInputs.lookup(cm)], logicRef: complianceVerifierInput.instance.created.logicRef, actionTreeRoot: actionTreeRoot, consumed: false @@ -366,16 +367,6 @@ contract ProtocolAdapter is pure returns (bytes32 root) { - bytes32[] memory actionTreeTags = new bytes32[](complianceUnitCount * 2); - - // The order in which the tags are added to the tree is provided by the compliance units. - for (uint256 j = 0; j < complianceUnitCount; ++j) { - Compliance.VerifierInput calldata complianceVerifierInput = action.complianceVerifierInputs[j]; - - actionTreeTags[2 * j] = complianceVerifierInput.instance.consumed.nullifier; - actionTreeTags[(2 * j) + 1] = complianceVerifierInput.instance.created.commitment; - } - - root = actionTreeTags.computeRoot(); + root = action.complianceVerifierInputs.computeActionTreeTags(complianceUnitCount).computeRoot(); } } diff --git a/contracts/src/proving/Compliance.sol b/contracts/src/proving/Compliance.sol index cc6daecc..fe2251f6 100644 --- a/contracts/src/proving/Compliance.sol +++ b/contracts/src/proving/Compliance.sol @@ -48,4 +48,23 @@ library Compliance { /// @notice The compliance verifying key. /// @dev The key is fixed as long as the compliance circuit binary is not changed. bytes32 internal constant _VERIFYING_KEY = 0x706468196fd92568220f5271e843c608126f7a8f204205d42ceef1f2c69f91df; + + /// @notice Computes the action tree root of an action constituted by all its nullifiers and commitments. + /// @param complianceVerifierInputs Compliance verifier inputs. + /// @param complianceUnitCount The number of compliance units in the action. + /// @return actionTreeTags The action tree tags corresponding to the compliance verifier inputs. + function computeActionTreeTags( + Compliance.VerifierInput[] calldata complianceVerifierInputs, + uint256 complianceUnitCount + ) internal pure returns (bytes32[] memory actionTreeTags) { + actionTreeTags = new bytes32[](complianceUnitCount * 2); + + // The order in which the tags are added to the tree is provided by the compliance units. + for (uint256 j = 0; j < complianceUnitCount; ++j) { + Compliance.VerifierInput calldata complianceVerifierInput = complianceVerifierInputs[j]; + + actionTreeTags[2 * j] = complianceVerifierInput.instance.consumed.nullifier; + actionTreeTags[(2 * j) + 1] = complianceVerifierInput.instance.created.commitment; + } + } } diff --git a/contracts/src/proving/Logic.sol b/contracts/src/proving/Logic.sol index 3aa39ba2..f6194561 100644 --- a/contracts/src/proving/Logic.sol +++ b/contracts/src/proving/Logic.sol @@ -52,16 +52,12 @@ library Logic { /// @notice Looks up a `VerifierInput` element from a list by its tag. /// @param list The list of verifier inputs. /// @param tag The tag to look up. - /// @return foundElement The found `VerifierInput` element. - function lookup(VerifierInput[] calldata list, bytes32 tag) - internal - pure - returns (VerifierInput calldata foundElement) - { + /// @return foundElementIdx The index of the found `VerifierInput` element. + function lookup(VerifierInput[] calldata list, bytes32 tag) internal pure returns (uint256 foundElementIdx) { uint256 len = list.length; for (uint256 i = 0; i < len; ++i) { if (list[i].tag == tag) { - return foundElement = list[i]; + return foundElementIdx = i; } } revert TagNotFound(tag); diff --git a/contracts/test/ProtocolAdapterMock.t.sol b/contracts/test/ProtocolAdapterMock.t.sol index 98fa6ec3..b7795643 100644 --- a/contracts/test/ProtocolAdapterMock.t.sol +++ b/contracts/test/ProtocolAdapterMock.t.sol @@ -12,6 +12,7 @@ import {RiscZeroUtils} from "./../src/libs/RiscZeroUtils.sol"; import {ProtocolAdapter} from "./../src/ProtocolAdapter.sol"; import {Compliance} from "./../src/proving/Compliance.sol"; import {Logic} from "./../src/proving/Logic.sol"; +import {CommitmentAccumulator} from "./../src/state/CommitmentAccumulator.sol"; import {NullifierSet} from "./../src/state/NullifierSet.sol"; import {Transaction, Action} from "./../src/Types.sol"; import {ForwarderExample} from "./examples/Forwarder.e.sol"; @@ -19,23 +20,16 @@ import {INPUT, EXPECTED_OUTPUT} from "./examples/ForwarderTarget.e.sol"; import {TxGen} from "./libs/TxGen.sol"; import {DeployRiscZeroContractsMock} from "./script/DeployRiscZeroContractsMock.s.sol"; -struct ProtocolAdapterTestArgs { - RiscZeroVerifierRouter router; - RiscZeroVerifierEmergencyStop emergencyStop; - RiscZeroMockVerifier verifier; -} - -contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { +contract ProtocolAdapterMockVerifierTest is Test { using MerkleTree for bytes32[]; using TxGen for Action[]; using TxGen for Action; using TxGen for Vm; using RiscZeroUtils for Logic.VerifierInput; + using Logic for Logic.VerifierInput[]; + using Compliance for Compliance.VerifierInput[]; /// @notice The parameters necessary to make a failing mutation to a transaction - /// @param The index of the action to mutate - /// @param The index of the compliance verifier input of the action to mutate - /// @param The value to mutate the action tree root to struct NonExistingRootFailsParams { uint256 actionIdx; uint256 inputIdx; @@ -43,17 +37,6 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { } /// @notice The parameters necessary to make a failing mutation to a transaction - /// @param The index of the action to mutate - /// @param The index of the compliance verifier input of the action to mutate - struct ShortProofFailsParams { - uint256 actionIdx; - uint256 inputIdx; - } - - /// @notice The parameters necessary to make a failing mutation to a transaction - /// @param The index of the action to mutate - /// @param The index of the compliance verifier input of the action to mutate - /// @param The proof to overwrite with struct UnknownSelectorFailsParams { uint256 actionIdx; uint256 inputIdx; @@ -61,27 +44,20 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { } /// @notice The parameters necessary to make a failing mutation to a transaction - /// @param The index of the action to mutate - /// @param The index of the compliance verifier input of the action to mutate - /// @param The tag to overwrite with struct UnknownTagFailsParams { uint256 actionIdx; uint256 inputIdx; bytes32 tag; + bool consumed; } /// @notice The parameters necessary to make a failing mutation to a transaction - /// @param The index of the action to mutate - /// @param The index of the compliance verifier input of the action to mutate - struct MismatchingResourcesFailParams { + struct GenericFailParams { uint256 actionIdx; uint256 inputIdx; } /// @notice The parameters necessary to make a failing mutation to a transaction - /// @param The index of the action to mutate - /// @param The index of the logic verifier input of the action to mutate - /// @param The logic reference to overwrite with struct MismatchingLogicRefsFailParams { uint256 actionIdx; uint256 inputIdx; @@ -89,15 +65,12 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { } /// @notice The parameters necessary to make a failing mutation to a transaction - /// @param The index of the action to mutate - /// @param The index of the logic verifier input of the action to mutate - /// @param The index of the external payload to mutate - /// @param The output to overwrite with struct MismatchingForwarderCallOutputsFailParams { uint256 actionIdx; uint256 inputIdx; uint256 payloadIdx; bytes output; + bool consumed; } address internal constant _EMERGENCY_COMMITTEE = address(uint160(1)); @@ -113,17 +86,11 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { bytes32 internal _carrierLabelRef; - constructor(ProtocolAdapterTestArgs memory args) - ProtocolAdapter(args.router, args.verifier.SELECTOR(), _EMERGENCY_COMMITTEE) - { - _router = args.router; - _emergencyStop = args.emergencyStop; - _mockVerifier = args.verifier; - _verifierSelector = args.verifier.SELECTOR(); - _mockPa = new ProtocolAdapter(_router, _mockVerifier.SELECTOR(), _EMERGENCY_COMMITTEE); - } - function setUp() public { + (_router, _emergencyStop, _mockVerifier) = new DeployRiscZeroContractsMock().run(); + _verifierSelector = _mockVerifier.SELECTOR(); + _mockPa = new ProtocolAdapter(_router, _verifierSelector, _EMERGENCY_COMMITTEE); + _fwd = address( new ForwarderExample({protocolAdapter: address(_mockPa), calldataCarrierLogicRef: _CARRIER_LOGIC_REF}) ); @@ -279,6 +246,10 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { _mockPa.execute(txn); } + function test_random_transactions_execute(TxGen.TransactionParams memory params) public { + _mockPa.execute(vm.transaction(_mockVerifier, params)); + } + function test_execute_reverts_on_pre_existing_nullifier() public { TxGen.ActionConfig[] memory configs = TxGen.generateActionConfigs({actionCount: 1, complianceUnitCount: 1}); @@ -318,94 +289,73 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { _mockPa.execute(txn); } - /// @notice Take a transaction that would execute successfully and make it - /// fail by giving one of its compliance verifier inputs an incorrect - /// commitment tree root. - /// @param transaction A successful transaction - /// @param params A failure inducing modification + /// @notice Make transaction fail by giving it an incorrect commitment tree root. function mutationTestExecuteNonExistingRootFails( Transaction memory transaction, NonExistingRootFailsParams memory params ) public { // Wrap the action index into range + vm.assume(transaction.actions.length > 0); params.actionIdx = params.actionIdx % transaction.actions.length; Compliance.VerifierInput[] memory complianceVerifierInputs = transaction.actions[params.actionIdx].complianceVerifierInputs; // Assume the proposed commitment tree root is not already contained - vm.assume(!_containsRoot(params.commitmentTreeRoot)); + vm.assume(!_mockPa.containsRoot(params.commitmentTreeRoot)); // Wrap the compliance verifier input index into range + vm.assume(complianceVerifierInputs.length > 0); params.inputIdx = params.inputIdx % complianceVerifierInputs.length; // Finally assign the proposed commitment tree root into the transaction complianceVerifierInputs[params.inputIdx].instance.consumed.commitmentTreeRoot = params.commitmentTreeRoot; // With an incorrect commitment tree root, we expect failure - vm.expectRevert(abi.encodeWithSelector(NonExistingRoot.selector, params.commitmentTreeRoot)); + vm.expectRevert( + abi.encodeWithSelector(CommitmentAccumulator.NonExistingRoot.selector, params.commitmentTreeRoot) + ); // Finally, execute the transaction to make sure that it fails - this.execute(transaction); + _mockPa.execute(transaction); } /// @notice Test that transactions with nonexistent rotts fail function testFuzz_execute_non_existing_root_fails( - uint8 actionCount, - uint8 complianceUnitCount, - NonExistingRootFailsParams memory params + TxGen.TransactionParams memory txParams, + NonExistingRootFailsParams memory mutParams ) public { - TxGen.ActionConfig[] memory configs = TxGen.generateActionConfigs({ - actionCount: uint8(bound(actionCount, 1, 5)), - complianceUnitCount: uint8(bound(complianceUnitCount, 1, 5)) - }); - - (Transaction memory txn,) = vm.transaction({mockVerifier: _mockVerifier, nonce: 0, configs: configs}); - mutationTestExecuteNonExistingRootFails(txn, params); + mutationTestExecuteNonExistingRootFails(vm.transaction(_mockVerifier, txParams), mutParams); } - /// @notice Take a transaction that would execute successfully and make it - /// fail by giving one of its compliance verifier inputs a proof that's too - /// short. - /// @param transaction A successful transaction - /// @param params A failure inducing modification - function mutationTestExecuteShortProofFails(Transaction memory transaction, ShortProofFailsParams memory params) - public - { + /// @notice Make transaction fail by giving it a proof that's too short + function mutationTestExecuteShortProofFails( + Transaction calldata transactionCalldata, + GenericFailParams memory params + ) public { + Transaction memory transaction = transactionCalldata; uint256 minProofLen = 4; // Wrap the action index into range + vm.assume(transaction.actions.length > 0); params.actionIdx = params.actionIdx % transaction.actions.length; Compliance.VerifierInput[] memory complianceVerifierInputs = transaction.actions[params.actionIdx].complianceVerifierInputs; // Wrap the compliance verifier input index into range + vm.assume(complianceVerifierInputs.length > 0); params.inputIdx = params.inputIdx % complianceVerifierInputs.length; // Finally truncate the compliance proof to below the minimum - bytes memory proof = complianceVerifierInputs[params.inputIdx].proof; - bytes memory truncatedProof = new bytes(proof.length % minProofLen); - for (uint256 k = 0; k < truncatedProof.length; k++) { - truncatedProof[k] = proof[k]; - } - complianceVerifierInputs[params.inputIdx].proof = truncatedProof; - // With a short proof, we expect failure - vm.expectRevert(address(_router)); + bytes calldata proof = + transactionCalldata.actions[params.actionIdx].complianceVerifierInputs[params.inputIdx].proof; + complianceVerifierInputs[params.inputIdx].proof = proof[0:(proof.length % minProofLen)]; + // With a short proof, we expect an EVM error (which is message-less) + vm.expectRevert(bytes(""), address(_router)); // Finally, execute the transaction to make sure that it fails - this.execute(transaction); + _mockPa.execute(transaction); } /// @notice Test that transactions with short proofs fail function testFuzz_execute_short_proof_fails( - uint8 actionCount, - uint8 complianceUnitCount, - ShortProofFailsParams memory params + TxGen.TransactionParams memory txParams, + GenericFailParams memory mutParams ) public { - TxGen.ActionConfig[] memory configs = TxGen.generateActionConfigs({ - actionCount: uint8(bound(actionCount, 1, 5)), - complianceUnitCount: uint8(bound(complianceUnitCount, 1, 5)) - }); - - (Transaction memory txn,) = vm.transaction({mockVerifier: _mockVerifier, nonce: 0, configs: configs}); - mutationTestExecuteShortProofFails(txn, params); + this.mutationTestExecuteShortProofFails(vm.transaction(_mockVerifier, txParams), mutParams); } - /// @notice Take a transaction that would execute successfully and make it - /// fail by giving one of its compliance verifier inputs a proof with an - /// unknown selector. - /// @param transaction A successful transaction - /// @param params A failure inducing modification + /// @notice Make transaction fail by giving it an unknown selector. function mutationTestExecuteUnknownSelectorFails( Transaction memory transaction, UnknownSelectorFailsParams memory params @@ -415,10 +365,12 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { vm.assume(params.proof.length >= minProofLen); vm.assume(address(_router.verifiers(bytes4(params.proof))) == address(0)); // Wrap the action index into range + vm.assume(transaction.actions.length > 0); params.actionIdx = params.actionIdx % transaction.actions.length; Compliance.VerifierInput[] memory complianceVerifierInputs = transaction.actions[params.actionIdx].complianceVerifierInputs; // Wrap the compliance verifier input index into range + vm.assume(complianceVerifierInputs.length > 0); params.inputIdx = params.inputIdx % complianceVerifierInputs.length; // Finally, corrupt the verifier selector complianceVerifierInputs[params.inputIdx].proof = params.proof; @@ -428,144 +380,67 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { address(_router) ); // Finally, execute the transaction to make sure that it fails - this.execute(transaction); + _mockPa.execute(transaction); } /// @notice Test that transactions with unknown selectors fail function testFuzz_execute_unknown_selector_fails( - uint8 actionCount, - uint8 complianceUnitCount, - UnknownSelectorFailsParams memory params + TxGen.TransactionParams memory txParams, + UnknownSelectorFailsParams memory mutParams ) public { - TxGen.ActionConfig[] memory configs = TxGen.generateActionConfigs({ - actionCount: uint8(bound(actionCount, 1, 5)), - complianceUnitCount: uint8(bound(complianceUnitCount, 1, 5)) - }); - - (Transaction memory txn,) = vm.transaction({mockVerifier: _mockVerifier, nonce: 0, configs: configs}); - mutationTestExecuteUnknownSelectorFails(txn, params); + mutationTestExecuteUnknownSelectorFails(vm.transaction(_mockVerifier, txParams), mutParams); } - /// @notice Take a transaction that would execute successfully and make it - /// fail by ensuring that the nullifier of one of its compliance verifier - /// inputs is not found in the logic verifier inputs. - /// @param transaction A successful transaction - /// @param params A failure inducing modification - function mutationTestExecuteUnknownNullifierTagFails( - Transaction memory transaction, + /// @notice Make transaction fail by ensuring unknown tag + function mutationTestExecuteUnknownTagFails( + Transaction calldata transactionCalldata, UnknownTagFailsParams memory params ) public { + Transaction memory transaction = transactionCalldata; // Wrap the action index into range + vm.assume(transaction.actions.length > 0); params.actionIdx = params.actionIdx % transaction.actions.length; - Compliance.VerifierInput[] memory complianceVerifierInputs = - transaction.actions[params.actionIdx].complianceVerifierInputs; - // Wrap the compliance verifier input index into range - params.inputIdx = params.inputIdx % complianceVerifierInputs.length; - Compliance.VerifierInput memory complianceVerifierInput = complianceVerifierInputs[params.inputIdx]; - // Make sure that the planned corruption will change something - vm.assume(complianceVerifierInput.instance.consumed.nullifier != params.tag); - // Finally, corrupt the corresponding logic verifier input tag - Logic.VerifierInput[] memory logicVerifierInputs = transaction.actions[params.actionIdx].logicVerifierInputs; - // Do a linear search to identify the corresponding logic verifier input - for (uint256 i = 0; i < logicVerifierInputs.length; i++) { - // Select the logic verifier input with a tag matching the nullifier - if (logicVerifierInputs[i].tag == complianceVerifierInput.instance.consumed.nullifier) { - // Finally, corrupt the logic verifier input tag so it can no longer be found - logicVerifierInputs[i].tag = params.tag; - } - } - // With an unknown tag, we expect failure - vm.expectRevert( - abi.encodeWithSelector( - Logic.TagNotFound.selector, complianceVerifierInputs[params.inputIdx].instance.consumed.nullifier - ) - ); - // Finally, execute the transaction to make sure that it fails - this.execute(transaction); - } - - /// @notice Test that transactions with unknown nullifier tags fail - function testFuzz_execute_unknown_nullifier_tag_fails( - uint8 actionCount, - uint8 complianceUnitCount, - UnknownTagFailsParams memory params - ) public { - TxGen.ActionConfig[] memory configs = TxGen.generateActionConfigs({ - actionCount: uint8(bound(actionCount, 1, 5)), - complianceUnitCount: uint8(bound(complianceUnitCount, 1, 5)) - }); - - (Transaction memory txn,) = vm.transaction({mockVerifier: _mockVerifier, nonce: 0, configs: configs}); - mutationTestExecuteUnknownNullifierTagFails(txn, params); - } - - /// @notice Take a transaction that would execute successfully and make it - /// fail by ensuring that the commitment of one of its compliance verifier - /// inputs is not found in the logic verifier inputs. - /// @param transaction A successful transaction - /// @param params A failure inducing modification - function mutationTestExecuteUnknownCommitmentTagFails( - Transaction memory transaction, - UnknownTagFailsParams memory params - ) public { - // Wrap the action index into range - params.actionIdx = params.actionIdx % transaction.actions.length; - Compliance.VerifierInput[] memory complianceVerifierInputs = - transaction.actions[params.actionIdx].complianceVerifierInputs; + Action calldata actionCalldata = transactionCalldata.actions[params.actionIdx]; + Action memory action = transaction.actions[params.actionIdx]; + Compliance.VerifierInput[] memory complianceVerifierInputs = action.complianceVerifierInputs; // Wrap the compliance verifier input index into range + vm.assume(complianceVerifierInputs.length > 0); params.inputIdx = params.inputIdx % complianceVerifierInputs.length; Compliance.VerifierInput memory complianceVerifierInput = complianceVerifierInputs[params.inputIdx]; // Make sure that the planned corruption will change something - vm.assume(complianceVerifierInput.instance.created.commitment != params.tag); + bytes32 tag = params.consumed + ? complianceVerifierInput.instance.consumed.nullifier + : complianceVerifierInput.instance.created.commitment; + vm.assume(tag != params.tag); // Finally, corrupt the corresponding logic verifier input tag - Logic.VerifierInput[] memory logicVerifierInputs = transaction.actions[params.actionIdx].logicVerifierInputs; - // Do a linear search to identify the corresponding logic verifier input - for (uint256 i = 0; i < logicVerifierInputs.length; i++) { - // Select the logic verifier input with a tag matching the commitment - if (logicVerifierInputs[i].tag == complianceVerifierInput.instance.created.commitment) { - // Finally, corrupt the logic verifier input tag so it can no longer be found - logicVerifierInputs[i].tag = params.tag; - } - } + uint256 logicVerifierInputIdx = actionCalldata.logicVerifierInputs.lookup(tag); + action.logicVerifierInputs[logicVerifierInputIdx].tag = params.tag; // With an unknown tag, we expect failure - vm.expectRevert( - abi.encodeWithSelector( - Logic.TagNotFound.selector, complianceVerifierInputs[params.inputIdx].instance.created.commitment - ) - ); + vm.expectRevert(abi.encodeWithSelector(Logic.TagNotFound.selector, tag)); // Finally, execute the transaction to make sure that it fails - this.execute(transaction); + _mockPa.execute(transaction); } - /// @notice Test that transactions with unknown commitment tags fail - function testFuzz_execute_unknown_commitment_tag_fails( - uint8 actionCount, - uint8 complianceUnitCount, - UnknownTagFailsParams memory params + /// @notice Test that transactions with unknown tags fail + function testFuzz_execute_unknown_tag_fails( + TxGen.TransactionParams memory txParams, + UnknownTagFailsParams memory mutParams ) public { - TxGen.ActionConfig[] memory configs = TxGen.generateActionConfigs({ - actionCount: uint8(bound(actionCount, 1, 5)), - complianceUnitCount: uint8(bound(complianceUnitCount, 1, 5)) - }); - - (Transaction memory txn,) = vm.transaction({mockVerifier: _mockVerifier, nonce: 0, configs: configs}); - mutationTestExecuteUnknownCommitmentTagFails(txn, params); + this.mutationTestExecuteUnknownTagFails(vm.transaction(_mockVerifier, txParams), mutParams); } - /// @notice Take a transaction that would execute successfully and make it - /// fail by ensuring that it has less compliance verifier inputs than half - /// the logic verifier inputs. - /// @param transaction A successful transaction - /// @param params A failure inducing modification + /// @notice Make transaction fail by ensuring that it has less compliance verifier inputs function mutationTestExecuteMissingComplianceVerifierInputFail( Transaction memory transaction, - MismatchingResourcesFailParams memory params + GenericFailParams memory params ) public { // Wrap the action index into range + vm.assume(transaction.actions.length > 0); params.actionIdx = params.actionIdx % transaction.actions.length; Action memory action = transaction.actions[params.actionIdx]; Compliance.VerifierInput[] memory complianceVerifierInputs = action.complianceVerifierInputs; // Wrap the compliance verifier input index into range + vm.assume(complianceVerifierInputs.length > 0); params.inputIdx = params.inputIdx % complianceVerifierInputs.length; // Now delete the array entry // Replace the target position with the last element @@ -579,41 +454,34 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { transaction.actions[params.actionIdx].complianceVerifierInputs = shorter; // With mismatching resource counts, we expect failure vm.expectRevert( - abi.encodeWithSelector(TagCountMismatch.selector, action.logicVerifierInputs.length, shorter.length * 2) + abi.encodeWithSelector( + ProtocolAdapter.TagCountMismatch.selector, action.logicVerifierInputs.length, shorter.length * 2 + ) ); // Finally, execute the transaction to make sure that it fails - this.execute(transaction); + _mockPa.execute(transaction); } /// @notice Test that transactions with a missing compliance verifier input fail function testFuzz_execute_missing_compliance_verifier_input_fail( - uint8 actionCount, - uint8 complianceUnitCount, - MismatchingResourcesFailParams memory params + TxGen.TransactionParams memory txParams, + GenericFailParams memory mutParams ) public { - TxGen.ActionConfig[] memory configs = TxGen.generateActionConfigs({ - actionCount: uint8(bound(actionCount, 1, 5)), - complianceUnitCount: uint8(bound(complianceUnitCount, 1, 5)) - }); - - (Transaction memory txn,) = vm.transaction({mockVerifier: _mockVerifier, nonce: 0, configs: configs}); - mutationTestExecuteMissingComplianceVerifierInputFail(txn, params); + mutationTestExecuteMissingComplianceVerifierInputFail(vm.transaction(_mockVerifier, txParams), mutParams); } - /// @notice Take a transaction that would execute successfully and make it - /// fail by ensuring that it has less logic verifier inputs than the number - /// of compliance verifier inputs doubled. - /// @param transaction A successful transaction - /// @param params A failure inducing modification + /// @notice Make transaction fail by ensuring that it has less logic verifier inputs function mutationTestExecuteMissingLogicVerifierInputFail( Transaction memory transaction, - MismatchingResourcesFailParams memory params + GenericFailParams memory params ) public { // Wrap the action index into range + vm.assume(transaction.actions.length > 0); params.actionIdx = params.actionIdx % transaction.actions.length; Action memory action = transaction.actions[params.actionIdx]; Logic.VerifierInput[] memory logicVerifierInputs = action.logicVerifierInputs; // Wrap the logic verifier input index into range + vm.assume(logicVerifierInputs.length > 0); params.inputIdx = params.inputIdx % logicVerifierInputs.length; // Now delete the array entry // Replace the target position with the last element @@ -628,82 +496,71 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { // With mismatching resource counts, we expect failure vm.expectRevert( abi.encodeWithSelector( - TagCountMismatch.selector, shorter.length, action.complianceVerifierInputs.length * 2 + ProtocolAdapter.TagCountMismatch.selector, shorter.length, action.complianceVerifierInputs.length * 2 ) ); // Finally, execute the transaction to make sure that it fails - this.execute(transaction); + _mockPa.execute(transaction); } /// @notice Test that transactions with a missing logic verifier input fail function testFuzz_execute_missing_logic_verifier_input_fail( - uint8 actionCount, - uint8 complianceUnitCount, - MismatchingResourcesFailParams memory params + TxGen.TransactionParams memory txParams, + GenericFailParams memory mutParams ) public { - TxGen.ActionConfig[] memory configs = TxGen.generateActionConfigs({ - actionCount: uint8(bound(actionCount, 1, 5)), - complianceUnitCount: uint8(bound(complianceUnitCount, 1, 5)) - }); - - (Transaction memory txn,) = vm.transaction({mockVerifier: _mockVerifier, nonce: 0, configs: configs}); - mutationTestExecuteMissingLogicVerifierInputFail(txn, params); + mutationTestExecuteMissingLogicVerifierInputFail(vm.transaction(_mockVerifier, txParams), mutParams); } - /// @notice Take a transaction that would execute successfully and make it - /// fail by ensuring that the verifying key of one of its logic verifier - /// inputs does not match the nullifier or commitment in the corresponding - /// compliance verifier input. - /// @param transaction A successful transaction - /// @param params A failure inducing modification + /// @notice Make transaction fail by making logic reference mismatch function mutationTestExecuteMismatchingLogicRefsFail( Transaction memory transaction, MismatchingLogicRefsFailParams memory params ) public { // Wrap the action index into range + vm.assume(transaction.actions.length > 0); params.actionIdx = params.actionIdx % transaction.actions.length; Logic.VerifierInput[] memory logicVerifierInputs = transaction.actions[params.actionIdx].logicVerifierInputs; // Wrap the logic verifier input index into range + vm.assume(logicVerifierInputs.length > 0); params.inputIdx = params.inputIdx % logicVerifierInputs.length; // Now corrupt the logic reference vm.assume(logicVerifierInputs[params.inputIdx].verifyingKey != params.logicRef); logicVerifierInputs[params.inputIdx].verifyingKey = params.logicRef; // With mismatching logic references, we expect failure - vm.expectPartialRevert(LogicRefMismatch.selector); + vm.expectPartialRevert(ProtocolAdapter.LogicRefMismatch.selector); // Finally, execute the transaction to make sure that it fails - this.execute(transaction); + _mockPa.execute(transaction); } /// @notice Test that transactions with mismatching logic references fail function testFuzz_execute_mismatching_logic_refs_fail( - uint8 actionCount, - uint8 complianceUnitCount, - MismatchingLogicRefsFailParams memory params + TxGen.TransactionParams memory txParams, + MismatchingLogicRefsFailParams memory mutParams ) public { - TxGen.ActionConfig[] memory configs = TxGen.generateActionConfigs({ - actionCount: uint8(bound(actionCount, 1, 5)), - complianceUnitCount: uint8(bound(complianceUnitCount, 1, 5)) - }); - - (Transaction memory txn,) = vm.transaction({mockVerifier: _mockVerifier, nonce: 0, configs: configs}); - mutationTestExecuteMismatchingLogicRefsFail(txn, params); + mutationTestExecuteMismatchingLogicRefsFail(vm.transaction(_mockVerifier, txParams), mutParams); } - /// @notice Take a transaction that would execute successfully and make it - /// fail by ensuring that one of its forwarder call outputs mismatch. - /// @param transaction A successful transaction - /// @param params A failure inducing modification + /// @notice Make transaction fail by ensuring that one of its forwarder call outputs mismatch. function mutationTestExecuteMismatchingForwarderCallOutputsFail( - Transaction memory transaction, + Transaction calldata transactionCalldata, MismatchingForwarderCallOutputsFailParams memory params ) public { + Transaction memory transaction = transactionCalldata; // Wrap the action index into range + vm.assume(transaction.actions.length > 0); params.actionIdx = params.actionIdx % transaction.actions.length; + Action calldata actionCalldata = transactionCalldata.actions[params.actionIdx]; Action memory action = transaction.actions[params.actionIdx]; - Logic.VerifierInput[] memory logicVerifierInputs = action.logicVerifierInputs; + Compliance.VerifierInput[] memory complianceVerifierInputs = action.complianceVerifierInputs; // Wrap the logic verifier input index into range - params.inputIdx = params.inputIdx % logicVerifierInputs.length; - Logic.VerifierInput memory logicVerifierInput = logicVerifierInputs[params.inputIdx]; + vm.assume(complianceVerifierInputs.length > 0); + params.inputIdx = params.inputIdx % complianceVerifierInputs.length; + Compliance.VerifierInput memory complianceVerifierInput = complianceVerifierInputs[params.inputIdx]; + bytes32 tag = params.consumed + ? complianceVerifierInput.instance.consumed.nullifier + : complianceVerifierInput.instance.created.commitment; + uint256 logicVerifierInputIdx = actionCalldata.logicVerifierInputs.lookup(tag); + Logic.VerifierInput memory logicVerifierInput = action.logicVerifierInputs[logicVerifierInputIdx]; Logic.ExpirableBlob[] memory externalPayloads = logicVerifierInput.appData.externalPayload; // Cannot do the mutation if transaction has no external payloads vm.assume(externalPayloads.length > 0); @@ -717,29 +574,20 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { ); // Re-encode the calldata and replace the value in the external payloads externalPayloads[params.payloadIdx].blob = abi.encode(untrustedForwarder, input, params.output); - // Now determine whether the current resource is being created or consumed - Compliance.VerifierInput[] memory complianceVerifierInputs = action.complianceVerifierInputs; - bool isConsumed; - for (uint256 i = 0; i < complianceVerifierInputs.length; i++) { - if (complianceVerifierInputs[i].instance.consumed.nullifier == logicVerifierInput.tag) { - isConsumed = true; - } else if ( - complianceVerifierInputs[i].instance.created.commitment == logicVerifierInputs[params.inputIdx].tag - ) { - isConsumed = false; - } - } // Compute the action tree root - bytes32 actionTreeRoot = _computeActionTreeRootMemory(action, action.complianceVerifierInputs.length); + bytes32[] memory actionTreeTags = + actionCalldata.complianceVerifierInputs.computeActionTreeTags(action.complianceVerifierInputs.length); // Recompute the logic verifier input proof - logicVerifierInputs[params.inputIdx].proof = _mockVerifier.mockProve({ - imageId: logicVerifierInputs[params.inputIdx].verifyingKey, - journalDigest: logicVerifierInputs[params.inputIdx].toJournalDigest(actionTreeRoot, isConsumed) + logicVerifierInput.proof = _mockVerifier.mockProve({ + imageId: logicVerifierInput.verifyingKey, + journalDigest: logicVerifierInput.toJournalDigest(actionTreeTags.computeRoot(), params.consumed) }).seal; // With mismatching forwarder call outputs, we expect failure - vm.expectRevert(abi.encodeWithSelector(ForwarderCallOutputMismatch.selector, params.output, expectedOutput)); + vm.expectRevert( + abi.encodeWithSelector(ProtocolAdapter.ForwarderCallOutputMismatch.selector, params.output, expectedOutput) + ); // Finally, execute the transaction to make sure that it fails - this.execute(transaction); + _mockPa.execute(transaction); } /// @notice Test that transactions with mismatching forwarder call outputs fails @@ -747,14 +595,12 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { MismatchingForwarderCallOutputsFailParams memory params ) public { bytes32 carrierLogicRef = bytes32(uint256(123)); - address fwd = - address(new ForwarderExample({protocolAdapter: address(this), calldataCarrierLogicRef: carrierLogicRef})); address fwd2 = - address(new ForwarderExample({protocolAdapter: address(this), calldataCarrierLogicRef: carrierLogicRef})); - assertNotEq(fwd, fwd2); + address(new ForwarderExample({protocolAdapter: address(_mockPa), calldataCarrierLogicRef: carrierLogicRef})); + assertNotEq(_fwd, fwd2); address[] memory fwdList = new address[](2); - fwdList[0] = fwd; + fwdList[0] = _fwd; fwdList[1] = fwd2; TxGen.ResourceAndAppData[] memory consumed = _exampleResourceAndEmptyAppData({nonce: 0}); @@ -763,29 +609,7 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { TxGen.ResourceLists[] memory resourceLists = new TxGen.ResourceLists[](1); resourceLists[0] = TxGen.ResourceLists({consumed: consumed, created: created}); Transaction memory txn = vm.transaction(_mockVerifier, resourceLists); - mutationTestExecuteMismatchingForwarderCallOutputsFail(txn, params); - } - - /// @notice Computes the action tree root of an action constituted by all its nullifiers and commitments. - /// @param action The action whose root we compute. - /// @param complianceUnitCount The number of compliance units in the action. - /// @return root The root of the corresponding tree. - function _computeActionTreeRootMemory(Action memory action, uint256 complianceUnitCount) - internal - pure - returns (bytes32 root) - { - bytes32[] memory actionTreeTags = new bytes32[](complianceUnitCount * 2); - - // The order in which the tags are added to the tree are provided by the compliance units - for (uint256 j = 0; j < complianceUnitCount; ++j) { - Compliance.VerifierInput memory complianceVerifierInput = action.complianceVerifierInputs[j]; - - actionTreeTags[2 * j] = complianceVerifierInput.instance.consumed.nullifier; - actionTreeTags[(2 * j) + 1] = complianceVerifierInput.instance.created.commitment; - } - - root = actionTreeTags.computeRoot(); + this.mutationTestExecuteMismatchingForwarderCallOutputsFail(txn, params); } function _exampleResourceAndEmptyAppData(uint256 nonce) @@ -844,15 +668,3 @@ contract ProtocolAdapterMockVerifierTest is Test, ProtocolAdapter { data[0].appData.externalPayload = externalBlobs; } } - -contract ProtocolAdapterTest is ProtocolAdapterMockVerifierTest { - constructor() ProtocolAdapterMockVerifierTest(baseArgs()) {} - - function baseArgs() public returns (ProtocolAdapterTestArgs memory args) { - RiscZeroVerifierRouter router; - RiscZeroVerifierEmergencyStop emergencyStop; - RiscZeroMockVerifier verifier; - (router, emergencyStop, verifier) = new DeployRiscZeroContractsMock().run(); - return ProtocolAdapterTestArgs({router: router, emergencyStop: emergencyStop, verifier: verifier}); - } -} diff --git a/contracts/test/libs/TxGen.sol b/contracts/test/libs/TxGen.sol index a20af6f3..a70e3ab9 100644 --- a/contracts/test/libs/TxGen.sol +++ b/contracts/test/libs/TxGen.sol @@ -19,6 +19,12 @@ library TxGen { using Logic for Logic.VerifierInput[]; using Delta for uint256[2]; + uint256 public constant MAX_ACTIONS = 4; + uint256 public constant MAX_RESOURCES = 5; + // The order of the secp256k1 curve. + uint256 internal constant SECP256K1_ORDER = + 115792089237316195423570985008687907852837564279074904382605163141518161494337; + struct ActionConfig { uint256 complianceUnitCount; } @@ -33,6 +39,18 @@ library TxGen { ResourceAndAppData[] created; } + struct ActionParams { + Resource[2][MAX_RESOURCES] resources; + uint256[MAX_RESOURCES] bijection; + uint256 targetResourcesLen; + uint256[2][MAX_RESOURCES] valueCommitmentRandomness; + } + + struct TransactionParams { + ActionParams[MAX_ACTIONS] actionParams; + uint256 targetActionsLen; + } + error ConsumedCreatedCountMismatch(uint256 nConsumed, uint256 nCreated); function complianceVerifierInput( @@ -257,6 +275,142 @@ library TxGen { txn = Transaction({actions: actions, deltaProof: proof}); } + function generateAction(VmSafe vm, RiscZeroMockVerifier mockVerifier, ActionParams memory params) + internal + returns (Action memory action, uint256 totalValueCommitmentRandomness) + { + Resource[2][] memory truncatedResources = + truncateResources(params.resources, params.targetResourcesLen % MAX_RESOURCES); + action.logicVerifierInputs = new Logic.VerifierInput[](truncatedResources.length * 2); + action.complianceVerifierInputs = new Compliance.VerifierInput[](truncatedResources.length); + // Created empty app data for all the resources + Logic.AppData memory appData = Logic.AppData({ + resourcePayload: new Logic.ExpirableBlob[](0), + discoveryPayload: new Logic.ExpirableBlob[](0), + externalPayload: new Logic.ExpirableBlob[](0), + applicationPayload: new Logic.ExpirableBlob[](0) + }); + // Match the created and consumed resources + uint256[] memory bijection = generateBijection(params.bijection, truncatedResources.length); + for (uint256 i = 0; i < truncatedResources.length; ++i) { + truncatedResources[bijection[i]][1].quantity = truncatedResources[i][0].quantity; + truncatedResources[bijection[i]][1].logicRef = truncatedResources[i][0].logicRef; + truncatedResources[bijection[i]][1].labelRef = truncatedResources[i][0].labelRef; + } + // Compute action tree tags and action tree root + bytes32[] memory actionTreeTags = new bytes32[](2 * truncatedResources.length); + totalValueCommitmentRandomness = 0; + for (uint256 i = 0; i < truncatedResources.length; ++i) { + uint256 index = (i * 2); + + actionTreeTags[index] = nullifier(truncatedResources[i][0], 0); + actionTreeTags[index + 1] = commitment(truncatedResources[i][1]); + // Adjust and accumulate the value randomness commitments + params.valueCommitmentRandomness[i][0] = + 1 + (params.valueCommitmentRandomness[i][0] % (SECP256K1_ORDER - 1)); + params.valueCommitmentRandomness[i][1] = + 1 + (params.valueCommitmentRandomness[i][1] % (SECP256K1_ORDER - 1)); + totalValueCommitmentRandomness = + addmod(totalValueCommitmentRandomness, params.valueCommitmentRandomness[i][0], SECP256K1_ORDER); + totalValueCommitmentRandomness = + addmod(totalValueCommitmentRandomness, params.valueCommitmentRandomness[i][1], SECP256K1_ORDER); + } + bytes32 actionTreeRoot = actionTreeTags.computeRoot(); + // Create logic and compliance verifier inputs + for (uint256 i = 0; i < truncatedResources.length; i++) { + Resource memory consumedResource = truncatedResources[i][0]; + Resource memory createdResource = truncatedResources[i][1]; + bytes32 nf = nullifier(consumedResource, 0); + bytes32 cm = commitment(createdResource); + + // Created logic verifier input for a consumed resource + action.logicVerifierInputs[2 * i] = + Logic.VerifierInput({tag: nf, verifyingKey: consumedResource.logicRef, proof: "", appData: appData}); + action.logicVerifierInputs[2 * i].proof = mockVerifier.mockProve({ + imageId: consumedResource.logicRef, + journalDigest: action.logicVerifierInputs[2 * i].toJournalDigest(actionTreeRoot, true) + }).seal; + // Create logic verifier input for a created resource + action.logicVerifierInputs[2 * i + 1] = + Logic.VerifierInput({tag: cm, verifyingKey: createdResource.logicRef, proof: "", appData: appData}); + action.logicVerifierInputs[2 * i + 1].proof = mockVerifier.mockProve({ + imageId: createdResource.logicRef, + journalDigest: action.logicVerifierInputs[2 * i + 1].toJournalDigest(actionTreeRoot, false) + }).seal; + // Create the delta for the consumed resource + uint256[2] memory unitDelta = DeltaGen.generateInstance( + vm, + DeltaGen.InstanceInputs({ + kind: kind(consumedResource), + quantity: consumedResource.quantity, + consumed: true, + valueCommitmentRandomness: params.valueCommitmentRandomness[i][0] + }) + ); + + // Add the delta for the created resource + unitDelta = Delta.add( + unitDelta, + DeltaGen.generateInstance( + vm, + DeltaGen.InstanceInputs({ + kind: kind(createdResource), + quantity: createdResource.quantity, + consumed: false, + valueCommitmentRandomness: params.valueCommitmentRandomness[i][1] + }) + ) + ); + // Create the compliance verifier input + Compliance.Instance memory instance = Compliance.Instance({ + unitDeltaX: bytes32(unitDelta[0]), + unitDeltaY: bytes32(unitDelta[1]), + consumed: Compliance.ConsumedRefs({ + nullifier: nf, + logicRef: consumedResource.logicRef, + commitmentTreeRoot: initialRoot() + }), + created: Compliance.CreatedRefs({commitment: cm, logicRef: createdResource.logicRef}) + }); + action.complianceVerifierInputs[i] = Compliance.VerifierInput({ + instance: instance, + proof: mockVerifier.mockProve({ + imageId: Compliance._VERIFYING_KEY, + journalDigest: instance.toJournalDigest() + }).seal + }); + } + } + + function transaction(VmSafe vm, RiscZeroMockVerifier mockVerifier, TransactionParams memory params) + internal + returns (Transaction memory txn) + { + // Generate actions + Action[] memory actions = new Action[](params.targetActionsLen % MAX_ACTIONS); + uint256 totalValueCommitmentRandomness = 0; + for (uint256 i = 0; i < actions.length; i++) { + uint256 valueCommitmentRandomness; + (actions[i], valueCommitmentRandomness) = generateAction(vm, mockVerifier, params.actionParams[i]); + totalValueCommitmentRandomness = + addmod(totalValueCommitmentRandomness, valueCommitmentRandomness, SECP256K1_ORDER); + } + // Generate delta proof + bytes memory proof = ""; + bytes32[] memory tags = TxGen.collectTags(actions); + if (tags.length != 0) { + proof = DeltaGen.generateProof( + vm, + DeltaGen.ProofInputs({ + valueCommitmentRandomness: totalValueCommitmentRandomness, + verifyingKey: Delta.computeVerifyingKey(tags) + }) + ); + } + // Generate transaction + txn = Transaction({actions: actions, deltaProof: proof}); + } + function logicVerifierInput( RiscZeroMockVerifier mockVerifier, bytes32 actionTreeRoot, @@ -378,4 +532,43 @@ library TxGen { function initialRoot() internal pure returns (bytes32 root) { root = SHA256.EMPTY_HASH; } + + function truncateResources(Resource[2][MAX_RESOURCES] memory resources, uint256 len) + internal + pure + returns (Resource[2][] memory truncatedResources) + { + truncatedResources = new Resource[2][](len); + for (uint256 i = 0; i < len; i++) { + truncatedResources[i][0] = resources[i][0]; + truncatedResources[i][1] = resources[i][1]; + } + } + + function generateBijection(uint256[MAX_RESOURCES] memory input, uint256 len) + internal + pure + returns (uint256[] memory output) + { + output = new uint256[](len); + uint256[] memory duplicates = new uint256[](len); + uint256 duplicateCount = 0; + for (uint256 i = 0; i < len; i++) { + output[i] = duplicates[i] = len; + input[i] %= len; + } + for (uint256 i = 0; i < len; i++) { + if (output[input[i]] == len) { + output[input[i]] = i; + } else { + duplicates[duplicateCount++] = i; + } + } + duplicateCount = 0; + for (uint256 i = 0; i < len; i++) { + if (output[i] == len) { + output[i] = duplicates[duplicateCount++]; + } + } + } }