diff --git a/deploy-config/sepolia.json b/deploy-config/sepolia.json index 9a85de7a..979a81e5 100644 --- a/deploy-config/sepolia.json +++ b/deploy-config/sepolia.json @@ -74,5 +74,7 @@ "multiproofGenesisBlockNumber": 37223829, "multiproofBlockInterval": 100, "multiproofIntermediateBlockInterval": 10, - "multiproofProofThreshold": 1 + "multiproofProofThreshold": 1, + "risc0VerifierRouter": "0xb121b667dd2cf27f95f9f5107137696f56f188f6", + "risc0SetBuilderImageId": "0x70909b25db0db00f1d4b4016aeb876f53568a3e5a8e6397cb562d79947a02cc9" } diff --git a/foundry.toml b/foundry.toml index 44da9da9..4f74a039 100644 --- a/foundry.toml +++ b/foundry.toml @@ -50,6 +50,7 @@ remappings = [ '@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts', '@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts', '@openzeppelin/contracts-v5/=lib/openzeppelin-contracts-v5/contracts', + 'openzeppelin/=lib/risc0-ethereum/lib/openzeppelin-contracts/', '@rari-capital/solmate/=lib/solmate', '@lib-keccak/=lib/lib-keccak/contracts/lib', 'solady/=lib/solady/src/', diff --git a/scripts/deploy/DeployConfig.s.sol b/scripts/deploy/DeployConfig.s.sol index 08519422..509c3f88 100644 --- a/scripts/deploy/DeployConfig.s.sol +++ b/scripts/deploy/DeployConfig.s.sol @@ -105,6 +105,14 @@ contract DeployConfig is Script { uint256 public multiproofIntermediateBlockInterval; uint256 public multiproofProofThreshold; + // RISC Zero / NitroEnclaveVerifier Configuration (reference values; DeployRiscZeroStack takes these as CLI args) + bytes32 public risc0SetBuilderImageId; + bytes32 public nitroRootCert; + bytes32 public nitroVerifierId; + bytes32 public nitroVerifierProofId; + address public nitroProofSubmitter; + address public risc0VerifierRouter; + bool public useInterop; bool public useUpgradedFork; bytes32 public devFeatureBitmap; @@ -208,12 +216,19 @@ contract DeployConfig is Script { multiproofConfigHash = bytes32(_readOr(_json, "$.multiproofConfigHash", 0)); multiproofGameType = _readOr(_json, "$.multiproofGameType", 621); teeProposer = _readOr(_json, "$.teeProposer", finalSystemOwner); - nitroEnclaveVerifier = stdJson.readAddress(_json, "$.nitroEnclaveVerifier"); + nitroEnclaveVerifier = _readOr(_json, "$.nitroEnclaveVerifier", address(0)); multiproofGenesisOutputRoot = bytes32(_readOr(_json, "$.multiproofGenesisOutputRoot", uint256(1))); multiproofGenesisBlockNumber = _readOr(_json, "$.multiproofGenesisBlockNumber", 0); multiproofBlockInterval = _readOr(_json, "$.multiproofBlockInterval", 100); multiproofIntermediateBlockInterval = _readOr(_json, "$.multiproofIntermediateBlockInterval", 10); multiproofProofThreshold = _readOr(_json, "$.multiproofProofThreshold", 1); + + risc0SetBuilderImageId = bytes32(_readOr(_json, "$.risc0SetBuilderImageId", 0)); + nitroRootCert = bytes32(_readOr(_json, "$.nitroRootCert", 0)); + nitroVerifierId = bytes32(_readOr(_json, "$.nitroVerifierId", 0)); + nitroVerifierProofId = bytes32(_readOr(_json, "$.nitroVerifierProofId", 0)); + nitroProofSubmitter = _readOr(_json, "$.nitroProofSubmitter", address(0)); + risc0VerifierRouter = _readOr(_json, "$.risc0VerifierRouter", address(0)); } function fork() public view returns (Fork fork_) { diff --git a/scripts/multiproof/DeployDevWithNitro.s.sol b/scripts/multiproof/DeployDevWithNitro.s.sol index 42631cdf..ea50469b 100644 --- a/scripts/multiproof/DeployDevWithNitro.s.sol +++ b/scripts/multiproof/DeployDevWithNitro.s.sol @@ -12,42 +12,19 @@ pragma solidity 0.8.15; * This script deploys infrastructure using the REAL TEEProverRegistry, which * REQUIRES a ZK proof of a valid AWS Nitro attestation for signer registration. * You cannot use addDevSigner() - you must go through the full registerSigner() flow. - * A pre-deployed NitroEnclaveVerifier contract address must be provided in the config. * - * USE THIS SCRIPT WHEN: - * - Testing the full Nitro attestation flow end-to-end - * - You have access to a real AWS Nitro enclave - * - You want to validate the production registration process - * - * DO NOT USE THIS SCRIPT IF: - * - You don't have a Nitro enclave available - * - You just want quick local testing (use DeployDevNoNitro instead) - * - * NOTE: This still uses mocks for AnchorStateRegistry, DelayedWETH, and ZK Verifier, - * so it's not a full production deployment - just production-like for the - * Nitro attestation flow. - * - * ───────────────────────────────────────────────────────────────────────────────── - * STEP 1: Register the PCR0 (enclave image hash) - OWNER ONLY - * ───────────────────────────────────────────────────────────────────────────────── - * - * The PCR0 is the raw 48-byte hash of the enclave image. You must register it - * before any signers using that image can be registered. - * - * # Get raw PCR0 bytes from the enclave (48 bytes, hex-encoded) - * PCR0_RAW="0x<48_bytes_hex>" - * - * # Register it (only owner can do this) - * cast send $TEE_PROVER_REGISTRY "registerPCR0(bytes)" $PCR0_RAW \ - * --private-key $OWNER_KEY --rpc-url $RPC_URL - * - * Note: The teeImageHash in deploy-config is keccak256(PCR0_RAW), not the raw bytes. + * PREREQUISITES: + * 1. Deploy the RISC Zero verifier stack AND NitroEnclaveVerifier using + * DeployRiscZeroStack.s.sol (required because NitroEnclaveVerifier and its + * dependencies need Solidity ^0.8.20, while this script is pinned to =0.8.15). + * 2. Set `nitroEnclaveVerifier` in the deploy config to the deployed address. * * ───────────────────────────────────────────────────────────────────────────────── - * STEP 2: Generate ZK proof of the attestation and register the signer + * SIGNER REGISTRATION FLOW * ───────────────────────────────────────────────────────────────────────────────── * - * Generate a Risc0 ZK proof of the Nitro attestation offchain, then call: + * After deployment, register a signer by generating a RISC Zero ZK proof of a + * valid AWS Nitro attestation document and calling: * * cast send $TEE_PROVER_REGISTRY \ * "registerSigner(bytes,bytes)" $ZK_OUTPUT $ZK_PROOF_BYTES \ @@ -56,33 +33,6 @@ pragma solidity 0.8.15; * IMPORTANT: The attestation is only valid for 60 minutes! Generate the proof * and submit the transaction within that window. * - * ───────────────────────────────────────────────────────────────────────────────── - * VERIFICATION - * ───────────────────────────────────────────────────────────────────────────────── - * - * After registration, verify the signer is registered: - * - * # Check if signer is valid - * cast call $TEE_PROVER_REGISTRY "isValidSigner(address)(bool)" $SIGNER_ADDRESS - * - * # Check if the signer is registered - * cast call $TEE_PROVER_REGISTRY "isValidSigner(address)(bool)" $SIGNER_ADDRESS - * - * ───────────────────────────────────────────────────────────────────────────────── - * COMPARISON WITH DeployDevNoNitro - * ───────────────────────────────────────────────────────────────────────────────── - * - * | Feature | DeployDevNoNitro | DeployDevWithNitro | - * |----------------------------|------------------------|------------------------| - * | TEEProverRegistry | DevTEEProverRegistry | TEEProverRegistry | - * | Signer registration | addDevSigner() | registerSigner() | - * | Requires Nitro enclave | No | Yes | - * | Validates attestation (ZK) | No | Yes | - * | PCR0 pre-registration | No | Yes | - * | Attestation freshness | N/A | < 60 minutes | - * - * Both scripts use mocks for AnchorStateRegistry, DelayedWETH, and ZK Verifier. - * * ══════════════════════════════════════════════════════════════════════════════════ */ @@ -114,6 +64,7 @@ import { MockDelayedWETH } from "./mocks/MockDelayedWETH.sol"; /// @title DeployDevWithNitro /// @notice Development deployment WITH AWS Nitro attestation validation. /// @dev Uses real TEEProverRegistry which requires registerSigner() with valid attestation. +/// NitroEnclaveVerifier must be pre-deployed via DeployRiscZeroStack.s.sol. contract DeployDevWithNitro is Script { /// @notice Constant from Optimism's Constants.sol - the storage slot for proxy admin. bytes32 internal constant PROXY_OWNER_ADDRESS = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; @@ -127,6 +78,7 @@ contract DeployDevWithNitro is Script { DeployConfig(address(uint160(uint256(keccak256(abi.encode("optimism.deployconfig")))))); // Deployed addresses + address public nitroEnclaveVerifierAddr; address public teeProverRegistryProxy; address public teeVerifier; address public disputeGameFactory; @@ -142,19 +94,26 @@ contract DeployDevWithNitro is Script { function run() public { GameType gameType = GameType.wrap(uint32(cfg.multiproofGameType())); + // NitroEnclaveVerifier must be pre-deployed (via DeployRiscZeroStack.s.sol) + nitroEnclaveVerifierAddr = cfg.nitroEnclaveVerifier(); + require( + nitroEnclaveVerifierAddr != address(0), + "nitroEnclaveVerifier must be set in config (deploy via DeployRiscZeroStack.s.sol first)" + ); + console.log("=== Deploying Dev Infrastructure (WITH NITRO) ==="); console.log("Chain ID:", block.chainid); console.log("Owner:", cfg.finalSystemOwner()); console.log("TEE Proposer:", cfg.teeProposer()); console.log("Game Type:", cfg.multiproofGameType()); + console.log("NitroEnclaveVerifier:", nitroEnclaveVerifierAddr); console.log(""); console.log("NOTE: Using REAL TEEProverRegistry - ZK attestation proof REQUIRED."); - console.log("NitroEnclaveVerifier:", cfg.nitroEnclaveVerifier()); vm.startBroadcast(); _deployInfrastructure(gameType); - _deployTEEContracts(cfg.finalSystemOwner(), cfg.nitroEnclaveVerifier()); + _deployTEEContracts(cfg.finalSystemOwner()); _deployAggregateVerifier(gameType); vm.stopBroadcast(); @@ -163,11 +122,12 @@ contract DeployDevWithNitro is Script { _writeOutput(); } - function _deployTEEContracts(address owner, address _nitroEnclaveVerifier) internal { + function _deployTEEContracts(address owner) internal { address scgImpl = address( - new TEEProverRegistry(INitroEnclaveVerifier(_nitroEnclaveVerifier), IDisputeGameFactory(disputeGameFactory)) + new TEEProverRegistry( + INitroEnclaveVerifier(nitroEnclaveVerifierAddr), IDisputeGameFactory(disputeGameFactory) + ) ); - console.log("NitroEnclaveVerifier (external):", _nitroEnclaveVerifier); teeProverRegistryProxy = address( new TransparentUpgradeableProxy( scgImpl, @@ -186,11 +146,6 @@ contract DeployDevWithNitro is Script { console.log("TEEVerifier:", teeVerifier); } - function _registerProposer(address teeProposer) internal { - TEEProverRegistry(teeProverRegistryProxy).setProposer(teeProposer, true); - console.log("Registered TEE proposer:", teeProposer); - } - function _deployInfrastructure(GameType gameType) internal { address factoryImpl = address(new DisputeGameFactory()); MinimalProxyAdmin proxyAdmin = new MinimalProxyAdmin(cfg.finalSystemOwner()); @@ -249,7 +204,7 @@ contract DeployDevWithNitro is Script { console.log(" DEV DEPLOYMENT COMPLETE (WITH NITRO)"); console.log("========================================"); console.log("\nTEE Contracts:"); - console.log(" NitroEnclaveVerifier (external):", cfg.nitroEnclaveVerifier()); + console.log(" NitroEnclaveVerifier:", nitroEnclaveVerifierAddr); console.log(" TEEProverRegistry:", teeProverRegistryProxy); console.log(" TEEVerifier:", teeVerifier); console.log("\nInfrastructure:"); @@ -262,38 +217,27 @@ contract DeployDevWithNitro is Script { console.log(" TEE Image Hash:", vm.toString(cfg.teeImageHash())); console.log(" Config Hash:", vm.toString(cfg.multiproofConfigHash())); console.log("========================================"); - console.log("\n>>> NEXT STEPS (ZK ATTESTATION PROOF REQUIRED) <<<"); - console.log("\n1. Register the PCR0 (raw 48-byte enclave image hash):"); - console.log(" cast send", teeProverRegistryProxy); - console.log(' "registerPCR0(bytes)" '); - console.log(" --private-key --rpc-url "); - console.log("\n2. Generate a Risc0 ZK proof of the Nitro attestation offchain."); - console.log("\n3. Register signer with ZK proof:"); - console.log(" cast send", teeProverRegistryProxy); - console.log(' "registerSigner(bytes,bytes)" '); - console.log(" --private-key --rpc-url "); - console.log("\nSee the comments at the top of this file for full details."); - console.log("========================================\n"); + console.log("\n>>> NEXT STEP: Register signer with ZK attestation proof <<<"); + console.log("\n cast send", teeProverRegistryProxy); + console.log(' "registerSigner(bytes,bytes)" '); + console.log(" --private-key --rpc-url "); + console.log("\n========================================\n"); } function _writeOutput() internal { + // Build the JSON output with all deployed addresses + string memory json = ""; + json = string.concat(json, '{"TEEProverRegistry":"', vm.toString(teeProverRegistryProxy)); + json = string.concat(json, '","TEEVerifier":"', vm.toString(teeVerifier)); + json = string.concat(json, '","NitroEnclaveVerifier":"', vm.toString(nitroEnclaveVerifierAddr)); + json = string.concat(json, '","DisputeGameFactory":"', vm.toString(disputeGameFactory)); + json = string.concat(json, '","AnchorStateRegistry":"', vm.toString(mockAnchorRegistry)); + json = string.concat(json, '","DelayedWETH":"', vm.toString(mockDelayedWETH)); + json = string.concat(json, '","AggregateVerifier":"', vm.toString(aggregateVerifier)); + json = string.concat(json, '"}'); + string memory outPath = string.concat("deployments/", vm.toString(block.chainid), "-dev-with-nitro.json"); - string memory output = string.concat( - '{"TEEProverRegistry":"', - vm.toString(teeProverRegistryProxy), - '","TEEVerifier":"', - vm.toString(teeVerifier), - '","DisputeGameFactory":"', - vm.toString(disputeGameFactory), - '","AnchorStateRegistry":"', - vm.toString(mockAnchorRegistry), - '","DelayedWETH":"', - vm.toString(mockDelayedWETH), - '","AggregateVerifier":"', - vm.toString(aggregateVerifier), - '"}' - ); - vm.writeFile(outPath, output); + vm.writeFile(outPath, json); console.log("Deployment saved to:", outPath); } } diff --git a/scripts/multiproof/DeployRiscZeroStack.s.sol b/scripts/multiproof/DeployRiscZeroStack.s.sol new file mode 100644 index 00000000..91f7b366 --- /dev/null +++ b/scripts/multiproof/DeployRiscZeroStack.s.sol @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +/** + * @title DeployRiscZeroStack + * @notice Deploys a RiscZeroSetVerifier and NitroEnclaveVerifier that work with + * an existing RISC Zero verifier router (e.g. the Boundless-deployed + * Router on Sepolia). + * + * This script is separated from the main deployment scripts because the + * RiscZeroSetVerifier and NitroEnclaveVerifier contracts (via ISP1Verifier) + * require Solidity ^0.8.20, while the main deployment scripts and their + * transitive dependencies are pinned to =0.8.15. + * + * The RISC Zero Groth16 verifier and Router are NOT deployed by this script. + * They are assumed to already exist on-chain (typically deployed by the + * Boundless marketplace). Pass the router address as a parameter. + * + * A local RiscZeroSetVerifier is deployed that delegates root seal verification + * to the existing Router. This is necessary because the Boundless-deployed + * SetVerifier may have an outdated inner Groth16 verifier. By routing through + * the Router, root seals are dispatched to the correct Groth16 verifier + * regardless of version. + * + * ───────────────────────────────────────────────────────────────────────────────── + * USAGE + * ───────────────────────────────────────────────────────────────────────────────── + * + * forge script scripts/multiproof/DeployRiscZeroStack.s.sol:DeployRiscZeroStack \ + * --sig "run(address,address,bytes32,bytes32,bytes32)" \ + * \ + * \ + * --broadcast --rpc-url --private-key + * + * NOTE: The deployer MUST be the same address as OWNER, since the script calls + * addVerifyRoute() on the NitroEnclaveVerifier (onlyOwner). + * + * Outputs: + * - RiscZeroSetVerifier (delegates to existing Router for root verification) + * - NitroEnclaveVerifier with route wired to the local SetVerifier + * + * POST-DEPLOY: + * After deploying TEEProverRegistry via DeployDevWithNitro.s.sol, update the + * proofSubmitter on NitroEnclaveVerifier to the TEEProverRegistry address: + * + * cast send "setProofSubmitter(address)" \ + * --rpc-url --private-key + * + * ───────────────────────────────────────────────────────────────────────────────── + */ + +import { Script } from "forge-std/Script.sol"; +import { console2 as console } from "forge-std/console2.sol"; + +import { IRiscZeroVerifier } from "lib/risc0-ethereum/contracts/src/IRiscZeroVerifier.sol"; +import { RiscZeroSetVerifier, RiscZeroSetVerifierLib } from "lib/risc0-ethereum/contracts/src/RiscZeroSetVerifier.sol"; + +import { + NitroEnclaveVerifier, + ZkCoProcessorType, + ZkCoProcessorConfig +} from "src/multiproof/tee/NitroEnclaveVerifier.sol"; + +/// @title DeployRiscZeroStack +/// @notice Deploys RiscZeroSetVerifier + NitroEnclaveVerifier using an existing Router. +contract DeployRiscZeroStack is Script { + /// @notice Maximum attestation age accepted by the NitroEnclaveVerifier (1 hour). + uint64 public constant NITRO_MAX_TIME_DIFF = 3600; + + address public setVerifier; + address public nitroEnclaveVerifier; + + /// @param owner Owner for the NitroEnclaveVerifier (must equal msg.sender). + /// @param risc0VerifierRouter Address of an existing RISC Zero verifier router + /// (e.g. Boundless-deployed Router). + /// @param setBuilderImageId RISC Zero set builder image ID (from Boundless deployment). + /// @param nitroRootCert SHA-256 hash of the AWS Nitro root certificate. + /// @param nitroVerifierId RISC Zero image ID of the attestation verifier guest. + function run( + address owner, + address risc0VerifierRouter, + bytes32 setBuilderImageId, + bytes32 nitroRootCert, + bytes32 nitroVerifierId + ) + public + { + require(owner != address(0), "owner must be non-zero"); + require(risc0VerifierRouter != address(0), "risc0VerifierRouter must be non-zero"); + require(setBuilderImageId != bytes32(0), "setBuilderImageId must be non-zero"); + require(nitroRootCert != bytes32(0), "nitroRootCert must be non-zero"); + require(nitroVerifierId != bytes32(0), "nitroVerifierId must be non-zero"); + + bytes4 setVerifierSelector = RiscZeroSetVerifierLib.selector(setBuilderImageId); + + console.log("=== Deploying RiscZeroSetVerifier + NitroEnclaveVerifier ==="); + console.log("Owner:", owner); + console.log("RISC Zero Verifier Router:", risc0VerifierRouter); + console.log("Set Builder Image ID:", vm.toString(setBuilderImageId)); + console.log("Set Verifier Selector:", vm.toString(setVerifierSelector)); + console.log("Nitro Root Cert:", vm.toString(nitroRootCert)); + console.log("Nitro Verifier ID:", vm.toString(nitroVerifierId)); + console.log(""); + console.log("NOTE: proofSubmitter is set to owner as placeholder."); + console.log(" Update it to TEEProverRegistry after deploying via setProofSubmitter()."); + console.log(""); + + vm.startBroadcast(); + + // ── RiscZeroSetVerifier + // ────────────────────────────────────────────── + // + // Deploy a SetVerifier whose inner VERIFIER is the Router. This ensures + // root seals are dispatched to the correct Groth16 verifier regardless + // of version, avoiding selector mismatches when the Boundless provers + // upgrade to newer Groth16 ControlIDs. + setVerifier = address(new RiscZeroSetVerifier(IRiscZeroVerifier(risc0VerifierRouter), setBuilderImageId, "")); + console.log("RiscZeroSetVerifier:", setVerifier); + console.log(" SELECTOR:", vm.toString(setVerifierSelector)); + + // ── NitroEnclaveVerifier + // ───────────────────────────────────────────── + + ZkCoProcessorConfig memory zkConfig = ZkCoProcessorConfig({ + verifierId: nitroVerifierId, aggregatorId: bytes32(0), zkVerifier: risc0VerifierRouter + }); + + // Start with an empty trusted certs array; certs will be auto-cached on first valid proof. + bytes32[] memory trustedCerts = new bytes32[](0); + + // Use owner as placeholder proofSubmitter; must be updated to TEEProverRegistry + // address after deployment via setProofSubmitter(). + NitroEnclaveVerifier nev = new NitroEnclaveVerifier( + owner, + NITRO_MAX_TIME_DIFF, + trustedCerts, + nitroRootCert, + owner, + ZkCoProcessorType.RiscZero, + zkConfig, + bytes32(0) + ); + nitroEnclaveVerifier = address(nev); + console.log("NitroEnclaveVerifier:", nitroEnclaveVerifier); + + // ── Wire up the per-selector route + // ─────────────────────────────────── + // + // Set inclusion proofs from Boundless carry the SetVerifier selector as + // their first 4 bytes. Route that selector to our local SetVerifier so + // root seals go through the Router (which has the correct Groth16). + nev.addVerifyRoute(ZkCoProcessorType.RiscZero, setVerifierSelector, setVerifier); + console.log(" Route added: selector", vm.toString(setVerifierSelector), "->", setVerifier); + + vm.stopBroadcast(); + + // Print summary + console.log(""); + console.log("========================================"); + console.log(" RISC ZERO STACK + NITRO DEPLOYED"); + console.log("========================================"); + console.log("RiscZeroSetVerifier:", setVerifier); + console.log("NitroEnclaveVerifier:", nitroEnclaveVerifier); + console.log("RISC Zero Router (external):", risc0VerifierRouter); + console.log(""); + console.log(">>> Set nitroEnclaveVerifier in deploy config to:", nitroEnclaveVerifier); + console.log(">>> Then run DeployDevWithNitro.s.sol <<<"); + console.log(">>> Then call setProofSubmitter(TEEProverRegistry) on NitroEnclaveVerifier <<<"); + console.log("========================================"); + + // Write output + string memory json = string.concat( + '{"RiscZeroSetVerifier":"', + vm.toString(setVerifier), + '","NitroEnclaveVerifier":"', + vm.toString(nitroEnclaveVerifier), + '","RiscZeroVerifierRouter":"', + vm.toString(risc0VerifierRouter), + '"}' + ); + string memory outPath = string.concat("deployments/", vm.toString(block.chainid), "-risc0-stack.json"); + vm.writeFile(outPath, json); + console.log("Deployment saved to:", outPath); + } +}