Skip to content

Commit

Permalink
feat: add automata proving contracts
Browse files Browse the repository at this point in the history
Signed-off-by: dikel <[email protected]>
  • Loading branch information
dikel committed Sep 17, 2024
1 parent 0d93dbb commit 83de555
Show file tree
Hide file tree
Showing 3 changed files with 271 additions and 0 deletions.
32 changes: 32 additions & 0 deletions packages/protocol/contracts/verifiers/AttestationVerifier.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.24;

import { IAttestation } from "./IAttestation.sol";

contract AttestationVerifier {
IAttestation public attestationVerifier;

constructor(address _attestationVerifierAddr) {
attestationVerifier = IAttestation(_attestationVerifierAddr);
}

error INVALID_REPORT();
error INVALID_REPORT_DATA();
error REPORT_DATA_MISMATCH();

function verifyAttestation(bytes calldata _report, bytes32 _userData) public {
if (address(attestationVerifier) == address(0)) return;

(bool succ, bytes memory output) = attestationVerifier.verifyAndAttestOnChain(_report);
if (!succ) revert INVALID_REPORT();

if (output.length < 32) revert INVALID_REPORT_DATA();

bytes32 quoteBodyLast32;
assembly {
quoteBodyLast32 := mload(add(add(output, 0x20), sub(mload(output), 32)))
}

if (quoteBodyLast32 != _userData) revert REPORT_DATA_MISMATCH();
}
}
51 changes: 51 additions & 0 deletions packages/protocol/contracts/verifiers/IAttestation.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

/**
* @title Interface standard that implement attestation contracts whose verification logic can be
* implemented
* both on-chain and with Risc0 ZK proofs
* @notice The interface simply provides two verification methods for a given attestation input.
* The user can either pay a possibly hefty gas cost to fully verify an attestation fully on-chain
* OR
* Provides ZK proofs from executing an off-chain program where the verification of such attestation
* is conducted.
* @dev should also implement Risc0 Guest Program to use this interface.
* See https://dev.risczero.com/api/blockchain-integration/bonsai-on-eth to learn more
*/
interface IAttestation {
/**
* @notice full on-chain verification for an attestation
* @dev must further specify the structure of inputs/outputs, to be serialized and passed to
* this method
* @param input - serialized raw input as defined by the project
* @return success - whether the quote has been successfully verified or not
* @return output - the output upon completion of verification. The output data may require
* post-processing by the consumer.
* For verification failures, the output is simply a UTF-8 encoded string, describing the reason
* for failure.
* @dev can directly type cast the failed output as a string
*/
function verifyAndAttestOnChain(bytes calldata input)
external
returns (bool success, bytes memory output);

/**
* @param journal - The output of the Guest program, this includes:
* - VerifiedOutput struct
* - TcbInfo hash
* - QEID hash
* - RootCA hash
* - TCB Signing CA hash
* - Root CRL hash
* - Platform CRL hash
* - Processor CRL hash
* @param seal - The encoded cryptographic proof (i.e. SNARK).
*/
function verifyAndAttestWithZKProof(
bytes calldata journal,
bytes calldata seal
)
external
returns (bool success, bytes memory output);
}
188 changes: 188 additions & 0 deletions packages/protocol/contracts/verifiers/ProverRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.24;

import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "../automata-attestation/lib/QuoteV3Auth/V3Struct.sol";
import "../common/EssentialContract.sol";
import "../common/LibStrings.sol";
import "../L1/ITaikoL1.sol";
import "./libs/LibPublicInput.sol";
import "./AttestationVerifier.sol";
import "./IVerifier.sol";

contract ProverRegistry is EssentialContract, IVerifier {
struct ProverInstance {
address addr;
uint256 validUntil;
uint256 teeType; // 1: IntelTDX
}

struct ReportData {
address addr;
uint256 teeType;
uint256 referenceBlockNumber;
bytes32 referenceBlockHash;
bytes32 binHash;
}

error INVALID_BLOCK_NUMBER();
error BLOCK_NUMBER_OUT_OF_DATE();
error BLOCK_NUMBER_MISMATCH();
error REPORT_USED();
error PROVER_TYPE_MISMATCH();
error PROVER_INVALID_INSTANCE_ID(uint256);
error PROVER_INVALID_ADDR(address);
error PROVER_ADDR_MISMATCH(address, address);
error PROVER_OUT_OF_DATE(uint256);

event InstanceAdded(
uint256 indexed id, address indexed instance, address replaced, uint256 validUntil
);

AttestationVerifier public verifier;
uint256 public attestValiditySeconds;
uint256 public maxBlockNumberDiff;
uint256 public chainID;
uint256 public nextInstanceId = 0;

mapping(bytes32 reportHash => bool used) public attestedReports;
mapping(uint256 proverInstanceID => ProverInstance instance) public attestedProvers;

uint256[43] private __gap;

constructor() {
_disableInitializers();
}

function initialize(
address _initialOwner,
address _verifierAddr,
address _rollupAddressManager,
uint256 _chainID,
uint256 _attestValiditySeconds,
uint256 _maxBlockNumberDiff
)
public
initializer
{
verifier = AttestationVerifier(_verifierAddr);
chainID = _chainID;
attestValiditySeconds = _attestValiditySeconds;
maxBlockNumberDiff = _maxBlockNumberDiff;
__Essential_init(_initialOwner, _rollupAddressManager);
}

function reinitialize(
uint8 i,
address _initialOwner,
address _verifierAddr,
uint256 _chainID,
uint256 _attestValiditySeconds,
uint256 _maxBlockNumberDiff
)
public
reinitializer(i)
{
verifier = AttestationVerifier(_verifierAddr);
chainID = _chainID;
attestValiditySeconds = _attestValiditySeconds;
maxBlockNumberDiff = _maxBlockNumberDiff;
_transferOwnership(_initialOwner);
}

/// @notice register prover instance with quote
function register(bytes calldata _report, ReportData calldata _data) external {
_checkBlockNumber(_data.referenceBlockNumber, _data.referenceBlockHash);
bytes32 dataHash = keccak256(abi.encode(_data));

verifier.verifyAttestation(_report, dataHash);

bytes32 reportHash = keccak256(_report);
if (attestedReports[reportHash]) revert REPORT_USED();
attestedReports[reportHash] = true;

uint256 instanceID = nextInstanceId + 1;
nextInstanceId += 1;

uint256 validUnitl = block.timestamp + attestValiditySeconds;
attestedProvers[instanceID] = ProverInstance(_data.addr, validUnitl, _data.teeType);

emit InstanceAdded(instanceID, _data.addr, address(0), validUnitl);
}

function checkProver(
uint256 _instanceID,
address _proverAddr
)
public
view
returns (ProverInstance memory)
{
ProverInstance memory prover;
if (_instanceID == 0) revert PROVER_INVALID_INSTANCE_ID(_instanceID);
if (_proverAddr == address(0)) revert PROVER_INVALID_ADDR(_proverAddr);
prover = attestedProvers[_instanceID];
if (prover.addr != _proverAddr) revert PROVER_ADDR_MISMATCH(prover.addr, _proverAddr);
if (prover.validUntil < block.timestamp) revert PROVER_OUT_OF_DATE(prover.validUntil);
return prover;
}

// Due to the inherent unpredictability of blockHash, it mitigates the risk of mass-generation
// of attestation reports in a short time frame, preventing their delayed and gradual
// exploitation.
// This function will make sure the attestation report generated in recent ${maxBlockNumberDiff}
// blocks
function _checkBlockNumber(uint256 blockNumber, bytes32 blockHash) private view {
if (blockNumber >= block.number) revert INVALID_BLOCK_NUMBER();
if (block.number - blockNumber >= maxBlockNumberDiff) {
revert BLOCK_NUMBER_OUT_OF_DATE();
}
if (blockhash(blockNumber) != blockHash) revert BLOCK_NUMBER_MISMATCH();
}

function verifyProof(
Context calldata _ctx,
TaikoData.Transition calldata _tran,
TaikoData.TierProof calldata _proof
)
external
{
// Do not run proof verification to contest an existing proof
if (_ctx.isContesting) return;

// Size is: 89 bytes
// 4 bytes + 20 bytes + 65 bytes (signature) = 89
// TODO: do we need this check?
// if (_proof.data.length != 89) revert SGX_INVALID_PROOF();

uint32 id = uint32(bytes4(_proof.data[:4]));
address newInstance = address(bytes20(_proof.data[4:24]));

address oldInstance = ECDSA.recover(
LibPublicInput.hashPublicInputs(
_tran, address(this), newInstance, _ctx.prover, _ctx.metaHash, taikoChainId()
),
_proof.data[24:]
);

ProverInstance memory prover = checkProver(id, oldInstance);
if (_proof.tier != prover.teeType) revert PROVER_TYPE_MISMATCH();
if (oldInstance != newInstance) {
attestedProvers[id].addr = newInstance;
emit InstanceAdded(id, oldInstance, newInstance, prover.validUntil);
}
}

/// @inheritdoc IVerifier
function verifyBatchProof(
ContextV2[] calldata, /*_ctxs*/
TaikoData.TierProof calldata /*_proof*/
)
external
view
{ }

function taikoChainId() internal view virtual returns (uint64) {
return ITaikoL1(resolve(LibStrings.B_TAIKO, false)).getConfig().chainId;
}
}

0 comments on commit 83de555

Please sign in to comment.