Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(protocol): Separate proof verification to a standalone, deployed contract #13794

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions packages/protocol/contracts/L1/IProofVerifier.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/

pragma solidity ^0.8.18;

import {TaikoData} from "./TaikoData.sol";
import {AddressResolver} from "../common/AddressResolver.sol";

interface IProofVerifier {
/**
* Verifying proof via the ProofVerifier contract
*
* @param instance Hashed public input
* @param blockProofs Proof array
* @param resolver Current (up-to-date) address resolver
*/
function verifyProofs(
bytes32 instance,
TaikoData.TypedProof[] calldata blockProofs,
AddressResolver resolver
) external;
Comment on lines +20 to +24
Copy link
Contributor

@Brechtpd Brechtpd May 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
function verifyProofs(
bytes32 instance,
TaikoData.TypedProof[] calldata blockProofs,
AddressResolver resolver
) external;
function verifyProof(bytes32 instance, bytes calldata proof) external;

What do you think of this API? This way we don't have any dependencies and the proof can really be whatever, and proofs don't have to confirm to TypedProof which may not always fit.

EDIT: Updated to still have the instance separate. I would say not to pass in the AddressResolver, I think addresses accessed by the verifier should not be set on the protocol AddressResolver, they should store their own data.

}
97 changes: 97 additions & 0 deletions packages/protocol/contracts/L1/ProofVerifier.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/

pragma solidity ^0.8.18;

import {AddressResolver} from "../common/AddressResolver.sol";
import {Proxied} from "../common/Proxied.sol";
import {TaikoErrors} from "./TaikoErrors.sol";
import {TaikoData} from "./TaikoData.sol";
import {LibVerifyTrusted} from "./libs/proofTypes/LibVerifyTrusted.sol";
import {LibVerifyZKP} from "./libs/proofTypes/LibVerifyZKP.sol";

library TaikoProofToggleMask {
function getToggleMask() internal pure returns (uint16) {
// BITMAP for efficient iteration and flexible additions later
// ZKP_ONLY, // 0000 0001
// SGX_ONLY, // 0000 0010
// RESERVED_X_ONLY, // 0000 0100
// RESERVED_Y_ONLY, // 0000 1000
// ZKP_AND_SGX, // 0000 0011
// X_ZKP_SGX, // 0000 0111
return uint16(1); // ZKP ONLY by default
}
}

/// @custom:security-contact [email protected]
contract ProofVerifier is TaikoErrors {
uint256[50] private __gap;

function getToggleMask() public pure virtual returns (uint16) {
return TaikoProofToggleMask.getToggleMask();
}

function verifyProofs(
bytes32 instance,
TaikoData.TypedProof[] calldata blockProofs,
AddressResolver resolver
)
external
view
{
uint16 mask = getToggleMask();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should simply remove the mask? A verifier just always hard-codes the logic that needs to be followed. If this logic changes, a different verifier contract should be deployed.

Well, it is maybe useful for testing still, so this could be the testnet verifier contract or something like that. But on mainnet, probably just want to keep it as simple as possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, makes things more difficult with testing (multi prover vs. exisitng ZK only proofs).
So, i'd just leave it for now - we shall keep it in mind - but since harmless even on mainnet i'd just handle it with low prio - and change it close to mainnet. (?)

for (uint16 i; i < blockProofs.length;) {
TaikoData.TypedProof memory proof = blockProofs[i];
if (proof.proofType == 0) {
revert L1_INVALID_PROOFTYPE();
}

uint16 bitMask = uint16(1 << (proof.proofType - 1));
if ((mask & bitMask) == 0) {
revert L1_NOT_ENABLED_PROOFTYPE();
}

verifyTypedProof(proof, instance, resolver);
mask &= ~bitMask;

unchecked {
++i;
}
}

if(mask != 0) {
revert L1_NOT_ALL_REQ_PROOF_VERIFIED();
}
}

function verifyTypedProof(
TaikoData.TypedProof memory proof,
bytes32 instance,
AddressResolver resolver
) internal view {
if (proof.proofType == 1) {
// This is the regular ZK proof and required based on the flag
// in config.proofToggleMask
LibVerifyZKP.verifyProof(
resolver,
proof.proof,
instance,
proof.verifierId
);
} else if (proof.proofType == 2) {
// This is the SGX signature proof and required based on the flag
// in config.proofToggleMask
LibVerifyTrusted.verifyProof(
resolver,
proof.proof,
instance,
proof.verifierId
);
}
}
}

contract ProxiedProofVerifier is Proxied, ProofVerifier {}
1 change: 0 additions & 1 deletion packages/protocol/contracts/L1/TaikoConfig.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ library TaikoConfig {
ethDepositMaxFee: 1 ether / 10,
txListCacheExpiry: 0,
adjustmentQuotient: 16,
proofToggleMask: 1, // 0000 0001: means ZKP only
relaySignalRoot: false
});
}
Expand Down
8 changes: 0 additions & 8 deletions packages/protocol/contracts/L1/TaikoData.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,6 @@ library TaikoData {
uint96 maxEthDepositAmount;
uint96 minEthDepositAmount;
uint8 adjustmentQuotient;
// // BITMAP for efficient iteration and flexible additions later
// ZKP_ONLY, // 0000 0001
// SGX_ONLY, // 0000 0010
// RESERVED_X_ONLY, // 0000 0100
// RESERVED_Y_ONLY, // 0000 1000
// ZKP_AND_SGX, // 0000 0011
// X_ZKP_SGX, // 0000 0111
uint16 proofToggleMask;
bool relaySignalRoot;
}

Expand Down
56 changes: 5 additions & 51 deletions packages/protocol/contracts/L1/libs/LibProving.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import {LibMath} from "../../libs/LibMath.sol";
import {LibTokenomics} from "./LibTokenomics.sol";
import {LibUtils} from "./LibUtils.sol";
import {TaikoData} from "../../L1/TaikoData.sol";
import {LibVerifyTrusted} from "./proofTypes/LibVerifyTrusted.sol";
import {LibVerifyZKP} from "./proofTypes/LibVerifyZKP.sol";
import {IProofVerifier} from "../IProofVerifier.sol";

library LibProving {
using LibMath for uint256;
Expand Down Expand Up @@ -110,7 +109,7 @@ library LibProving {
uint160(address(resolver.resolve(config.chainId, "taiko", false)))
);

inputs[3] = uint256(blk.metaHash);
inputs[3] = uint256(evidence.metaHash);
inputs[4] = uint256(evidence.parentHash);
inputs[5] = uint256(evidence.blockHash);
inputs[6] = uint256(evidence.signalRoot);
Expand All @@ -125,29 +124,8 @@ library LibProving {
instance := keccak256(inputs, mul(32, 9))
}

uint16 mask = config.proofToggleMask;
for (uint16 i; i < evidence.blockProofs.length; ) {
TaikoData.TypedProof memory proof = evidence.blockProofs[i];
if (proof.proofType == 0) {
revert L1_INVALID_PROOFTYPE();
}

uint16 bitMask = uint16(1 << (proof.proofType - 1));
if ((mask & bitMask) == 0) {
revert L1_NOT_ENABLED_PROOFTYPE();
}

verifyTypedProof(proof, instance, resolver);
mask &= ~bitMask;

unchecked {
++i;
}
}

if(mask != 0) {
revert L1_NOT_ALL_REQ_PROOF_VERIFIED();
}
// Reverts if unsuccessful
IProofVerifier(resolver.resolve("proof_verifier", false)).verifyProofs(instance, evidence.blockProofs, resolver);

emit BlockProven({
id: blk.blockId,
Expand Down Expand Up @@ -209,29 +187,5 @@ library LibProving {
fc = blk.forkChoices[fcId];
}

function verifyTypedProof(
TaikoData.TypedProof memory proof,
bytes32 instance,
AddressResolver resolver
) internal view {
if (proof.proofType == 1) {
// This is the regular ZK proof and required based on the flag
// in config.proofToggleMask
LibVerifyZKP.verifyProof(
resolver,
proof.proof,
instance,
proof.verifierId
);
} else if (proof.proofType == 2) {
// This is the SGX signature proof and required based on the flag
// in config.proofToggleMask
LibVerifyTrusted.verifyProof(
resolver,
proof.proof,
instance,
proof.verifierId
);
}
}

}
2 changes: 1 addition & 1 deletion packages/protocol/contracts/L1/libs/LibVerifying.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ library LibVerifying {
|| config.txListCacheExpiry > 30 * 24 hours || config.ethDepositGas == 0
|| config.ethDepositMaxFee == 0 || config.ethDepositMaxFee >= type(uint96).max
|| config.adjustmentQuotient == 0 || initProofTimeTarget == 0
|| initProofTimeIssued == 0 || config.proofToggleMask == 0
|| initProofTimeIssued == 0
) revert L1_INVALID_CONFIG();

uint64 timeNow = uint64(block.timestamp);
Expand Down
12 changes: 12 additions & 0 deletions packages/protocol/script/DeployOnL1.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.so
import "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol";
import "../contracts/L1/TaikoToken.sol";
import "../contracts/L1/TaikoL1.sol";
import "../contracts/L1/ProofVerifier.sol";
import "../contracts/bridge/Bridge.sol";
import "../contracts/bridge/TokenVault.sol";
import "../contracts/signal/SignalService.sol";
Expand Down Expand Up @@ -101,6 +102,17 @@ contract DeployOnL1 is Script {
)
);


ProofVerifier proofVerifier = new ProxiedProofVerifier();

deployProxy(
"proof_verifier",
address(proofVerifier),
""
);

setAddress("proof_verifier", address(proofVerifier));

// HorseToken && BullToken
address horseToken = address(new FreeMintERC20("Horse Token", "HORSE"));
console2.log("HorseToken", horseToken);
Expand Down
17 changes: 16 additions & 1 deletion packages/protocol/test/TaikoL1MultiProving.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {TaikoToken} from "../contracts/L1/TaikoToken.sol";
import {SignalService} from "../contracts/signal/SignalService.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {TaikoL1TestBase} from "./TaikoL1TestBase.t.sol";
import {ProofVerifier} from "../contracts/L1/ProofVerifier.sol";

contract TaikoL1Oracle is TaikoL1 {
function getConfig() public pure override returns (TaikoData.Config memory config) {
Expand All @@ -24,7 +25,19 @@ contract TaikoL1Oracle is TaikoL1 {
config.ringBufferSize = 12;
config.proofCooldownPeriod = 5 minutes;
config.realProofSkipSize = 10;
config.proofToggleMask = 3; // It means SGX proof is necessary
}
}

contract TaikoMultiProverVerifier is ProofVerifier {
function getToggleMask() public pure override returns (uint16) {
// BITMAP for efficient iteration and flexible additions later
// ZKP_ONLY, // 0000 0001
// SGX_ONLY, // 0000 0010
// RESERVED_X_ONLY, // 0000 0100
// RESERVED_Y_ONLY, // 0000 1000
// ZKP_AND_SGX, // 0000 0011
// X_ZKP_SGX, // 0000 0111
return uint16(3); // ZKP_AND_SGX
}
}

Expand All @@ -41,6 +54,8 @@ contract TaikoL1OracleTest is TaikoL1TestBase {

function setUp() public override {
TaikoL1TestBase.setUp();
pv = new TaikoMultiProverVerifier();
registerAddress("proof_verifier", address(pv));
registerAddress(L1.getVerifierName(100), address(new Verifier()));
}

Expand Down
5 changes: 5 additions & 0 deletions packages/protocol/test/TaikoL1TestBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {LibUtils} from "../contracts/L1/libs/LibUtils.sol";
import {TaikoConfig} from "../contracts/L1/TaikoConfig.sol";
import {TaikoData} from "../contracts/L1/TaikoData.sol";
import {TaikoL1} from "../contracts/L1/TaikoL1.sol";
import {ProofVerifier} from "../contracts/L1/ProofVerifier.sol";
import {TaikoToken} from "../contracts/L1/TaikoToken.sol";
import {SignalService} from "../contracts/signal/SignalService.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
Expand All @@ -23,6 +24,7 @@ abstract contract TaikoL1TestBase is Test {
TaikoToken public tko;
SignalService public ss;
TaikoL1 public L1;
ProofVerifier public pv;
TaikoData.Config conf;
uint256 internal logCount;

Expand Down Expand Up @@ -77,6 +79,9 @@ abstract contract TaikoL1TestBase is Test {
// e.g.: 0..9999 ZK verifiers, 10000..19999 SGX verifier addresses
registerAddress(L1.getVerifierName(10000), Alice);

pv = new ProofVerifier();
registerAddress("proof_verifier", address(pv));

tko = new TaikoToken();
registerAddress("taiko_token", address(tko));
address[] memory premintRecipients;
Expand Down