|
| 1 | +// SPDX-License-Identifier: MIT |
| 2 | +pragma solidity 0.8.19; |
| 3 | + |
| 4 | +import { Halo2Verifier } from "./Halo2Verifier.sol"; |
| 5 | +import { IOpenVmHalo2Verifier } from "./interfaces/IOpenVmHalo2Verifier.sol"; |
| 6 | + |
| 7 | +type MemoryPointer is uint256; |
| 8 | + |
| 9 | +/// @notice This contract provides a thin wrapper around the Halo2 verifier |
| 10 | +/// outputted by `snark-verifier`, exposing a more user-friendly interface. |
| 11 | +contract OpenVmHalo2Verifier is Halo2Verifier, IOpenVmHalo2Verifier { |
| 12 | + /// @dev Invalid public values length |
| 13 | + error InvalidPublicValuesLength(uint256 expected, uint256 actual); |
| 14 | + |
| 15 | + /// @dev Invalid proof data length |
| 16 | + error InvalidProofDataLength(uint256 expected, uint256 actual); |
| 17 | + |
| 18 | + /// @dev Proof verification failed |
| 19 | + error ProofVerificationFailed(); |
| 20 | + |
| 21 | + /// @dev The length of the proof data, in bytes. |
| 22 | + uint256 private constant PROOF_DATA_LENGTH = (12 + 43) * 32; |
| 23 | + |
| 24 | + /// @dev The length of the public values, in bytes. This value is set by |
| 25 | + /// OpenVM and is guaranteed to be no larger than 8192. |
| 26 | + uint256 private constant PUBLIC_VALUES_LENGTH = 32; |
| 27 | + |
| 28 | + /// @dev The length of the full proof, in bytes |
| 29 | + uint256 private constant FULL_PROOF_LENGTH = (12 + 2 + PUBLIC_VALUES_LENGTH + 43) * 32; |
| 30 | + |
| 31 | + /// @dev The version of OpenVM that generated this verifier. |
| 32 | + string public constant OPENVM_VERSION = "1.4"; |
| 33 | + |
| 34 | + /// @notice A wrapper that constructs the proof into the right format for |
| 35 | + /// use with the `snark-verifier` verification. |
| 36 | + /// |
| 37 | + /// @dev The verifier expected proof format is: |
| 38 | + /// proof[..12 * 32]: KZG accumulator |
| 39 | + /// proof[12 * 32..13 * 32]: app exe commit |
| 40 | + /// proof[13 * 32..14 * 32]: app vm commit |
| 41 | + /// proof[14 * 32..(14 + PUBLIC_VALUES_LENGTH) * 32]: publicValues[0..PUBLIC_VALUES_LENGTH] |
| 42 | + /// proof[(14 + PUBLIC_VALUES_LENGTH) * 32..]: Proof Suffix |
| 43 | + /// |
| 44 | + /// @param publicValues The PVs revealed by the OpenVM guest program. |
| 45 | + /// @param proofData All components of the proof except the public values and |
| 46 | + /// app exe and vm commits. The expected format is: |
| 47 | + /// `abi.encodePacked(kzgAccumulator, proofSuffix)` |
| 48 | + /// @param appExeCommit The commitment to the OpenVM application executable whose execution |
| 49 | + /// is being verified. |
| 50 | + /// @param appVmCommit The commitment to the VM configuration. |
| 51 | + function verify(bytes calldata publicValues, bytes calldata proofData, bytes32 appExeCommit, bytes32 appVmCommit) |
| 52 | + external |
| 53 | + view |
| 54 | + { |
| 55 | + if (publicValues.length != PUBLIC_VALUES_LENGTH) { |
| 56 | + revert InvalidPublicValuesLength(PUBLIC_VALUES_LENGTH, publicValues.length); |
| 57 | + } |
| 58 | + if (proofData.length != PROOF_DATA_LENGTH) revert InvalidProofDataLength(PROOF_DATA_LENGTH, proofData.length); |
| 59 | + |
| 60 | + // We will format the public values and construct the full proof payload |
| 61 | + // below. |
| 62 | + |
| 63 | + MemoryPointer proofPtr = _constructProof(publicValues, proofData, appExeCommit, appVmCommit); |
| 64 | + |
| 65 | + uint256 fullProofLength = FULL_PROOF_LENGTH; |
| 66 | + |
| 67 | + /// @solidity memory-safe-assembly |
| 68 | + assembly { |
| 69 | + // Self-call using the proof as calldata |
| 70 | + if iszero(staticcall(gas(), address(), proofPtr, fullProofLength, 0, 0)) { |
| 71 | + mstore(0x00, 0xd611c318) // ProofVerificationFailed() |
| 72 | + revert(0x1c, 0x04) |
| 73 | + } |
| 74 | + } |
| 75 | + } |
| 76 | + |
| 77 | + /// @dev The assembly code should perform the same function as the following |
| 78 | + /// solidity code: |
| 79 | + // |
| 80 | + /// ```solidity |
| 81 | + /// bytes memory proof = |
| 82 | + /// abi.encodePacked(proofData[0:0x180], appExeCommit, appVmCommit, publicValuesPayload, proofData[0x180:]); |
| 83 | + /// ``` |
| 84 | + // |
| 85 | + /// where `publicValuesPayload` is a memory payload with each byte in |
| 86 | + /// `publicValues` separated into its own `bytes32` word. |
| 87 | + /// |
| 88 | + /// This function does not clean the memory it allocates. Since it is the |
| 89 | + /// only memory write that occurs in the call frame, we know that |
| 90 | + /// the memory region cannot have been dirtied. |
| 91 | + /// |
| 92 | + /// @return proofPtr Memory pointer to the beginning of the constructed |
| 93 | + /// proof. This pointer does not follow `bytes memory` semantics. |
| 94 | + function _constructProof( |
| 95 | + bytes calldata publicValues, |
| 96 | + bytes calldata proofData, |
| 97 | + bytes32 appExeCommit, |
| 98 | + bytes32 appVmCommit |
| 99 | + ) internal pure returns (MemoryPointer proofPtr) { |
| 100 | + uint256 fullProofLength = FULL_PROOF_LENGTH; |
| 101 | + |
| 102 | + // The expected proof format using hex offsets: |
| 103 | + // |
| 104 | + // proof[..0x180]: KZG accumulator |
| 105 | + // proof[0x180..0x1a0]: app exe commit |
| 106 | + // proof[0x1a0..0x1c0]: app vm commit |
| 107 | + // proof[0x1c0..(0x1c0 + PUBLIC_VALUES_LENGTH * 32)]: publicValues[0..PUBLIC_VALUES_LENGTH] |
| 108 | + // proof[(0x1c0 + PUBLIC_VALUES_LENGTH * 32)..]: Proof Suffix |
| 109 | + |
| 110 | + /// @solidity memory-safe-assembly |
| 111 | + assembly { |
| 112 | + proofPtr := mload(0x40) |
| 113 | + // Allocate the memory as a safety measure. |
| 114 | + mstore(0x40, add(proofPtr, fullProofLength)) |
| 115 | + |
| 116 | + // Copy the KZG accumulator (length 0x180) into the beginning of |
| 117 | + // the memory buffer |
| 118 | + calldatacopy(proofPtr, proofData.offset, 0x180) |
| 119 | + |
| 120 | + // Copy the App Exe Commit and App Vm Commit into the memory buffer |
| 121 | + mstore(add(proofPtr, 0x180), appExeCommit) |
| 122 | + mstore(add(proofPtr, 0x1a0), appVmCommit) |
| 123 | + |
| 124 | + // Copy the Proof Suffix (length 43 * 32 = 0x560) into the |
| 125 | + // end of the memory buffer, leaving PUBLIC_VALUES_LENGTH words in |
| 126 | + // between for the publicValuesPayload. |
| 127 | + // |
| 128 | + // Begin copying from the end of the KZG accumulator in the |
| 129 | + // calldata buffer (0x180) |
| 130 | + let proofSuffixOffset := add(0x1c0, shl(5, PUBLIC_VALUES_LENGTH)) |
| 131 | + calldatacopy(add(proofPtr, proofSuffixOffset), add(proofData.offset, 0x180), 0x560) |
| 132 | + |
| 133 | + // Copy each byte of the public values into the proof. It copies the |
| 134 | + // most significant bytes of public values first. |
| 135 | + let publicValuesMemOffset := add(add(proofPtr, 0x1c0), 0x1f) |
| 136 | + for { let i := 0 } iszero(eq(i, PUBLIC_VALUES_LENGTH)) { i := add(i, 1) } { |
| 137 | + calldatacopy(add(publicValuesMemOffset, shl(5, i)), add(publicValues.offset, i), 0x01) |
| 138 | + } |
| 139 | + } |
| 140 | + } |
| 141 | +} |
0 commit comments