Skip to content
Merged
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
8 changes: 4 additions & 4 deletions snapshots/semver-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -248,12 +248,12 @@
"sourceCodeHash": "0xb9786dc79b4b494d81905235f7bda044c5b1a98ad82416829f5f6948be1175cc"
},
"src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal": {
"initCodeHash": "0x76da4f2a736d7a39a01720e5d900a85fcaa60ba0430fcacbb8ab367f55ba5411",
"sourceCodeHash": "0xa6261402efe0105e2a4f9369818bafb4e65515e51850b44d47504151e1c39d01"
"initCodeHash": "0xee219c003a6af440b447e214e43d520e802001ae3d557262a7921ca3d57ebddf",
"sourceCodeHash": "0xe350108585e0855f10bac20d0e8894b3de9afe3be413908b4c59a1036e7a9842"
},
"src/multiproof/tee/SystemConfigGlobal.sol:SystemConfigGlobal:dispute": {
"initCodeHash": "0xfae3a71157f3c64a7bda037ec116bf6e7265099397d9bcad22c01cc1f029ed7d",
"sourceCodeHash": "0xa6261402efe0105e2a4f9369818bafb4e65515e51850b44d47504151e1c39d01"
"initCodeHash": "0x8ae045f0121d2c63ab6f0a830be842aaf0445096bfabe29d85cfd9bd38b40565",
"sourceCodeHash": "0xe350108585e0855f10bac20d0e8894b3de9afe3be413908b4c59a1036e7a9842"
},
"src/multiproof/tee/TEEVerifier.sol:TEEVerifier": {
"initCodeHash": "0x78317d9088a26523d938ce0d88ed0134abc60292c0cdb3f8a6a9530638fd9e9a",
Expand Down
4 changes: 4 additions & 0 deletions src/multiproof/mocks/MockDevSystemConfigGlobal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity 0.8.15;
import {
INitroEnclaveVerifier
} from "lib/aws-nitro-enclave-attestation/contracts/src/interfaces/INitroEnclaveVerifier.sol";
import { EnumerableSetLib } from "@solady-v0.0.245/utils/EnumerableSetLib.sol";

import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol";

Expand All @@ -12,6 +13,8 @@ import { SystemConfigGlobal } from "src/multiproof/tee/SystemConfigGlobal.sol";
/// @dev This contract adds addDevSigner() which bypasses AWS Nitro attestation verification.
/// DO NOT deploy this contract to production networks.
contract DevSystemConfigGlobal is SystemConfigGlobal {
using EnumerableSetLib for EnumerableSetLib.AddressSet;

constructor(INitroEnclaveVerifier nitroVerifier) SystemConfigGlobal(nitroVerifier) { }

/// @notice Registers a signer for testing (bypasses attestation verification).
Expand All @@ -20,6 +23,7 @@ contract DevSystemConfigGlobal is SystemConfigGlobal {
/// @param pcr0Hash The PCR0 hash to associate with this signer.
function addDevSigner(address signer, bytes32 pcr0Hash) external onlyOwner {
signerPCR0[signer] = pcr0Hash;
_registeredSigners.add(signer);
emit SignerRegistered(signer, pcr0Hash);
}
}
21 changes: 19 additions & 2 deletions src/multiproof/tee/SystemConfigGlobal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from "lib/aws-nitro-enclave-attestation/contracts/src/interfaces/INitroEnclaveVerifier.sol";
import { OwnableManagedUpgradeable } from "lib/op-enclave/contracts/src/OwnableManagedUpgradeable.sol";
import { ISemver } from "interfaces/universal/ISemver.sol";
import { EnumerableSetLib } from "@solady-v0.0.245/utils/EnumerableSetLib.sol";

/// @title SystemConfigGlobal
/// @notice Manages TEE signer registration via ZK-verified AWS Nitro attestation.
Expand All @@ -19,6 +20,7 @@ import { ISemver } from "interfaces/universal/ISemver.sol";
/// Each signer is associated with the PCR0 (enclave image hash) from their attestation,
/// which allows TEEVerifier to validate that a signer was registered with a specific image.
contract SystemConfigGlobal is OwnableManagedUpgradeable, ISemver {
using EnumerableSetLib for EnumerableSetLib.AddressSet;
/// @notice Maximum age of an attestation document (60 minutes), in seconds.
uint256 public constant MAX_AGE = 60 minutes;

Expand All @@ -41,6 +43,11 @@ contract SystemConfigGlobal is OwnableManagedUpgradeable, ISemver {
/// @notice Mapping of whether an address is a valid proposer.
mapping(address => bool) public isValidProposer;

/// @notice Enumerable set of all currently registered signer addresses.
/// @dev Kept in sync with `signerPCR0`: add on register, remove on deregister.
/// Enables O(1) on-chain enumeration via `getRegisteredSigners()`.
EnumerableSetLib.AddressSet internal _registeredSigners;

/// @notice Emitted when a signer is registered.
event SignerRegistered(address indexed signer, bytes32 indexed pcr0);

Expand Down Expand Up @@ -128,13 +135,15 @@ contract SystemConfigGlobal is OwnableManagedUpgradeable, ISemver {
address enclaveAddress = address(uint160(uint256(publicKeyHash)));

signerPCR0[enclaveAddress] = pcr0Hash;
_registeredSigners.add(enclaveAddress);
emit SignerRegistered(enclaveAddress, pcr0Hash);
}

/// @notice Deregisters a signer.
/// @param signer The address of the signer to deregister.
function deregisterSigner(address signer) external onlyOwnerOrManager {
delete signerPCR0[signer];
_registeredSigners.remove(signer);
emit SignerDeregistered(signer);
}

Expand All @@ -145,6 +154,14 @@ contract SystemConfigGlobal is OwnableManagedUpgradeable, ISemver {
return signerPCR0[signer] != bytes32(0);
}

/// @notice Returns all currently registered signer addresses.
/// @dev Reads directly from the on-chain enumerable set — no event scanning required.
/// The order of addresses in the returned array is not guaranteed.
/// @return An array of all registered signer addresses.
function getRegisteredSigners() external view returns (address[] memory) {
return _registeredSigners.values();
}

/// @notice Initializes the contract with owner and manager.
/// @param initialOwner The initial owner address.
/// @param initialManager The initial manager address.
Expand All @@ -155,9 +172,9 @@ contract SystemConfigGlobal is OwnableManagedUpgradeable, ISemver {
}

/// @notice Semantic version.
/// @custom:semver 0.1.0
/// @custom:semver 0.2.0
function version() public pure virtual returns (string memory) {
return "0.1.0";
return "0.2.0";
}

/// @dev Finds PCR0 (index 0) in the PCR array and returns its keccak256 hash.
Expand Down
118 changes: 117 additions & 1 deletion test/multiproof/SystemConfigGlobal.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ contract SystemConfigGlobalTest is Test {
function testInitialization() public view {
assertEq(systemConfigGlobal.owner(), owner);
assertEq(systemConfigGlobal.manager(), manager);
assertEq(systemConfigGlobal.version(), "0.1.0");
assertEq(systemConfigGlobal.version(), "0.2.0");
}

// ============ PCR0 Registration Tests ============
Expand Down Expand Up @@ -394,4 +394,120 @@ contract SystemConfigGlobalTest is Test {
assertEq(systemConfigGlobal.signerPCR0(signer2), pcr0Hash2);
assertEq(systemConfigGlobal.signerPCR0(signer3), pcr0Hash3);
}

// ============ getRegisteredSigners Tests ============

function testGetRegisteredSignersEmpty() public view {
address[] memory signers = systemConfigGlobal.getRegisteredSigners();
assertEq(signers.length, 0);
}

function testGetRegisteredSignersAfterRegister() public {
address signer = makeAddr("signer");
bytes32 signerPcr0 = keccak256("pcr0");

vm.prank(owner);
systemConfigGlobal.addDevSigner(signer, signerPcr0);

address[] memory signers = systemConfigGlobal.getRegisteredSigners();
assertEq(signers.length, 1);
assertEq(signers[0], signer);
}

function testGetRegisteredSignersAfterDeregister() public {
address signer = makeAddr("signer");
bytes32 signerPcr0 = keccak256("pcr0");

vm.prank(owner);
systemConfigGlobal.addDevSigner(signer, signerPcr0);

assertEq(systemConfigGlobal.getRegisteredSigners().length, 1);

vm.prank(owner);
systemConfigGlobal.deregisterSigner(signer);

address[] memory signers = systemConfigGlobal.getRegisteredSigners();
assertEq(signers.length, 0);
}

function testGetRegisteredSignersMultiple() public {
address signer1 = makeAddr("signer-1");
address signer2 = makeAddr("signer-2");
address signer3 = makeAddr("signer-3");

bytes32 sharedPcr0 = keccak256("pcr0");

vm.startPrank(owner);
systemConfigGlobal.addDevSigner(signer1, sharedPcr0);
systemConfigGlobal.addDevSigner(signer2, sharedPcr0);
systemConfigGlobal.addDevSigner(signer3, sharedPcr0);
vm.stopPrank();

address[] memory signers = systemConfigGlobal.getRegisteredSigners();
assertEq(signers.length, 3);

// Verify all three are present (order not guaranteed)
bool foundSigner1;
bool foundSigner2;
bool foundSigner3;
for (uint256 i = 0; i < signers.length; i++) {
if (signers[i] == signer1) foundSigner1 = true;
if (signers[i] == signer2) foundSigner2 = true;
if (signers[i] == signer3) foundSigner3 = true;
}
assertTrue(foundSigner1);
assertTrue(foundSigner2);
assertTrue(foundSigner3);
}

function testGetRegisteredSignersConsistencyAfterMixedOperations() public {
address signer1 = makeAddr("signer-1");
address signer2 = makeAddr("signer-2");
address signer3 = makeAddr("signer-3");

bytes32 sharedPcr0 = keccak256("pcr0");

// Register three signers
vm.startPrank(owner);
systemConfigGlobal.addDevSigner(signer1, sharedPcr0);
systemConfigGlobal.addDevSigner(signer2, sharedPcr0);
systemConfigGlobal.addDevSigner(signer3, sharedPcr0);
vm.stopPrank();

assertEq(systemConfigGlobal.getRegisteredSigners().length, 3);

// Deregister the middle one
vm.prank(manager);
systemConfigGlobal.deregisterSigner(signer2);

address[] memory signers = systemConfigGlobal.getRegisteredSigners();
assertEq(signers.length, 2);

// Mapping and set stay consistent
for (uint256 i = 0; i < signers.length; i++) {
assertTrue(systemConfigGlobal.isValidSigner(signers[i]));
assertNotEq(signers[i], signer2);
}

// Deregistered signer not in mapping either
assertFalse(systemConfigGlobal.isValidSigner(signer2));
assertEq(systemConfigGlobal.signerPCR0(signer2), bytes32(0));
}

function testGetRegisteredSignersDeregisterIdempotent() public {
address signer = makeAddr("signer");
bytes32 signerPcr0 = keccak256("pcr0");

vm.prank(owner);
systemConfigGlobal.addDevSigner(signer, signerPcr0);

vm.prank(owner);
systemConfigGlobal.deregisterSigner(signer);

// Deregistering again should not revert and set should still be empty
vm.prank(owner);
systemConfigGlobal.deregisterSigner(signer);

assertEq(systemConfigGlobal.getRegisteredSigners().length, 0);
}
}