From 71f6224ef305f14ff9e597ce9c5c6685f6214a8f Mon Sep 17 00:00:00 2001 From: Leopold Joy Date: Fri, 27 Mar 2026 15:25:26 +0000 Subject: [PATCH 1/4] Add DeployRiscZeroStack script and simplify DeployDevWithNitro to use pre-deployed NitroEnclaveVerifier --- foundry.toml | 1 + scripts/deploy/DeployConfig.s.sol | 15 +- scripts/multiproof/DeployDevWithNitro.s.sol | 138 +++++---------- scripts/multiproof/DeployRiscZeroStack.s.sol | 168 +++++++++++++++++++ 4 files changed, 224 insertions(+), 98 deletions(-) create mode 100644 scripts/multiproof/DeployRiscZeroStack.s.sol 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..7211a170 100644 --- a/scripts/deploy/DeployConfig.s.sol +++ b/scripts/deploy/DeployConfig.s.sol @@ -105,6 +105,13 @@ contract DeployConfig is Script { uint256 public multiproofIntermediateBlockInterval; uint256 public multiproofProofThreshold; + // NitroEnclaveVerifier Configuration (used by DeployDevWithNitro when deploying the verifier inline) + 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 +215,18 @@ 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); + + 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..8a688cb1 --- /dev/null +++ b/scripts/multiproof/DeployRiscZeroStack.s.sol @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +/** + * @title DeployRiscZeroStack + * @notice Deploys the RISC Zero verifier stack and NitroEnclaveVerifier. + * + * This script is separated from the main deployment scripts because the RISC Zero + * set verifier 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. + * + * ───────────────────────────────────────────────────────────────────────────────── + * USAGE + * ───────────────────────────────────────────────────────────────────────────────── + * + * forge script scripts/multiproof/DeployRiscZeroStack.s.sol:DeployRiscZeroStack \ + * --sig "run(address,bytes32,bytes32,bytes32,address,bytes32)" \ + * \ + * \ + * --broadcast --rpc-url --private-key + * + * Outputs: + * - RiscZeroGroth16Verifier, RiscZeroSetVerifier, RiscZeroVerifierRouter + * - NitroEnclaveVerifier (set `nitroEnclaveVerifier` in deploy config to this address) + * + * ───────────────────────────────────────────────────────────────────────────────── + */ + +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 { IRiscZeroSelectable } from "lib/risc0-ethereum/contracts/src/IRiscZeroSelectable.sol"; +import { RiscZeroVerifierRouter } from "lib/risc0-ethereum/contracts/src/RiscZeroVerifierRouter.sol"; +import { RiscZeroGroth16Verifier } from "lib/risc0-ethereum/contracts/src/groth16/RiscZeroGroth16Verifier.sol"; +import { ControlID } from "lib/risc0-ethereum/contracts/src/groth16/ControlID.sol"; +import { RiscZeroSetVerifier } from "lib/risc0-ethereum/contracts/src/RiscZeroSetVerifier.sol"; + +import { + NitroEnclaveVerifier, + ZkCoProcessorType, + ZkCoProcessorConfig +} from "src/multiproof/tee/NitroEnclaveVerifier.sol"; + +/// @title DeployRiscZeroStack +/// @notice Deploys the RISC Zero verifier stack + NitroEnclaveVerifier for use with DeployDevWithNitro. +contract DeployRiscZeroStack is Script { + /// @notice Maximum attestation age accepted by the NitroEnclaveVerifier (1 hour). + uint64 public constant NITRO_MAX_TIME_DIFF = 3600; + + address public groth16Verifier; + address public setVerifier; + address public router; + address public nitroEnclaveVerifier; + + /// @param owner Owner for the Router and NitroEnclaveVerifier. + /// @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. + /// @param nitroProofSubmitter Address authorized to submit proofs (e.g. TEE registrar). + /// @param nitroVerifierProofId Proof ID for batch verification (bytes32(0) if unused). + function run( + address owner, + bytes32 setBuilderImageId, + bytes32 nitroRootCert, + bytes32 nitroVerifierId, + address nitroProofSubmitter, + bytes32 nitroVerifierProofId + ) public { + require(owner != address(0), "owner 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"); + require(nitroProofSubmitter != address(0), "nitroProofSubmitter must be non-zero"); + + console.log("=== Deploying RISC Zero Verifier Stack + NitroEnclaveVerifier ==="); + console.log("Owner:", owner); + console.log("Set Builder Image ID:", vm.toString(setBuilderImageId)); + console.log("Nitro Root Cert:", vm.toString(nitroRootCert)); + console.log("Nitro Verifier ID:", vm.toString(nitroVerifierId)); + console.log("Nitro Proof Submitter:", nitroProofSubmitter); + console.log(""); + + vm.startBroadcast(); + + // ── RISC Zero verifier stack ────────────────────────────────────── + + // 1. Deploy Groth16 verifier (uses version-locked ControlID constants) + groth16Verifier = address( + new RiscZeroGroth16Verifier(ControlID.CONTROL_ROOT, ControlID.BN254_CONTROL_ID) + ); + console.log("RiscZeroGroth16Verifier:", groth16Verifier); + + // 2. Deploy set verifier (delegates root verification to Groth16 verifier) + setVerifier = address( + new RiscZeroSetVerifier(IRiscZeroVerifier(groth16Verifier), setBuilderImageId, "") + ); + console.log("RiscZeroSetVerifier:", setVerifier); + + // 3. Deploy router and register both verifiers + RiscZeroVerifierRouter routerContract = new RiscZeroVerifierRouter(owner); + router = address(routerContract); + console.log("RiscZeroVerifierRouter:", router); + + bytes4 groth16Selector = IRiscZeroSelectable(groth16Verifier).SELECTOR(); + bytes4 setVerifierSelector = IRiscZeroSelectable(setVerifier).SELECTOR(); + routerContract.addVerifier(groth16Selector, IRiscZeroVerifier(groth16Verifier)); + routerContract.addVerifier(setVerifierSelector, IRiscZeroVerifier(setVerifier)); + console.log(" Groth16 selector:", vm.toString(groth16Selector)); + console.log(" SetVerifier selector:", vm.toString(setVerifierSelector)); + + // ── NitroEnclaveVerifier ────────────────────────────────────────── + + ZkCoProcessorConfig memory zkConfig = ZkCoProcessorConfig({ + verifierId: nitroVerifierId, + aggregatorId: bytes32(0), + zkVerifier: router + }); + + // Start with an empty trusted certs array; certs will be auto-cached on first valid proof. + bytes32[] memory trustedCerts = new bytes32[](0); + + nitroEnclaveVerifier = address( + new NitroEnclaveVerifier( + owner, + NITRO_MAX_TIME_DIFF, + trustedCerts, + nitroRootCert, + nitroProofSubmitter, + ZkCoProcessorType.RiscZero, + zkConfig, + nitroVerifierProofId + ) + ); + console.log("NitroEnclaveVerifier:", nitroEnclaveVerifier); + + vm.stopBroadcast(); + + // Print summary + console.log(""); + console.log("========================================"); + console.log(" RISC ZERO STACK + NITRO DEPLOYED"); + console.log("========================================"); + console.log("RiscZeroGroth16Verifier:", groth16Verifier); + console.log("RiscZeroSetVerifier:", setVerifier); + console.log("RiscZeroVerifierRouter:", router); + console.log("NitroEnclaveVerifier:", nitroEnclaveVerifier); + console.log(""); + console.log(">>> Set nitroEnclaveVerifier in deploy config to:", nitroEnclaveVerifier); + console.log(">>> Then run DeployDevWithNitro.s.sol <<<"); + console.log("========================================"); + + // Write output + string memory json = string.concat( + '{"RiscZeroGroth16Verifier":"', vm.toString(groth16Verifier), + '","RiscZeroSetVerifier":"', vm.toString(setVerifier), + '","RiscZeroVerifierRouter":"', vm.toString(router), + '","NitroEnclaveVerifier":"', vm.toString(nitroEnclaveVerifier), + '"}' + ); + string memory outPath = string.concat( + "deployments/", vm.toString(block.chainid), "-risc0-stack.json" + ); + vm.writeFile(outPath, json); + console.log("Deployment saved to:", outPath); + } +} From 0faf961a1007593ac2ba8eef45935e8be8839736 Mon Sep 17 00:00:00 2001 From: Leopold Joy Date: Fri, 27 Mar 2026 21:57:15 +0000 Subject: [PATCH 2/4] Update sepolia.json to include 2 additional RiscZero-related deployment fields --- deploy-config/sepolia.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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" } From 91dfb7f031ef24bc22c896fef63492a9caa8cbfd Mon Sep 17 00:00:00 2001 From: Leopold Joy Date: Sat, 28 Mar 2026 02:12:17 +0000 Subject: [PATCH 3/4] refactor: simplify DeployRiscZeroStack to use existing RISC Zero router instead of deploying full verifier stack --- scripts/deploy/DeployConfig.s.sol | 4 +- scripts/multiproof/DeployRiscZeroStack.s.sol | 155 ++++++++++--------- 2 files changed, 88 insertions(+), 71 deletions(-) diff --git a/scripts/deploy/DeployConfig.s.sol b/scripts/deploy/DeployConfig.s.sol index 7211a170..509c3f88 100644 --- a/scripts/deploy/DeployConfig.s.sol +++ b/scripts/deploy/DeployConfig.s.sol @@ -105,7 +105,8 @@ contract DeployConfig is Script { uint256 public multiproofIntermediateBlockInterval; uint256 public multiproofProofThreshold; - // NitroEnclaveVerifier Configuration (used by DeployDevWithNitro when deploying the verifier inline) + // RISC Zero / NitroEnclaveVerifier Configuration (reference values; DeployRiscZeroStack takes these as CLI args) + bytes32 public risc0SetBuilderImageId; bytes32 public nitroRootCert; bytes32 public nitroVerifierId; bytes32 public nitroVerifierProofId; @@ -222,6 +223,7 @@ contract DeployConfig is Script { 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)); diff --git a/scripts/multiproof/DeployRiscZeroStack.s.sol b/scripts/multiproof/DeployRiscZeroStack.s.sol index 8a688cb1..3a89915a 100644 --- a/scripts/multiproof/DeployRiscZeroStack.s.sol +++ b/scripts/multiproof/DeployRiscZeroStack.s.sol @@ -3,26 +3,48 @@ pragma solidity ^0.8.20; /** * @title DeployRiscZeroStack - * @notice Deploys the RISC Zero verifier stack and NitroEnclaveVerifier. + * @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 RISC Zero - * set verifier 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. + * 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,bytes32,bytes32,bytes32,address,bytes32)" \ - * \ - * \ + * --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: - * - RiscZeroGroth16Verifier, RiscZeroSetVerifier, RiscZeroVerifierRouter - * - NitroEnclaveVerifier (set `nitroEnclaveVerifier` in deploy config to this address) + * - 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 * * ───────────────────────────────────────────────────────────────────────────────── */ @@ -31,11 +53,7 @@ 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 { IRiscZeroSelectable } from "lib/risc0-ethereum/contracts/src/IRiscZeroSelectable.sol"; -import { RiscZeroVerifierRouter } from "lib/risc0-ethereum/contracts/src/RiscZeroVerifierRouter.sol"; -import { RiscZeroGroth16Verifier } from "lib/risc0-ethereum/contracts/src/groth16/RiscZeroGroth16Verifier.sol"; -import { ControlID } from "lib/risc0-ethereum/contracts/src/groth16/ControlID.sol"; -import { RiscZeroSetVerifier } from "lib/risc0-ethereum/contracts/src/RiscZeroSetVerifier.sol"; +import { RiscZeroSetVerifier, RiscZeroSetVerifierLib } from "lib/risc0-ethereum/contracts/src/RiscZeroSetVerifier.sol"; import { NitroEnclaveVerifier, @@ -44,97 +62,95 @@ import { } from "src/multiproof/tee/NitroEnclaveVerifier.sol"; /// @title DeployRiscZeroStack -/// @notice Deploys the RISC Zero verifier stack + NitroEnclaveVerifier for use with DeployDevWithNitro. +/// @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 groth16Verifier; address public setVerifier; - address public router; address public nitroEnclaveVerifier; - /// @param owner Owner for the Router and NitroEnclaveVerifier. - /// @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. - /// @param nitroProofSubmitter Address authorized to submit proofs (e.g. TEE registrar). - /// @param nitroVerifierProofId Proof ID for batch verification (bytes32(0) if unused). + /// @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, - address nitroProofSubmitter, - bytes32 nitroVerifierProofId + 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"); - require(nitroProofSubmitter != address(0), "nitroProofSubmitter must be non-zero"); - console.log("=== Deploying RISC Zero Verifier Stack + NitroEnclaveVerifier ==="); + 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("Nitro Proof Submitter:", nitroProofSubmitter); + 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(); - // ── RISC Zero verifier stack ────────────────────────────────────── - - // 1. Deploy Groth16 verifier (uses version-locked ControlID constants) - groth16Verifier = address( - new RiscZeroGroth16Verifier(ControlID.CONTROL_ROOT, ControlID.BN254_CONTROL_ID) - ); - console.log("RiscZeroGroth16Verifier:", groth16Verifier); - - // 2. Deploy set verifier (delegates root verification to Groth16 verifier) + // ── 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(groth16Verifier), setBuilderImageId, "") + new RiscZeroSetVerifier(IRiscZeroVerifier(risc0VerifierRouter), setBuilderImageId, "") ); console.log("RiscZeroSetVerifier:", setVerifier); + console.log(" SELECTOR:", vm.toString(setVerifierSelector)); - // 3. Deploy router and register both verifiers - RiscZeroVerifierRouter routerContract = new RiscZeroVerifierRouter(owner); - router = address(routerContract); - console.log("RiscZeroVerifierRouter:", router); - - bytes4 groth16Selector = IRiscZeroSelectable(groth16Verifier).SELECTOR(); - bytes4 setVerifierSelector = IRiscZeroSelectable(setVerifier).SELECTOR(); - routerContract.addVerifier(groth16Selector, IRiscZeroVerifier(groth16Verifier)); - routerContract.addVerifier(setVerifierSelector, IRiscZeroVerifier(setVerifier)); - console.log(" Groth16 selector:", vm.toString(groth16Selector)); - console.log(" SetVerifier selector:", vm.toString(setVerifierSelector)); - - // ── NitroEnclaveVerifier ────────────────────────────────────────── + // ── NitroEnclaveVerifier ───────────────────────────────────────────── ZkCoProcessorConfig memory zkConfig = ZkCoProcessorConfig({ verifierId: nitroVerifierId, aggregatorId: bytes32(0), - zkVerifier: router + zkVerifier: risc0VerifierRouter }); // Start with an empty trusted certs array; certs will be auto-cached on first valid proof. bytes32[] memory trustedCerts = new bytes32[](0); - nitroEnclaveVerifier = address( - new NitroEnclaveVerifier( - owner, - NITRO_MAX_TIME_DIFF, - trustedCerts, - nitroRootCert, - nitroProofSubmitter, - ZkCoProcessorType.RiscZero, - zkConfig, - nitroVerifierProofId - ) + // 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 @@ -142,21 +158,20 @@ contract DeployRiscZeroStack is Script { console.log("========================================"); console.log(" RISC ZERO STACK + NITRO DEPLOYED"); console.log("========================================"); - console.log("RiscZeroGroth16Verifier:", groth16Verifier); console.log("RiscZeroSetVerifier:", setVerifier); - console.log("RiscZeroVerifierRouter:", router); 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( - '{"RiscZeroGroth16Verifier":"', vm.toString(groth16Verifier), - '","RiscZeroSetVerifier":"', vm.toString(setVerifier), - '","RiscZeroVerifierRouter":"', vm.toString(router), + '{"RiscZeroSetVerifier":"', vm.toString(setVerifier), '","NitroEnclaveVerifier":"', vm.toString(nitroEnclaveVerifier), + '","RiscZeroVerifierRouter":"', vm.toString(risc0VerifierRouter), '"}' ); string memory outPath = string.concat( From 2ea3529157c18c2681c8782a46517a133e755baa Mon Sep 17 00:00:00 2001 From: Leopold Joy Date: Sat, 28 Mar 2026 02:14:06 +0000 Subject: [PATCH 4/4] fix fmt --- scripts/multiproof/DeployRiscZeroStack.s.sol | 34 +++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/scripts/multiproof/DeployRiscZeroStack.s.sol b/scripts/multiproof/DeployRiscZeroStack.s.sol index 3a89915a..91f7b366 100644 --- a/scripts/multiproof/DeployRiscZeroStack.s.sol +++ b/scripts/multiproof/DeployRiscZeroStack.s.sol @@ -82,7 +82,9 @@ contract DeployRiscZeroStack is Script { bytes32 setBuilderImageId, bytes32 nitroRootCert, bytes32 nitroVerifierId - ) public { + ) + 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"); @@ -105,24 +107,22 @@ contract DeployRiscZeroStack is Script { vm.startBroadcast(); - // ── RiscZeroSetVerifier ────────────────────────────────────────────── + // ── 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, "") - ); + setVerifier = address(new RiscZeroSetVerifier(IRiscZeroVerifier(risc0VerifierRouter), setBuilderImageId, "")); console.log("RiscZeroSetVerifier:", setVerifier); console.log(" SELECTOR:", vm.toString(setVerifierSelector)); - // ── NitroEnclaveVerifier ───────────────────────────────────────────── + // ── NitroEnclaveVerifier + // ───────────────────────────────────────────── ZkCoProcessorConfig memory zkConfig = ZkCoProcessorConfig({ - verifierId: nitroVerifierId, - aggregatorId: bytes32(0), - zkVerifier: risc0VerifierRouter + verifierId: nitroVerifierId, aggregatorId: bytes32(0), zkVerifier: risc0VerifierRouter }); // Start with an empty trusted certs array; certs will be auto-cached on first valid proof. @@ -143,7 +143,8 @@ contract DeployRiscZeroStack is Script { nitroEnclaveVerifier = address(nev); console.log("NitroEnclaveVerifier:", nitroEnclaveVerifier); - // ── Wire up the per-selector route ─────────────────────────────────── + // ── 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 @@ -169,14 +170,15 @@ contract DeployRiscZeroStack is Script { // Write output string memory json = string.concat( - '{"RiscZeroSetVerifier":"', vm.toString(setVerifier), - '","NitroEnclaveVerifier":"', vm.toString(nitroEnclaveVerifier), - '","RiscZeroVerifierRouter":"', vm.toString(risc0VerifierRouter), + '{"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" - ); + string memory outPath = string.concat("deployments/", vm.toString(block.chainid), "-risc0-stack.json"); vm.writeFile(outPath, json); console.log("Deployment saved to:", outPath); }