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): Enable protocol to handle multiple proof types #13745

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from 16 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
2 changes: 1 addition & 1 deletion packages/protocol/contracts/L1/TaikoConfig.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ library TaikoConfig {
// transactions list calldata, 8K for the remaining tx fields.
maxBytesPerTxList: 120000,
proofCooldownPeriod: 30 minutes,
systemProofCooldownPeriod: 15 minutes,
// Only need 1 real zkp per 10 blocks.
// If block number is N, then only when N % 10 == 0, the real ZKP
// is needed. For mainnet, this must be 0 or 1.
Expand All @@ -42,6 +41,7 @@ library TaikoConfig {
ethDepositMaxFee: 1 ether / 10,
txListCacheExpiry: 0,
adjustmentQuotient: 16,
proofToggleMask: 1, // 0000 0001: means ZKP only
relaySignalRoot: false
});
}
Expand Down
18 changes: 15 additions & 3 deletions packages/protocol/contracts/L1/TaikoData.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ library TaikoData {
uint64 maxBytesPerTxList;
uint256 txListCacheExpiry;
uint256 proofCooldownPeriod;
uint256 systemProofCooldownPeriod;
uint256 realProofSkipSize;
uint256 ethDepositGas;
uint256 ethDepositMaxFee;
Expand All @@ -28,6 +27,14 @@ 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;
Copy link
Contributor Author

@adaki2004 adaki2004 May 23, 2023

Choose a reason for hiding this comment

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

Maybe have it static 2 proofs now - instead of a bitmap. Future updates will handle rest.

bool relaySignalRoot;
}

Expand Down Expand Up @@ -71,6 +78,12 @@ library TaikoData {
TaikoData.EthDeposit[] depositsProcessed;
}

struct TypedProof {
uint16 verifierId;
uint16 proofType; // 0: Not set = invalid, 1: ZKP, 2: SGX, 3 e.g.: X (not implemented yet), etc.
bytes proof;
}

struct BlockEvidence {
bytes32 metaHash;
bytes32 parentHash;
Expand All @@ -80,8 +93,7 @@ library TaikoData {
address prover;
uint32 parentGasUsed;
uint32 gasUsed;
uint16 verifierId;
bytes proof;
TypedProof[] blockProofs;
}

// 4 slots
Expand Down
12 changes: 6 additions & 6 deletions packages/protocol/contracts/L1/TaikoErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ abstract contract TaikoErrors {
error L1_INVALID_METADATA();
error L1_INVALID_PARAM();
error L1_INVALID_PROOF();
error L1_INVALID_PROOF_OVERWRITE();
error L1_NOT_SPECIAL_PROVER();
error L1_ORACLE_PROVER_DISABLED();
error L1_SAME_PROOF();
error L1_SYSTEM_PROVER_DISABLED();
error L1_SYSTEM_PROVER_PROHIBITED();
error L1_INVALID_PROOFTYPE();
error L1_INVALID_SGX_SIGNATURE();
error L1_NOT_ALL_REQ_PROOF_VERIFIED();
error L1_NO_AUTH_TO_OVERWRITE_FK();
error L1_NOT_ENABLED_PROOFTYPE();
error L1_NOTHING_TO_OVERWRITE();
error L1_TOO_MANY_BLOCKS();
error L1_TX_LIST_NOT_EXIST();
error L1_TX_LIST_HASH();
Expand Down
2 changes: 2 additions & 0 deletions packages/protocol/contracts/L1/TaikoEvents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ abstract contract TaikoEvents {
event EthDeposited(TaikoData.EthDeposit deposit);

event ProofTimeTargetChanged(uint64 proofTimeTarget);

event BlockForkChoiceFailsafeRewrite(uint256 blockId);
}
20 changes: 20 additions & 0 deletions packages/protocol/contracts/L1/TaikoL1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,26 @@ contract TaikoL1 is EssentialContract, ICrossChainSync, TaikoEvents, TaikoErrors
emit ProofTimeTargetChanged(newProofTimeTarget);
}

/**
* A failsafe mechanism to overwrite malicious / wrong fork choices in such a rare (if any?)
case when there are bugs both in SGX and in ZK circuits. Only failsafe account can call it.
*
* @param blockId The index of the block to prove. This is also used
* to select the right implementation version.
* @param input An abi-encoded TaikoData.BlockEvidence object.
*/
function setForkChoice(uint256 blockId, bytes calldata input) external {
LibProving.setForkChoice({
state: state,
config: getConfig(),
resolver: AddressResolver(this),
blockId: blockId,
evidence: abi.decode(input, (TaikoData.BlockEvidence))
});

emit BlockForkChoiceFailsafeRewrite(blockId);
}

function depositTaikoToken(uint256 amount) external nonReentrant {
LibTokenomics.depositTaikoToken(state, AddressResolver(this), amount);
}
Expand Down
221 changes: 113 additions & 108 deletions packages/protocol/contracts/L1/libs/LibProving.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {AddressResolver} from "../../common/AddressResolver.sol";
import {LibMath} from "../../libs/LibMath.sol";
import {LibUtils} from "./LibUtils.sol";
import {TaikoData} from "../../L1/TaikoData.sol";
import {LibVerifyTrusted} from "./proofTypes/LibVerifyTrusted.sol";
import {LibVerifyZKP} from "./proofTypes/LibVerifyZKP.sol";

library LibProving {
using LibMath for uint256;
Expand All @@ -29,13 +31,11 @@ library LibProving {
error L1_EVIDENCE_MISMATCH(bytes32 expected, bytes32 actual);
error L1_FORK_CHOICE_NOT_FOUND();
error L1_INVALID_EVIDENCE();
error L1_INVALID_PROOF();
error L1_INVALID_PROOF_OVERWRITE();
error L1_NOT_SPECIAL_PROVER();
error L1_ORACLE_PROVER_DISABLED();
error L1_SAME_PROOF();
error L1_SYSTEM_PROVER_DISABLED();
error L1_SYSTEM_PROVER_PROHIBITED();
error L1_INVALID_PROOFTYPE();
error L1_NO_AUTH_TO_OVERWRITE_FK();
error L1_NOT_ALL_REQ_PROOF_VERIFIED();
error L1_NOT_ENABLED_PROOFTYPE();
error L1_NOTHING_TO_OVERWRITE();

function proveBlock(
TaikoData.State storage state,
Expand All @@ -62,48 +62,6 @@ library LibProving {
revert L1_EVIDENCE_MISMATCH(blk.metaHash, evidence.metaHash);
}

// Separate between oracle proof (which needs to be overwritten)
// and non-oracle but system proofs
address specialProver;
if (evidence.prover == address(0)) {
specialProver = resolver.resolve("oracle_prover", true);
if (specialProver == address(0)) {
revert L1_ORACLE_PROVER_DISABLED();
}
} else if (evidence.prover == address(1)) {
specialProver = resolver.resolve("system_prover", true);
if (specialProver == address(0)) {
revert L1_SYSTEM_PROVER_DISABLED();
}

if (config.realProofSkipSize <= 1 || blockId % config.realProofSkipSize == 0) {
revert L1_SYSTEM_PROVER_PROHIBITED();
}
}

if (specialProver != address(0) && msg.sender != specialProver) {
if (evidence.proof.length != 64) {
revert L1_NOT_SPECIAL_PROVER();
} else {
uint8 v = uint8(evidence.verifierId);
bytes32 r;
bytes32 s;
bytes memory data = evidence.proof;
assembly {
r := mload(add(data, 32))
s := mload(add(data, 64))
}

// clear the proof before hashing evidence
evidence.verifierId = 0;
evidence.proof = new bytes(0);

if (specialProver != ecrecover(keccak256(abi.encode(evidence)), v, r, s)) {
revert L1_NOT_SPECIAL_PROVER();
}
}
}

TaikoData.ForkChoice storage fc;

uint256 fcId =
Expand All @@ -124,80 +82,66 @@ library LibProving {
} else {
state.forkChoiceIds[blk.blockId][evidence.parentHash][evidence.parentGasUsed] = fcId;
}
} else if (evidence.prover == address(0)) {
// This is the branch the oracle prover is trying to overwrite
fc = blk.forkChoices[fcId];
if (
fc.blockHash == evidence.blockHash && fc.signalRoot == evidence.signalRoot
&& fc.gasUsed == evidence.gasUsed
) revert L1_SAME_PROOF();
} else {
// This is the branch provers trying to overwrite
fc = blk.forkChoices[fcId];
if (fc.prover != address(0) && fc.prover != address(1)) {
revert L1_ALREADY_PROVEN();
}

if (
fc.blockHash != evidence.blockHash || fc.signalRoot != evidence.signalRoot
|| fc.gasUsed != evidence.gasUsed
) revert L1_INVALID_PROOF_OVERWRITE();
revert L1_ALREADY_PROVEN();
}

fc.blockHash = evidence.blockHash;
fc.signalRoot = evidence.signalRoot;
fc.gasUsed = evidence.gasUsed;
fc.prover = evidence.prover;

if (evidence.prover == address(1)) {
fc.provenAt = uint64(block.timestamp.max(blk.proposedAt + state.proofTimeTarget));
} else {
fc.provenAt = uint64(block.timestamp);
fc.provenAt = uint64(block.timestamp);

// Put together the input for proof and signature verification
uint256[10] memory inputs;

inputs[0] = uint256(uint160(address(resolver.resolve("signal_service", false))));
inputs[1] =
uint256(uint160(address(resolver.resolve(config.chainId, "signal_service", false))));
inputs[2] = uint256(uint160(address(resolver.resolve(config.chainId, "taiko", false))));

inputs[3] = uint256(evidence.metaHash);
inputs[4] = uint256(evidence.parentHash);
inputs[5] = uint256(evidence.blockHash);
inputs[6] = uint256(evidence.signalRoot);
inputs[7] = uint256(evidence.graffiti);
inputs[8] = (uint256(uint160(evidence.prover)) << 96)
| (uint256(evidence.parentGasUsed) << 64) | (uint256(evidence.gasUsed) << 32);

// Also hash configs that will be used by circuits
inputs[9] = uint256(config.blockMaxGasLimit) << 192
| uint256(config.maxTransactionsPerBlock) << 128
| uint256(config.maxBytesPerTxList) << 64;

bytes32 instance;
assembly {
instance := keccak256(inputs, mul(32, 10))
}

if (evidence.prover != address(0) && evidence.prover != address(1)) {
uint256[10] memory inputs;

inputs[0] = uint256(uint160(address(resolver.resolve("signal_service", false))));
inputs[1] =
uint256(uint160(address(resolver.resolve(config.chainId, "signal_service", false))));
inputs[2] = uint256(uint160(address(resolver.resolve(config.chainId, "taiko", false))));

inputs[3] = uint256(evidence.metaHash);
inputs[4] = uint256(evidence.parentHash);
inputs[5] = uint256(evidence.blockHash);
inputs[6] = uint256(evidence.signalRoot);
inputs[7] = uint256(evidence.graffiti);
inputs[8] = (uint256(uint160(evidence.prover)) << 96)
| (uint256(evidence.parentGasUsed) << 64) | (uint256(evidence.gasUsed) << 32);

// Also hash configs that will be used by circuits
inputs[9] = uint256(config.blockMaxGasLimit) << 192
| uint256(config.maxTransactionsPerBlock) << 128
| uint256(config.maxBytesPerTxList) << 64;

bytes32 instance;
assembly {
instance := keccak256(inputs, mul(32, 10))
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();
}

(bool verified, bytes memory ret) = resolver.resolve(
LibUtils.getVerifierName(evidence.verifierId), false
).staticcall(
bytes.concat(
bytes16(0),
bytes16(instance), // left 16 bytes of the given instance
bytes16(0),
bytes16(uint128(uint256(instance))), // right 16 bytes of the given instance
evidence.proof
)
);
uint16 bitMask = uint16(1 << (proof.proofType - 1));
if ((mask & bitMask) == 0) {
revert L1_NOT_ENABLED_PROOFTYPE();
}

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

if (!verified || ret.length != 32 || bytes32(ret) != keccak256("taiko")) {
revert L1_INVALID_PROOF();
unchecked {
++i;
}
}

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

emit BlockProven({
id: blk.blockId,
parentHash: evidence.parentHash,
Expand All @@ -208,6 +152,41 @@ library LibProving {
});
}

function setForkChoice(
TaikoData.State storage state,
TaikoData.Config memory config,
AddressResolver resolver,
uint256 blockId,
TaikoData.BlockEvidence memory evidence
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Put back this functionality into proveBlock()

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmmm why? The functionality of this is significantly different from proveBlock, which made proveBlock really complicated before.

) internal {
if (msg.sender != resolver.resolve("forkchoice_failsafe", false)) {
revert L1_NO_AUTH_TO_OVERWRITE_FK();
}

if (blockId <= state.lastVerifiedBlockId || blockId >= state.numBlocks) {
adaki2004 marked this conversation as resolved.
Show resolved Hide resolved
revert L1_BLOCK_ID();
}

TaikoData.Block storage blk = state.blocks[blockId % config.ringBufferSize];

// We make it so this will always be the first fork choice
TaikoData.ForkChoice storage fc = blk.forkChoices[1];

// In case it was 0 (unproven) - it's fine we prove it here otherwise
// it does not matter if 2 or 3, only fk idx 1 is valid.
unchecked {
++blk.nextForkChoiceId;
}

fc.key = LibUtils.keyForForkChoice(evidence.parentHash, evidence.parentGasUsed);

fc.blockHash = evidence.blockHash;
fc.signalRoot = evidence.signalRoot;
fc.gasUsed = evidence.gasUsed;
fc.prover = msg.sender; // "special" prover, the failsafe one
fc.provenAt = blk.proposedAt + state.proofTimeTarget;
}

function getForkChoice(
TaikoData.State storage state,
TaikoData.Config memory config,
Expand All @@ -222,4 +201,30 @@ library LibProving {
if (fcId == 0) revert L1_FORK_CHOICE_NOT_FOUND();
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
);
}
}
}
Loading