diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 81b60a7..75f19ef 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -12,18 +12,18 @@ contract Deploy is BaseScript { IRiscZeroVerifier trustedSepoliaVerifier = IRiscZeroVerifier(vm.parseAddress(vm.readLine(path))); - bytes32 logicCircuitID = vm.parseBytes32(vm.readLine(path)); - bytes32 complianceCircuitID = vm.parseBytes32(vm.readLine(path)); - uint8 treeDepth = uint8(vm.parseUint(vm.readLine(path))); + uint8 commitmentTreeDepth = uint8(vm.parseUint(vm.readLine(path))); + + uint8 actionTreeDepth = uint8(vm.parseUint(vm.readLine(path))); protocolAdapter = address( new ProtocolAdapter{salt: sha256("ProtocolAdapter")}({ riscZeroVerifier: trustedSepoliaVerifier, - logicCircuitID: logicCircuitID, complianceCircuitID: complianceCircuitID, - treeDepth: treeDepth + commitmentTreeDepth: commitmentTreeDepth, + actionTreeDepth: actionTreeDepth }) ); } diff --git a/script/constructor-args.txt b/script/constructor-args.txt index 2c8ca23..3eb4281 100644 --- a/script/constructor-args.txt +++ b/script/constructor-args.txt @@ -1,4 +1,4 @@ 0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187 0x0000000000000000000000000000000000000000000000000000000000000000 -0x0000000000000000000000000000000000000000000000000000000000000000 -32 \ No newline at end of file +32 +4 \ No newline at end of file diff --git a/src/ERC20Forwarder.sol b/src/ERC20Forwarder.sol index f1fd934..342e2bf 100644 --- a/src/ERC20Forwarder.sol +++ b/src/ERC20Forwarder.sol @@ -19,14 +19,7 @@ contract ERC20Forwarder is ForwarderBase { _ERC20_CONTRACT = erc20; } - // TODO make generic proxy, allow native ETH transfers function _forwardCall(bytes calldata input) internal override returns (bytes memory output) { output = _ERC20_CONTRACT.functionCall(input); } - - // Native ETH transfer - // TODO! The msg.sender must call directly, but the protocol adapter is the caller. This won't work. - //receive() external payable { - // emit NativeTokenDeposited(msg.sender, msg.value); - //} } diff --git a/src/ProtocolAdapter.sol b/src/ProtocolAdapter.sol index 51d00fa..7940529 100644 --- a/src/ProtocolAdapter.sol +++ b/src/ProtocolAdapter.sol @@ -2,37 +2,29 @@ pragma solidity ^0.8.27; import {ReentrancyGuardTransient} from "@openzeppelin-contracts/utils/ReentrancyGuardTransient.sol"; -import {EnumerableSet} from "@openzeppelin-contracts/utils/structs/EnumerableSet.sol"; import {IRiscZeroVerifier as TrustedRiscZeroVerifier} from "@risc0-ethereum/IRiscZeroVerifier.sol"; -import {MockDelta} from "../test/mocks/MockDelta.sol"; // TODO remove - import {IForwarder} from "./interfaces/IForwarder.sol"; import {IProtocolAdapter} from "./interfaces/IProtocolAdapter.sol"; -import {AppData} from "./libs/AppData.sol"; -import {ArrayLookup} from "./libs/ArrayLookup.sol"; import {ComputableComponents} from "./libs/ComputableComponents.sol"; -import {Reference} from "./libs/Reference.sol"; +import {MerkleTree} from "./libs/MerkleTree.sol"; import {Delta} from "./proving/Delta.sol"; import {LogicProofs} from "./proving/Logic.sol"; import {BlobStorage} from "./state/BlobStorage.sol"; import {CommitmentAccumulator} from "./state/CommitmentAccumulator.sol"; + import {NullifierSet} from "./state/NullifierSet.sol"; import { Action, ForwarderCalldata, Resource, - TagAppDataPair, Transaction, - LogicInstance, TagLogicProofPair, - LogicRefProofPair, - ComplianceUnit, - DeletionCriterion, - ExpirableBlob + LogicProof, + ComplianceUnit } from "./Types.sol"; contract ProtocolAdapter is @@ -42,40 +34,36 @@ contract ProtocolAdapter is NullifierSet, BlobStorage { - using ArrayLookup for bytes32[]; using ComputableComponents for Resource; - using Reference for bytes; - using AppData for TagAppDataPair[]; using LogicProofs for TagLogicProofPair[]; - using EnumerableSet for EnumerableSet.Bytes32Set; TrustedRiscZeroVerifier internal immutable _TRUSTED_RISC_ZERO_VERIFIER; bytes32 internal immutable _COMPLIANCE_CIRCUIT_ID; - bytes32 internal immutable _LOGIC_CIRCUIT_ID; + uint8 internal immutable _ACTION_TREE_DEPTH; uint256 private _txCount; - event TransactionExecuted(uint256 indexed id, Transaction transaction); - error InvalidRootRef(bytes32 root); error InvalidNullifierRef(bytes32 nullifier); error InvalidCommitmentRef(bytes32 commitment); error ForwarderCallOutputMismatch(bytes expected, bytes actual); + error RootMismatch(bytes32 expected, bytes32 actual); + error LogicRefMismatch(bytes32 expected, bytes32 actual); + error CalldataCarrierKindMismatch(bytes32 expected, bytes32 actual); error CalldataCarrierAppDataMismatch(bytes32 expected, bytes32 actual); error CalldataCarrierLabelMismatch(bytes32 expected, bytes32 actual); error CalldataCarrierCommitmentNotFound(bytes32 commitment); - error TransactionUnbalanced(uint256 expected, uint256 actual); constructor( TrustedRiscZeroVerifier riscZeroVerifier, - bytes32 logicCircuitID, bytes32 complianceCircuitID, - uint8 treeDepth - ) CommitmentAccumulator(treeDepth) { + uint8 commitmentTreeDepth, + uint8 actionTreeDepth + ) CommitmentAccumulator(commitmentTreeDepth) { _TRUSTED_RISC_ZERO_VERIFIER = riscZeroVerifier; - _LOGIC_CIRCUIT_ID = logicCircuitID; + _ACTION_TREE_DEPTH = actionTreeDepth; _COMPLIANCE_CIRCUIT_ID = complianceCircuitID; } @@ -86,33 +74,32 @@ contract ProtocolAdapter is emit TransactionExecuted({id: ++_txCount, transaction: transaction}); - uint256 n = transaction.actions.length; - uint256 m; - uint256 j; bytes32 newRoot = 0; - for (uint256 i = 0; i < n; ++i) { - Action calldata action = transaction.actions[i]; - m = action.tagAppDataPairs.length; - for (j = 0; j < m; ++j) { - _storeBlob(action.tagAppDataPairs[j].appData); - } + uint256 nActions = transaction.actions.length; + for (uint256 i = 0; i < nActions; ++i) { + Action calldata action = transaction.actions[i]; - m = action.nullifiers.length; - for (j = 0; j < m; ++j) { - // Nullifier non-existence was already checked in `_verify(transaction);` at the top. - _addNullifierUnchecked(action.nullifiers[j]); - } + uint256 nResources = action.tagLogicProofPairs.length; + for (uint256 j = 0; j < nResources; ++j) { + TagLogicProofPair calldata pair = action.tagLogicProofPairs[j]; - m = action.commitments.length; + if (pair.logicProof.instance.isConsumed) { + // Nullifier non-existence was already checked in `_verify(transaction);` at the top. + _addNullifierUnchecked(pair.tag); + } else { + // Commitment non-existence was already checked in `_verify(transaction);` at the top. + newRoot = _addCommitmentUnchecked(pair.tag); + } - for (j = 0; j < m; ++j) { - // Commitment non-existence was already checked in `_verify(transaction);` at the top. - newRoot = _addCommitmentUnchecked(action.commitments[j]); + uint256 nBlobs = pair.logicProof.instance.appData.length; + for (uint256 k = 0; k < nBlobs; ++j) { + _storeBlob(pair.logicProof.instance.appData[k]); + } } - m = action.resourceCalldataPairs.length; - for (j = 0; j < m; ++j) { + uint256 nForwarderCalls = action.resourceCalldataPairs.length; + for (uint256 j = 0; j < nForwarderCalls; ++j) { _executeForwarderCall(action.resourceCalldataPairs[j].call); } } @@ -125,7 +112,6 @@ contract ProtocolAdapter is _verify(transaction); } - // TODO Consider DoS attacks https://detectors.auditbase.com/avoid-external-calls-in-unbounded-loops-solidity // slither-disable-next-line calls-loop function _executeForwarderCall(ForwarderCalldata calldata call) internal { bytes memory output = IForwarder(call.untrustedForwarder).forwardCall(call.input); @@ -138,138 +124,121 @@ contract ProtocolAdapter is // solhint-disable-next-line function-max-lines // slither-disable-next-line calls-loop function _verify(Transaction calldata transaction) internal view { - // Can also be named DeltaHash (which is what Yulia does). uint256[2] memory transactionDelta = Delta.zero(); - // Helper variable - uint256 resourceCount = 0; - uint256 nActions = transaction.actions.length; - for (uint256 i; i < nActions; ++i) { - resourceCount += transaction.actions[i].commitments.length; - resourceCount += transaction.actions[i].nullifiers.length; + + uint256 resCounter = 0; + for (uint256 i = 0; i < nActions; ++i) { + resCounter += transaction.actions[i].tagLogicProofPairs.length; } - bytes32[] memory tags = new bytes32[](resourceCount); - // Reset resource count for later use. - resourceCount = 0; + // Allocate the array. + bytes32[] memory tags = new bytes32[](resCounter); + + // Reset the resource counter. + resCounter = 0; - uint256 len; for (uint256 i; i < nActions; ++i) { Action calldata action = transaction.actions[i]; _verifyForwarderCalls(action); // Compliance Proofs - len = action.complianceUnits.length; - for (uint256 j; j < len; ++j) { - ComplianceUnit calldata unit = action.complianceUnits[j]; - - // Check consumed resources - // TODO This check can be removed after Xuyang's and Artem's specs change proposal gets merged. - if (!transaction.roots.contains(unit.instance.consumed.rootRef)) { - revert InvalidRootRef(unit.instance.consumed.rootRef); - } - _checkRootPreExistence(unit.instance.consumed.rootRef); + { + uint256 nCUs = action.complianceUnits.length; + for (uint256 j = 0; j < nCUs; ++j) { + ComplianceUnit calldata unit = action.complianceUnits[j]; - // TODO This check can be removed after Xuyang's and Artem's specs change proposal gets merged. - if (!action.nullifiers.contains(unit.instance.consumed.nullifierRef)) { - revert InvalidNullifierRef(unit.instance.consumed.nullifierRef); - } - _checkNullifierNonExistence(unit.instance.consumed.nullifierRef); + // Check consumed resources + _checkRootPreExistence(unit.instance.consumed.root); + _checkNullifierNonExistence(unit.instance.consumed.nullifier); - // Check created resources - // TODO This check can be removed after Xuyang's and Artem's specs change proposal gets merged. - if (!action.commitments.contains(unit.instance.created.commitmentRef)) { - revert InvalidCommitmentRef(unit.instance.created.commitmentRef); - } - _checkCommitmentNonExistence(unit.instance.created.commitmentRef); + // Check created resources + _checkCommitmentNonExistence(unit.instance.created.commitment); - _TRUSTED_RISC_ZERO_VERIFIER.verify({ - seal: unit.proof, - imageId: _COMPLIANCE_CIRCUIT_ID, - journalDigest: sha256(abi.encode(unit.verifyingKey, unit.instance)) - }); + _TRUSTED_RISC_ZERO_VERIFIER.verify({ + seal: unit.proof, + imageId: _COMPLIANCE_CIRCUIT_ID, + journalDigest: sha256(abi.encode(unit.verifyingKey, unit.instance)) + }); - // Prepare delta proof - transactionDelta = Delta.add({p1: transactionDelta, p2: unit.instance.unitDelta}); + // Check the logic ref consistency + { + bytes32 nf = unit.instance.consumed.nullifier; + LogicProof calldata logicProof = action.tagLogicProofPairs.lookup(nf); + + if (unit.instance.consumed.logicRef != logicProof.logicRef) { + revert LogicRefMismatch({ + expected: logicProof.logicRef, + actual: unit.instance.consumed.logicRef + }); + } + // solhint-disable-next-line gas-increment-by-one + tags[resCounter++] = nf; + } + { + bytes32 cm = unit.instance.created.commitment; + LogicProof calldata logicProof = action.tagLogicProofPairs.lookup(cm); + + if (unit.instance.created.logicRef != logicProof.logicRef) { + revert LogicRefMismatch({ + expected: logicProof.logicRef, + actual: unit.instance.created.logicRef + }); + } + // solhint-disable-next-line gas-increment-by-one + tags[resCounter++] = cm; + } + + // Prepare delta proof + transactionDelta = Delta.add({p1: transactionDelta, p2: unit.instance.unitDelta}); + } } // Logic Proofs - LogicInstance memory instance = LogicInstance({ - tag: bytes32(0), - isConsumed: true, - consumed: action.nullifiers, - created: action.commitments, - tagSpecificAppData: ExpirableBlob({deletionCriterion: DeletionCriterion.Immediately, blob: bytes("")}) - }); - LogicRefProofPair memory logicRefProofPair; - - // Check consumed resources - len = action.nullifiers.length; - for (uint256 j; j < len; ++j) { - bytes32 tag = action.nullifiers[j]; - - tags[j] = tag; - ++resourceCount; - - instance.tag = tag; - instance.tagSpecificAppData = action.tagAppDataPairs.lookupCalldata(tag); - - { - logicRefProofPair = action.logicProofs.lookup(tag); + { + uint256 nResources = action.tagLogicProofPairs.length; - _TRUSTED_RISC_ZERO_VERIFIER.verify({ - seal: logicRefProofPair.proof, - imageId: _LOGIC_CIRCUIT_ID, - journalDigest: sha256(abi.encode( /*verifying key*/ logicRefProofPair.logicRef, instance)) - }); + bytes32[] memory actionTags = new bytes32[](nResources); + for (uint256 j = 0; j < nResources; ++j) { + actionTags[j] = action.tagLogicProofPairs[j].tag; } - } - // Check created resources - instance.isConsumed = false; - - len = action.commitments.length; - for (uint256 j; j < len; ++j) { - bytes32 tag = action.commitments[j]; + bytes32 computedActionTreeRoot = MerkleTree.computeRoot(actionTags, _ACTION_TREE_DEPTH); - tags[action.nullifiers.length + j] = tag; - ++resourceCount; + for (uint256 j = 0; j < nResources; ++j) { + LogicProof calldata proof = action.tagLogicProofPairs[j].logicProof; - instance.tag = tag; - instance.tagSpecificAppData = action.tagAppDataPairs.lookup(tag); + // Check root consistency + if (proof.instance.root != computedActionTreeRoot) { + revert RootMismatch({expected: computedActionTreeRoot, actual: proof.instance.root}); + } - { - logicRefProofPair = action.logicProofs.lookup(tag); _TRUSTED_RISC_ZERO_VERIFIER.verify({ - seal: logicRefProofPair.proof, - imageId: _LOGIC_CIRCUIT_ID, - journalDigest: sha256(abi.encode( /*verifying key*/ logicRefProofPair.logicRef, instance)) + seal: proof.proof, + imageId: proof.logicRef, + journalDigest: sha256(abi.encode(proof.instance)) }); } } } // Delta Proof - // TODO: THIS IS A TEMPORARY MOCK PROOF AND MUST BE REMOVED. - // NOTE: The `transactionHash(tags)` and `transactionDelta` are not used here. - _transactionHash(tags); - MockDelta.verify({deltaProof: transaction.deltaProof}); - /* - Delta.verify({ - transactionHash: _transactionHash(tags), - transactionDelta: transactionDelta, - deltaProof: transaction.deltaProof - }); - */ + { + Delta.verify({ + transactionHash: sha256(abi.encode(tags)), + transactionDelta: transactionDelta, + deltaProof: transaction.deltaProof + }); + } } // slither-disable-next-line calls-loop function _verifyForwarderCalls(Action calldata action) internal view { - uint256 len = action.resourceCalldataPairs.length; - for (uint256 j; j < len; ++j) { - Resource calldata carrier = action.resourceCalldataPairs[j].carrier; - ForwarderCalldata calldata call = action.resourceCalldataPairs[j].call; + uint256 nForwarderCalls = action.resourceCalldataPairs.length; + for (uint256 i = 0; i < nForwarderCalls; ++i) { + Resource calldata carrier = action.resourceCalldataPairs[i].carrier; + ForwarderCalldata calldata call = action.resourceCalldataPairs[i].call; // Kind integrity check { @@ -286,7 +255,9 @@ contract ProtocolAdapter is { bytes32 expectedAppDataHash = keccak256(abi.encode(call.untrustedForwarder, call.input, call.output)); - bytes32 actualAppDataHash = keccak256(action.tagAppDataPairs.lookup(carrier.commitment()).blob); + // Lookup the first appData entry. + bytes32 actualAppDataHash = + keccak256(abi.encode(action.tagLogicProofPairs.lookup(carrier.commitment()).instance.appData[0])); if (actualAppDataHash != expectedAppDataHash) { revert CalldataCarrierAppDataMismatch({actual: actualAppDataHash, expected: expectedAppDataHash}); @@ -294,8 +265,4 @@ contract ProtocolAdapter is } } } - - function _transactionHash(bytes32[] memory tags) internal pure returns (bytes32 txHash) { - txHash = sha256(abi.encode(tags)); - } } diff --git a/src/Types.sol b/src/Types.sol index 72d2f18..334f5b8 100644 --- a/src/Types.sol +++ b/src/Types.sol @@ -23,36 +23,38 @@ struct Resource { } struct Transaction { - bytes32[] roots; Action[] actions; + // DeltaProof deltaProof bytes deltaProof; } struct Action { - bytes32[] commitments; - bytes32[] nullifiers; - TagLogicProofPair[] logicProofs; + TagLogicProofPair[] tagLogicProofPairs; ComplianceUnit[] complianceUnits; - TagAppDataPair[] tagAppDataPairs; ResourceForwarderCalldataPair[] resourceCalldataPairs; } +//struct DeltaProof { +// bytes delta; // Type: DeltaHash +// bytes32 deltaVerifyingKey; // NOTE by Xuyang: This is currently not //used in SRM. +//} + struct LogicInstance { bytes32 tag; bool isConsumed; - bytes32[] consumed; - bytes32[] created; - ExpirableBlob tagSpecificAppData; + bytes32 root; + ExpirableBlob[] appData; } -struct TagLogicProofPair { - bytes32 tag; - LogicRefProofPair pair; +struct LogicProof { + bytes proof; + LogicInstance instance; + bytes32 logicRef; // logicVerifyingKeyOuter; } -struct LogicRefProofPair { - bytes32 logicRef; - bytes proof; +struct TagLogicProofPair { + bytes32 tag; + LogicProof logicProof; } struct ComplianceUnit { @@ -68,13 +70,13 @@ struct ComplianceInstance { } struct ConsumedRefs { - bytes32 nullifierRef; - bytes32 rootRef; + bytes32 nullifier; + bytes32 root; bytes32 logicRef; } struct CreatedRefs { - bytes32 commitmentRef; + bytes32 commitment; bytes32 logicRef; } @@ -93,8 +95,3 @@ struct ForwarderCalldata { bytes input; bytes output; } - -struct TagAppDataPair { - bytes32 tag; - ExpirableBlob appData; -} diff --git a/src/interfaces/IProtocolAdapter.sol b/src/interfaces/IProtocolAdapter.sol index 9ac7f7e..1095308 100644 --- a/src/interfaces/IProtocolAdapter.sol +++ b/src/interfaces/IProtocolAdapter.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.27; import {Transaction} from "../Types.sol"; interface IProtocolAdapter { + event TransactionExecuted(uint256 indexed id, Transaction transaction); + /// @notice Executes a transaction by adding the commitments and nullifiers to the commitment tree and nullifier /// set, respectively. /// @param transaction The transaction to execute. diff --git a/src/libs/AppData.sol b/src/libs/AppData.sol deleted file mode 100644 index f4ae50d..0000000 --- a/src/libs/AppData.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.27; - -import {TagAppDataPair, ExpirableBlob} from "../Types.sol"; - -library AppData { - error AppDataTagNotFound(bytes32 tag); - error AppDataIndexOutBounds(uint256 index, uint256 max); - - function lookupCalldata(TagAppDataPair[] calldata map, bytes32 tag) - internal - pure - returns (ExpirableBlob memory appData) - { - uint256 len = map.length; - for (uint256 i = 0; i < len; ++i) { - if (map[i].tag == tag) { - return appData = map[i].appData; - } - } - revert AppDataTagNotFound(tag); - } - - function lookup(TagAppDataPair[] memory map, bytes32 tag) internal pure returns (ExpirableBlob memory appData) { - uint256 len = map.length; - for (uint256 i = 0; i < len; ++i) { - if (map[i].tag == tag) { - return appData = map[i].appData; - } - } - revert AppDataTagNotFound(tag); - } - - function at(TagAppDataPair[] calldata map, uint256 index) internal pure returns (ExpirableBlob memory appData) { - uint256 lastIndex = map.length - 1; - if (index > lastIndex) { - revert AppDataIndexOutBounds({index: index, max: lastIndex}); - } - appData = map[index].appData; - } -} diff --git a/src/libs/ArrayLookup.sol b/src/libs/ArrayLookup.sol index 06d1ecd..0e61b27 100644 --- a/src/libs/ArrayLookup.sol +++ b/src/libs/ArrayLookup.sol @@ -2,10 +2,12 @@ pragma solidity ^0.8.27; library ArrayLookup { - function contains(bytes32[] memory set, bytes32 tag) internal pure returns (bool success) { + error ElementNotFound(bytes32 tag); + + function contains(bytes32[] memory set, bytes32 elem) internal pure returns (bool success) { uint256 len = set.length; for (uint256 i = 0; i < len; ++i) { - if (set[i] == tag) { + if (set[i] == elem) { return success = true; } } diff --git a/src/state/MerkleTree.sol b/src/libs/MerkleTree.sol similarity index 89% rename from src/state/MerkleTree.sol rename to src/libs/MerkleTree.sol index ecdfb39..5fb254d 100644 --- a/src/state/MerkleTree.sol +++ b/src/libs/MerkleTree.sol @@ -17,10 +17,6 @@ library MerkleTree { bytes32[] _zeros; } - /// @notice The hash representing the empty leaf that is not expected to be part of the tree. - /// @dev Obtained from `sha256("EMPTY_LEAF")`. - bytes32 internal constant _EMPTY_LEAF_HASH = 0x283d1bb3a401a7e0302d0ffb9102c8fc1f4730c2715a2bfd46a9d9209d5965e0; - error TreeCapacityExceeded(); error NonExistentLeafIndex(uint256 index); @@ -32,7 +28,7 @@ library MerkleTree { function setup(Tree storage self, uint8 treeDepth) internal returns (bytes32 initialRoot) { Arrays.unsafeSetLength(self._zeros, treeDepth); - bytes32 currentZero = _EMPTY_LEAF_HASH; + bytes32 currentZero = SHA256.EMPTY_HASH; for (uint256 i = 0; i < treeDepth; ++i) { Arrays.unsafeAccess(self._zeros, i).value = currentZero; @@ -189,4 +185,29 @@ library MerkleTree { function isLeftSibling(uint256 directionBits, uint256 d) internal pure returns (bool isLeft) { isLeft = (directionBits >> d) & 1 == 0; } + + function computeRoot(bytes32[] memory leafs, uint256 treeDepth) internal pure returns (bytes32 root) { + uint256 totalLeafs = 1 << treeDepth; // 2^treeDepth + + // Create array of full leaf set with padding if necessary + bytes32[] memory nodes = new bytes32[](totalLeafs); + for (uint256 i = 0; i < totalLeafs; ++i) { + if (i < leafs.length) { + nodes[i] = leafs[i]; + } else { + nodes[i] = SHA256.EMPTY_HASH; + } + } + + // Build the tree upward + uint256 currentSize = totalLeafs; + while (currentSize > 1) { + for (uint256 i = 0; i < currentSize / 2; ++i) { + nodes[i] = sha256(abi.encodePacked(nodes[2 * i], nodes[2 * i + 1])); + } + currentSize /= 2; + } + + root = nodes[0]; // root + } } diff --git a/src/libs/Reference.sol b/src/libs/Reference.sol deleted file mode 100644 index 52d4689..0000000 --- a/src/libs/Reference.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.27; - -library Reference { - function toRef(address addr) internal pure returns (bytes32 ref) { - ref = sha256(abi.encode(addr)); - } - - function toRef(bytes calldata data) internal pure returns (bytes32 ref) { - ref = sha256(data); - } - - function toRefCalldata(bytes memory data) internal pure returns (bytes32 ref) { - ref = sha256(data); - } -} diff --git a/src/libs/SHA256.sol b/src/libs/SHA256.sol index 88ab818..5ee1a58 100644 --- a/src/libs/SHA256.sol +++ b/src/libs/SHA256.sol @@ -2,6 +2,10 @@ pragma solidity ^0.8.27; library SHA256 { + /// @notice The hash representing the empty leaf that is not expected to be part of the tree. + /// @dev Obtained from `sha256("EMPTY")`. + bytes32 public constant EMPTY_HASH = 0xcc1d2f838445db7aec431df9ee8a871f40e7aa5e064fc056633ef8c60fab7b06; + function hash(bytes32 a) internal pure returns (bytes32 ha) { ha = sha256(abi.encode(a)); } diff --git a/src/proving/Logic.sol b/src/proving/Logic.sol index d72143d..e98ff41 100644 --- a/src/proving/Logic.sol +++ b/src/proving/Logic.sol @@ -1,35 +1,27 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.27; -import {TagLogicProofPair, LogicRefProofPair} from "../Types.sol"; +import {TagLogicProofPair, LogicProof} from "../Types.sol"; library LogicProofs { error LogicProofTagNotFound(bytes32 tag); error LogicProofIndexOutBounds(uint256 index, uint256 max); - function lookup(TagLogicProofPair[] calldata map, bytes32 tag) - internal - pure - returns (LogicRefProofPair memory elem) - { + function lookup(TagLogicProofPair[] calldata map, bytes32 tag) internal pure returns (LogicProof calldata elem) { uint256 len = map.length; for (uint256 i = 0; i < len; ++i) { if (map[i].tag == tag) { - return elem = (map[i].pair); + return elem = (map[i].logicProof); } } revert LogicProofTagNotFound(tag); } - function at(TagLogicProofPair[] calldata map, uint256 index) - internal - pure - returns (LogicRefProofPair memory elem) - { + function at(TagLogicProofPair[] calldata map, uint256 index) internal pure returns (LogicProof memory elem) { uint256 lastIndex = map.length - 1; if (index > lastIndex) { revert LogicProofIndexOutBounds({index: index, max: lastIndex}); } - elem = map[index].pair; + elem = map[index].logicProof; } } diff --git a/src/state/CommitmentAccumulator.sol b/src/state/CommitmentAccumulator.sol index 34f2e19..509ea6c 100644 --- a/src/state/CommitmentAccumulator.sol +++ b/src/state/CommitmentAccumulator.sol @@ -5,7 +5,7 @@ import {Arrays} from "@openzeppelin-contracts/utils/Arrays.sol"; import {EnumerableSet} from "@openzeppelin-contracts/utils/structs/EnumerableSet.sol"; import {ICommitmentAccumulator} from "../interfaces/ICommitmentAccumulator.sol"; -import {MerkleTree} from "./MerkleTree.sol"; +import {MerkleTree} from "../libs/MerkleTree.sol"; contract CommitmentAccumulator is ICommitmentAccumulator { using MerkleTree for MerkleTree.Tree; diff --git a/test/Deployment.t.sol b/test/Deployment.t.sol index 5d255b9..3234de2 100644 --- a/test/Deployment.t.sol +++ b/test/Deployment.t.sol @@ -17,6 +17,6 @@ contract ProtocolAdapterTest is Test { } function test_run_deploys_deterministically() public view { - assertEq(address(_pa), 0x83a5347727703729F6ffca6046CC44B99b56bF81); + assertEq(address(_pa), 0xAFCbB5614652Da927D480A9e82bA832C22836D1C); } } diff --git a/test/ProtocolAdapter.t.sol b/test/ProtocolAdapter.t.sol index c1d308a..0c6ebad 100644 --- a/test/ProtocolAdapter.t.sol +++ b/test/ProtocolAdapter.t.sol @@ -5,13 +5,17 @@ import {RiscZeroMockVerifier} from "@risc0-ethereum/test/RiscZeroMockVerifier.so import {Test} from "forge-std/Test.sol"; import {ProtocolAdapter} from "../src/ProtocolAdapter.sol"; -import {Resource, Transaction} from "../src/Types.sol"; +// import {Resource, Transaction} from "../src/Types.sol"; import {MockRiscZeroProof} from "./mocks/MockRiscZeroProof.sol"; -import {MockTypes} from "./mocks/MockTypes.sol"; + +//import {MockTypes} from "./mocks/MockTypes.sol"; contract ProtocolAdapterTest is Test { - uint8 internal constant _TREE_DEPTH = 2 ^ 32; + uint8 internal constant _COMMITMENT_TREE_DEPTH = 32; + + uint8 internal constant _ACTION_TREE_DEPTH = 4; + // IRiscZeroVerifier internal constant _SEPOLIA_VERIFIER = // IRiscZeroVerifier(address(0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187)); @@ -23,12 +27,13 @@ contract ProtocolAdapterTest is Test { _pa = new ProtocolAdapter({ riscZeroVerifier: _mockVerifier, - logicCircuitID: MockRiscZeroProof.IMAGE_ID_1, - complianceCircuitID: MockRiscZeroProof.IMAGE_ID_2, - treeDepth: _TREE_DEPTH + complianceCircuitID: MockRiscZeroProof.IMAGE_ID, + commitmentTreeDepth: _COMMITMENT_TREE_DEPTH, + actionTreeDepth: _ACTION_TREE_DEPTH }); } + /* function test_benchmark() public { uint16[3] memory n = [uint16(5), uint16(50), uint16(500)]; @@ -90,5 +95,5 @@ contract ProtocolAdapterTest is Test { }); _pa.verify(txn); - } + }*/ } diff --git a/test/mocks/CommitmentAccumulatorMock.sol b/test/mocks/CommitmentAccumulatorMock.sol index ef0a8d2..33d0e31 100644 --- a/test/mocks/CommitmentAccumulatorMock.sol +++ b/test/mocks/CommitmentAccumulatorMock.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.27; import {EnumerableSet} from "@openzeppelin-contracts/utils/structs/EnumerableSet.sol"; +import {MerkleTree} from "../../src/libs/MerkleTree.sol"; import {CommitmentAccumulator} from "../../src/state/CommitmentAccumulator.sol"; -import {MerkleTree} from "../../src/state/MerkleTree.sol"; contract CommitmentAccumulatorMock is CommitmentAccumulator { using EnumerableSet for EnumerableSet.Bytes32Set; diff --git a/test/mocks/ExampleLogicProof.sol b/test/mocks/ExampleLogicProof.sol new file mode 100644 index 0000000..cbcc9ef --- /dev/null +++ b/test/mocks/ExampleLogicProof.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +library ExampleLogicProof { + bytes internal constant SEAL = hex"70D0"; + + bytes32 internal constant LOGIC_CIRCUIT_ID = hex"70D0"; + + bytes32 internal constant JOURNAL_DIGEST = sha256(hex"70D0"); +} diff --git a/test/mocks/MockDelta.sol b/test/mocks/MockDelta.sol index 738804f..a2f66d7 100644 --- a/test/mocks/MockDelta.sol +++ b/test/mocks/MockDelta.sol @@ -6,9 +6,9 @@ import {Delta} from "../../src/proving/Delta.sol"; library MockDelta { using Delta for uint256[2]; + /// @notice A message containing the empty `bytes32` array. /// @dev Obtained from `abi.encode(new bytes32[](0))`. - bytes internal constant MESSAGE = hex"0000000000000000000000000000000000000000000000000000000000000020" hex"0000000000000000000000000000000000000000000000000000000000000000"; diff --git a/test/mocks/MockRiscZeroProof.sol b/test/mocks/MockRiscZeroProof.sol index 46f76ba..bff8eae 100644 --- a/test/mocks/MockRiscZeroProof.sol +++ b/test/mocks/MockRiscZeroProof.sol @@ -4,8 +4,7 @@ pragma solidity ^0.8.27; library MockRiscZeroProof { bytes4 internal constant VERIFIER_SELECTOR = 0x12345678; - bytes32 internal constant IMAGE_ID_1 = bytes32(uint256(1)); - bytes32 internal constant IMAGE_ID_2 = bytes32(uint256(2)); + bytes32 internal constant IMAGE_ID = bytes32(uint256(1)); /// @notice Generated from `sha256("MOCK_JOURNAL_DIGEST");`. bytes32 internal constant JOURNAL_DIGEST = 0x26968006c64cf2912711161619df51842dc3d1b2dd9e58765cf6385c16beee1f; diff --git a/test/mocks/MockRiscZeroProof.t.sol b/test/mocks/MockRiscZeroProof.t.sol index 55a05fa..98ae687 100644 --- a/test/mocks/MockRiscZeroProof.t.sol +++ b/test/mocks/MockRiscZeroProof.t.sol @@ -19,7 +19,7 @@ contract MockRiscZeroProofTest is Test { _MOCK_VERIFIER = new RiscZeroMockVerifier(MockRiscZeroProof.VERIFIER_SELECTOR); _proof = _MOCK_VERIFIER.mockProve({ - imageId: MockRiscZeroProof.IMAGE_ID_1, + imageId: MockRiscZeroProof.IMAGE_ID, journalDigest: MockRiscZeroProof.JOURNAL_DIGEST }); } @@ -29,7 +29,7 @@ contract MockRiscZeroProofTest is Test { vm.expectRevert(VerificationFailed.selector); _MOCK_VERIFIER.verify({ seal: _proof.seal, - imageId: MockRiscZeroProof.IMAGE_ID_2, + imageId: bytes32(uint256(123)), journalDigest: MockRiscZeroProof.JOURNAL_DIGEST }); } @@ -40,7 +40,7 @@ contract MockRiscZeroProofTest is Test { vm.expectRevert(VerificationFailed.selector, address(_MOCK_VERIFIER)); _MOCK_VERIFIER.verify({ seal: wrongSeal, - imageId: MockRiscZeroProof.IMAGE_ID_1, + imageId: MockRiscZeroProof.IMAGE_ID, journalDigest: MockRiscZeroProof.JOURNAL_DIGEST }); } @@ -54,7 +54,7 @@ contract MockRiscZeroProofTest is Test { ); _MOCK_VERIFIER.verify({ seal: abi.encode(wrongSelector), - imageId: MockRiscZeroProof.IMAGE_ID_1, + imageId: MockRiscZeroProof.IMAGE_ID, journalDigest: MockRiscZeroProof.JOURNAL_DIGEST }); } @@ -63,14 +63,14 @@ contract MockRiscZeroProofTest is Test { bytes32 wrongDigest = bytes32(0); vm.expectRevert(VerificationFailed.selector, address(_MOCK_VERIFIER)); - _MOCK_VERIFIER.verify({seal: _proof.seal, imageId: MockRiscZeroProof.IMAGE_ID_1, journalDigest: wrongDigest}); + _MOCK_VERIFIER.verify({seal: _proof.seal, imageId: MockRiscZeroProof.IMAGE_ID, journalDigest: wrongDigest}); } /// @notice It should verify correct _proofs. function test_correctProof() public view { _MOCK_VERIFIER.verify({ seal: _proof.seal, - imageId: MockRiscZeroProof.IMAGE_ID_1, + imageId: MockRiscZeroProof.IMAGE_ID, journalDigest: MockRiscZeroProof.JOURNAL_DIGEST }); } diff --git a/test/mocks/MockTree.sol b/test/mocks/MockTree.sol index ede2ad7..60d175f 100644 --- a/test/mocks/MockTree.sol +++ b/test/mocks/MockTree.sol @@ -3,8 +3,6 @@ pragma solidity ^0.8.27; import {SHA256} from "../../src/libs/SHA256.sol"; -import {MerkleTree} from "../../src/state/MerkleTree.sol"; - contract MockTree { uint8 internal constant _TREE_DEPTH = 2; uint256 internal constant _N_LEAFS = 2 ** _TREE_DEPTH; @@ -33,7 +31,7 @@ contract MockTree { } for (uint256 j = i; j < _N_ROOTS - 1; ++j) { - _leaves[i][j] = MerkleTree._EMPTY_LEAF_HASH; + _leaves[i][j] = SHA256.EMPTY_HASH; } _nodes[i][0] = SHA256.hash(_leaves[i][0], _leaves[i][1]); diff --git a/test/mocks/MockTypes.sol b/test/mocks/MockTypes.sol index 4387f1f..b308504 100644 --- a/test/mocks/MockTypes.sol +++ b/test/mocks/MockTypes.sol @@ -1,10 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.27; +/* import {Receipt as RiscZeroReceipt} from "@risc0-ethereum/IRiscZeroVerifier.sol"; import {RiscZeroMockVerifier} from "@risc0-ethereum/test/RiscZeroMockVerifier.sol"; -import {AppData, TagAppDataPair} from "../../src/libs/AppData.sol"; +//import {AppData, TagAppDataPair} from "../../src/libs/AppData.sol"; +import {ArrayLookup} from "../../src/libs/ArrayLookup.sol"; import {ComputableComponents} from "../../src/libs/ComputableComponents.sol"; import {Universal} from "../../src/libs/Identities.sol"; import {Delta} from "../../src/proving/Delta.sol"; @@ -17,7 +19,7 @@ import { Transaction, LogicInstance, TagLogicProofPair, - LogicRefProofPair, + LogicProof, ComplianceUnit, ComplianceInstance, ConsumedRefs, @@ -28,8 +30,9 @@ import {MockDelta} from "../mocks/MockDelta.sol"; import {MockRiscZeroProof} from "../mocks/MockRiscZeroProof.sol"; library MockTypes { + using ArrayLookup for bytes32[]; using ComputableComponents for Resource; - using AppData for TagAppDataPair[]; + // using AppData for TagAppDataPair[]; using Delta for uint256[2]; bytes32 internal constant _ALWAYS_VALID_LOGIC_REF = bytes32(0); @@ -86,9 +89,10 @@ library MockTypes { } } - TagAppDataPair[] memory appData = mockAppData({nullifiers: nfs, commitments: cms}); + //TagAppDataPair[] memory appData = mockAppData({nullifiers: nfs, commitments: cms}); + TagLogicProofPair[] memory rlProofs = - _mockLogicProofs({mockVerifier: mockVerifier, nullifiers: nfs, commitments: cms, appData: appData}); + _mockLogicProofs({mockVerifier: mockVerifier, nullifiers: nfs, commitments: cms}); ComplianceUnit[] memory complianceUnits = mockComplianceUnits({mockVerifier: mockVerifier, root: roots[0], commitments: cms, nullifiers: nfs}); @@ -96,39 +100,72 @@ library MockTypes { ResourceForwarderCalldataPair[] memory emptyCalls; actions[a] = Action({ - commitments: cms, - nullifiers: nfs, - logicProofs: rlProofs, + tagLogicProofPairs: rlProofs, complianceUnits: complianceUnits, - tagAppDataPairs: appData, resourceCalldataPairs: emptyCalls }); } bytes memory deltaProof = MockDelta.PROOF; - transaction = Transaction({roots: roots, actions: actions, deltaProof: deltaProof}); + bytes32[] storage tags; + for (uint256 i = 0; i < actions.length; ++i) { + for (uint256 j = 0; j < actions[i].tagLogicProofPairs.length; ++j) { + tags.push(actions[i].tagLogicProofPairs[j].tag); + } + } + + transaction = Transaction({ + actions: actions, + deltaProof: deltaProof, + deltaVerifyingKey: ComputableComponents.transactionHash(tags), + expectedBalance: Delta.zero() + }); } // solhint-disable-next-line function-max-lines function _mockLogicProofs( RiscZeroMockVerifier mockVerifier, bytes32[] memory nullifiers, - bytes32[] memory commitments, - TagAppDataPair[] memory appData - ) internal view returns (TagLogicProofPair[] memory logicProofs) { - logicProofs = new TagLogicProofPair[](nullifiers.length + commitments.length); + bytes32[] memory commitments + ) + //, TagAppDataPair[] memory tagAppDataPairs + internal + view + returns (TagLogicProofPair[] memory logicProofs) + { + (bytes32[][] memory consumed, bytes32[][] memory created) = ComputableComponents.prepareLists(tagAppDataPairs); + + uint256 nResources = tagAppDataPairs.length; + + ExpirableBlob[] memory appData = new ExpirableBlob[](1); + appData[0] = ExpirableBlob({deletionCriterion: DeletionCriterion.Immediately, blob: _MOCK_BLOB}); + + for (uint256 j = 0; j < nResources; ++j) { + bytes32 tag = tagAppDataPairs[j].tag; + // tags[resCounter++] = tag; + + LogicProof calldata proof = tagLogicProofPairs[j].logicProof; + + LogicInstance memory instance = LogicInstance({ + tag: tag, + isConsumed: proof.isConsumed, + consumed: consumed[j], + created: created[j], + appData: proof.appData + }); + } - uint256 len = nullifiers.length; + uint256 len = consumedTagAppDataPair.length; for (uint256 i = 0; i < len; ++i) { - bytes32 tag = nullifiers[i]; + bytes32 tag = consumedTagAppDataPair[i].tag; LogicInstance memory instance = LogicInstance({ tag: tag, isConsumed: true, - consumed: nullifiers, + consumed: nullifiers.removeElement(tag), created: commitments, - tagSpecificAppData: appData.lookup(tag) + appData: appDataEntries[i] }); bytes32 verifyingKey = _ALWAYS_VALID_LOGIC_REF; @@ -140,7 +177,12 @@ library MockTypes { logicProofs[i] = TagLogicProofPair({ tag: tag, - pair: LogicRefProofPair({logicRef: _ALWAYS_VALID_LOGIC_REF, proof: receipt.seal}) + logicProof: LogicProof({ + isConsumed: true, + logicVerifyingKeyOuter: _ALWAYS_VALID_LOGIC_REF, + appData: appDataEntries[i], + proof: receipt.seal + }) }); } @@ -152,8 +194,8 @@ library MockTypes { tag: tag, isConsumed: false, consumed: nullifiers, - created: commitments, - tagSpecificAppData: appData.lookup(tag) + created: commitments.removeElement(tag), + appData: appDataEntries[nullifiers.length + i] }); bytes32 verifyingKey = _ALWAYS_VALID_LOGIC_REF; @@ -165,7 +207,12 @@ library MockTypes { logicProofs[nullifiers.length + i] = TagLogicProofPair({ tag: tag, - pair: LogicRefProofPair({logicRef: _ALWAYS_VALID_LOGIC_REF, proof: receipt.seal}) + logicProof: LogicProof({ + isConsumed: true, + logicVerifyingKeyOuter: _ALWAYS_VALID_LOGIC_REF, + appData: appDataEntries[nullifiers.length + i], + proof: receipt.seal + }) }); } } @@ -185,8 +232,8 @@ library MockTypes { for (uint256 i = 0; i < nUnits; ++i) { ComplianceInstance memory instance = ComplianceInstance({ - consumed: ConsumedRefs({nullifierRef: nullifiers[i], rootRef: root, logicRef: _ALWAYS_VALID_LOGIC_REF}), - created: CreatedRefs({commitmentRef: commitments[i], logicRef: _ALWAYS_VALID_LOGIC_REF}), + consumed: ConsumedRefs({nullifier: nullifiers[i], rootRef: root, logicRef: _ALWAYS_VALID_LOGIC_REF}), + created: CreatedRefs({commitment: commitments[i], logicRef: _ALWAYS_VALID_LOGIC_REF}), unitDelta: Delta.zero() // TODO }); @@ -233,27 +280,14 @@ library MockTypes { } } - function mockAppData(bytes32[] memory nullifiers, bytes32[] memory commitments) - internal - pure - returns (TagAppDataPair[] memory appData) - { - appData = new TagAppDataPair[](nullifiers.length + commitments.length); - { - uint256 len = nullifiers.length; - for (uint256 i = 0; i < len; ++i) { - appData[i] = TagAppDataPair({ - tag: nullifiers[i], - appData: ExpirableBlob({deletionCriterion: DeletionCriterion.Immediately, blob: _MOCK_BLOB}) - }); - } - len = commitments.length; - for (uint256 i = 0; i < len; ++i) { - appData[nullifiers.length + i] = TagAppDataPair({ - tag: commitments[i], - appData: ExpirableBlob({deletionCriterion: DeletionCriterion.Immediately, blob: _MOCK_BLOB}) - }); - } + function mockAppData(bytes32[] memory tags) internal pure returns (TagAppDataPair[] memory appData) { + appData = new TagAppDataPair[](tags.length); + + for (uint256 i = 0; i < tags.length; ++i) { + appData[i] = TagAppDataPair({ + tag: tags[i], + appData: ExpirableBlob({deletionCriterion: DeletionCriterion.Immediately, blob: _MOCK_BLOB}) + }); } } @@ -316,3 +350,4 @@ library MockTypes { } } } +*/ diff --git a/test/state/CommitmentAccumulator.t.sol b/test/state/CommitmentAccumulator.t.sol index 59416c5..a5fd929 100644 --- a/test/state/CommitmentAccumulator.t.sol +++ b/test/state/CommitmentAccumulator.t.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.27; import {Test} from "forge-std/Test.sol"; +import {MerkleTree} from "../../src/libs/MerkleTree.sol"; import {SHA256} from "../../src/libs/SHA256.sol"; import {CommitmentAccumulator} from "../../src/state/CommitmentAccumulator.sol"; -import {MerkleTree} from "../../src/state/MerkleTree.sol"; import {CommitmentAccumulatorMock} from "../mocks/CommitmentAccumulatorMock.sol"; import {MockTree} from "../mocks/MockTree.sol"; diff --git a/test/state/MerkleTree.t.sol b/test/state/MerkleTree.t.sol index 6dbe0fe..6670392 100644 --- a/test/state/MerkleTree.t.sol +++ b/test/state/MerkleTree.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.27; import {Test} from "forge-std/Test.sol"; -import {MerkleTree} from "../../src/state/MerkleTree.sol"; +import {MerkleTree} from "../../src/libs/MerkleTree.sol"; import {MockTree} from "../mocks/MockTree.sol"; contract MerkleTreeTest is Test, MockTree {