From cfd4951b053f930a749e1b1237ab747f891b5eac Mon Sep 17 00:00:00 2001 From: chunter-cb Date: Tue, 22 Oct 2024 16:50:28 -0400 Subject: [PATCH] Add nitro validator (#6) --- .gitmodules | 11 ++ foundry.toml | 5 +- nitro-validator/.gitignore | 8 + nitro-validator/README.md | 45 +++++ .../84532-nitro-validator-deploy.json | 1 + nitro-validator/foundry.toml | 13 ++ nitro-validator/lib/NitroProver | 1 + nitro-validator/lib/forge-std | 1 + nitro-validator/lib/solidity-cbor | 1 + nitro-validator/remappings.txt | 3 + .../script/DeployNitroValidator.s.sol | 29 ++++ nitro-validator/src/INitroValidator.sol | 13 ++ nitro-validator/src/NitroValidator.sol | 160 ++++++++++++++++++ nitro-validator/test/NitroValidator.t.sol | 34 ++++ .../nitro-attestation/sample_attestation.bin | Bin 0 -> 4482 bytes .../nitro-attestation/sample_attestation2.bin | Bin 0 -> 4480 bytes remappings.txt | 1 + script/DeploySystem.s.sol | 16 +- src/SystemConfigGlobal.sol | 18 +- test/SystemGlobalConfig.t.sol | 50 ++++++ 20 files changed, 400 insertions(+), 10 deletions(-) create mode 100644 nitro-validator/.gitignore create mode 100644 nitro-validator/README.md create mode 100644 nitro-validator/deployments/84532-nitro-validator-deploy.json create mode 100644 nitro-validator/foundry.toml create mode 160000 nitro-validator/lib/NitroProver create mode 160000 nitro-validator/lib/forge-std create mode 160000 nitro-validator/lib/solidity-cbor create mode 100644 nitro-validator/remappings.txt create mode 100644 nitro-validator/script/DeployNitroValidator.s.sol create mode 100644 nitro-validator/src/INitroValidator.sol create mode 100644 nitro-validator/src/NitroValidator.sol create mode 100644 nitro-validator/test/NitroValidator.t.sol create mode 100644 nitro-validator/test/nitro-attestation/sample_attestation.bin create mode 100644 nitro-validator/test/nitro-attestation/sample_attestation2.bin create mode 100644 test/SystemGlobalConfig.t.sol diff --git a/.gitmodules b/.gitmodules index bffbb9c..6d341d4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,14 @@ [submodule "lib/optimism"] path = lib/optimism url = https://github.com/ethereum-optimism/optimism + +# Nitro Validator +[submodule "nitro-validator/lib/NitroProver"] + path = nitro-validator/lib/NitroProver + url = https://github.com/marlinprotocol/NitroProver +[submodule "nitro-validator/lib/solidity-cbor"] + path = nitro-validator/lib/solidity-cbor + url = https://github.com/marlinprotocol/solidity-cbor +[submodule "nitro-validator/lib/forge-std"] + path = nitro-validator/lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/foundry.toml b/foundry.toml index 7920eca..0925ac1 100644 --- a/foundry.toml +++ b/foundry.toml @@ -17,5 +17,8 @@ evm_version = "cancun" fs_permissions = [ { access='read', path='./deploy-config/' }, - { access='read-write', path='./deployments/' } + { access='read-write', path='./deployments/' }, + { access='read', path='./nitro-validator/' } ] + +exclude = ['nitro-validator/**'] diff --git a/nitro-validator/.gitignore b/nitro-validator/.gitignore new file mode 100644 index 0000000..cc732a2 --- /dev/null +++ b/nitro-validator/.gitignore @@ -0,0 +1,8 @@ +/.idea/ +.DS_Store +/out/ +/cache/ +/testnet/data/ +/testnet/.env +/deployments/*-*-*.json +/bin/ \ No newline at end of file diff --git a/nitro-validator/README.md b/nitro-validator/README.md new file mode 100644 index 0000000..fdd3727 --- /dev/null +++ b/nitro-validator/README.md @@ -0,0 +1,45 @@ +## Nitro Validator + +This directory is used to build the nitro validator. It currently needs both solidity ^0.8.24 and to be compiled with via-ir and therefore cant be in the same src directory. + +This should be a singleton deployed on the L1. + + +### Build + +```shell +$ forge build +``` + +### Test + +```shell +$ forge test +``` + +### Format + +```shell +$ forge fmt +``` + +### Deploy + +```shell +$ forge script script/DeployNitroValidator.s.sol:DeployNitroValidator --rpc-url +``` + + +### Cast + +```shell +$ cast +``` + +### Help + +```shell +$ forge --help +$ anvil --help +$ cast --help +``` diff --git a/nitro-validator/deployments/84532-nitro-validator-deploy.json b/nitro-validator/deployments/84532-nitro-validator-deploy.json new file mode 100644 index 0000000..b9075fa --- /dev/null +++ b/nitro-validator/deployments/84532-nitro-validator-deploy.json @@ -0,0 +1 @@ +{"address": "0x20823F43A70eEE2F92A6f44cC378566C6c7d4b0E"} \ No newline at end of file diff --git a/nitro-validator/foundry.toml b/nitro-validator/foundry.toml new file mode 100644 index 0000000..592d743 --- /dev/null +++ b/nitro-validator/foundry.toml @@ -0,0 +1,13 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +fs_permissions = [ + { access = "read", path = "./test/nitro-attestation"}, + { access='read-write', path='./deployments/' }, + { access = "read-write", path = "./nitro-validator/deployments/" } +] + + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/nitro-validator/lib/NitroProver b/nitro-validator/lib/NitroProver new file mode 160000 index 0000000..8923d35 --- /dev/null +++ b/nitro-validator/lib/NitroProver @@ -0,0 +1 @@ +Subproject commit 8923d350f8b82af713b1bba6797e8681b77a6d30 diff --git a/nitro-validator/lib/forge-std b/nitro-validator/lib/forge-std new file mode 160000 index 0000000..1de6eec --- /dev/null +++ b/nitro-validator/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 1de6eecf821de7fe2c908cc48d3ab3dced20717f diff --git a/nitro-validator/lib/solidity-cbor b/nitro-validator/lib/solidity-cbor new file mode 160000 index 0000000..6b86835 --- /dev/null +++ b/nitro-validator/lib/solidity-cbor @@ -0,0 +1 @@ +Subproject commit 6b868352a0822f14136219ed6a5d20e3d74304ae diff --git a/nitro-validator/remappings.txt b/nitro-validator/remappings.txt new file mode 100644 index 0000000..520d75d --- /dev/null +++ b/nitro-validator/remappings.txt @@ -0,0 +1,3 @@ +@marlinprotocol/=lib/NitroProver/src/ +@solidity-cbor/=lib/solidity-cbor/packages/solidity-cbor/contracts/ +forge-std/=lib/forge-std/src/ diff --git a/nitro-validator/script/DeployNitroValidator.s.sol b/nitro-validator/script/DeployNitroValidator.s.sol new file mode 100644 index 0000000..969171f --- /dev/null +++ b/nitro-validator/script/DeployNitroValidator.s.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {Script} from "forge-std/Script.sol"; +import {console2 as console} from "forge-std/console2.sol"; +import {NitroValidator} from "src/NitroValidator.sol"; + +/// @notice will deploy the singleton NitroValidatorContract to a deterministic address +contract DeployNitroValidator is Script { + bytes32 constant SALT = bytes32(uint256(0x4E313752304F5)); // todo + + function run() public returns (address addr_) { + vm.startBroadcast(); + + addr_ = address(new NitroValidator{salt: SALT}()); + + console.log("NitroValidator to be deployed at:", addr_); + + // Save the address to the deployment file + string memory deploymentJson = string.concat("{", '"address": "', vm.toString(addr_), '"}'); + + vm.writeFile( + string.concat(vm.projectRoot(), "/deployments/", vm.toString(block.chainid), "-nitro-validator-deploy.json"), + deploymentJson + ); + + vm.stopBroadcast(); + } +} diff --git a/nitro-validator/src/INitroValidator.sol b/nitro-validator/src/INitroValidator.sol new file mode 100644 index 0000000..2076d52 --- /dev/null +++ b/nitro-validator/src/INitroValidator.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +interface INitroValidator { + /// @notice Verifies an AWS Nitro attestation + /// @param attestation The attestation document + /// @param maxAge Maximum age of the attestation in seconds + /// @return enclavePubKey The enclave's public key + /// @return pcr0 User data included in the attestation + function validateAttestation(bytes memory attestation, uint256 maxAge) + external view + returns (bytes memory enclavePubKey, bytes memory pcr0); +} diff --git a/nitro-validator/src/NitroValidator.sol b/nitro-validator/src/NitroValidator.sol new file mode 100644 index 0000000..851638d --- /dev/null +++ b/nitro-validator/src/NitroValidator.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {CBORDecoding} from "@solidity-cbor/CBORDecoding.sol"; +import {CBOR} from "@solidity-cbor/CBOREncoding.sol"; +import {ByteParser} from "@solidity-cbor/ByteParser.sol"; +import {INitroValidator} from "./INitroValidator.sol"; +import {NitroProver} from "@marlinprotocol/NitroProver.sol"; + +contract NitroValidator is NitroProver, INitroValidator { + /// @notice based off of NitroProver's verifyAttestation. Will validate an attestation and return the public key and PRC0 used + function validateAttestation(bytes memory attestation, uint256 maxAge) + external view + returns (bytes memory, bytes memory) + { + /* + https://github.com/aws/aws-nitro-enclaves-nsm-api/blob/main/docs/attestation_process.md#31-cose-and-cbor + Attestation document is an array of 4 elements + [ + protected: Header, + unprotected: Header, + payload: This field contains the serialized content to be signed, + signature: This field contains the computed signature value. + ] + */ + + // GAS: Attestation decode gas ~62k + bytes[] memory attestationDecoded = CBORDecoding.decodeArray(attestation); + + // TODO: confirm that the attestation is untagged CBOR structure + // https://datatracker.ietf.org/doc/html/rfc8152#section-3.1 + // Protected header for COSE_Sign1 + bytes[2][] memory protectedHeader = CBORDecoding.decodeMapping(attestationDecoded[0]); + // Protected header should have algorithm flag which is specified by 1 + require(ByteParser.bytesToUint64(protectedHeader[0][0]) == 1, "Not algo flag"); + // Algorithm should be ECDSA w/ SHA-384 + require(ByteParser.bytesToNegativeInt128(protectedHeader[0][1]) == -35, "Incorrect algorithm"); + // Protected header should just have sig algo flag + require(protectedHeader.length == 1, "Only algo flag should be present"); + + // Unprotected header for COSE_Sign1 + bytes[2][] memory unprotectedHeader = CBORDecoding.decodeMapping(attestationDecoded[1]); + // Unprotected header should be empty + require(unprotectedHeader.length == 0, "Unprotected header should be empty"); + + bytes memory payload = attestationDecoded[2]; + (bytes memory certPubKey, bytes memory enclavePubKey, bytes memory pcr0) = + _getAttestationDocKeysAndPCR0(payload, maxAge); + + // verify COSE signature as per https://www.rfc-editor.org/rfc/rfc9052.html#section-4.4 + bytes memory attestationSig = attestationDecoded[3]; + + // create COSE structure + // GAS: COSE structure creation gas ~42.7k + // TODO: set CBOR length appropriately + CBOR.CBORBuffer memory buf = CBOR.create(payload.length * 2); + CBOR.startFixedArray(buf, 4); + // context to be written as Signature1 as COSE_Sign1 is used https://www.rfc-editor.org/rfc/rfc9052.html#section-4.4-2.1.1 + CBOR.writeString(buf, "Signature1"); + // Protected headers to be added https://www.rfc-editor.org/rfc/rfc9052.html#section-4.4-2.2 + CBOR.writeBytes(buf, attestationDecoded[0]); + // externally supplied data is empty https://www.rfc-editor.org/rfc/rfc9052.html#section-4.4-2.4 + CBOR.writeBytes(buf, ""); + // Payload to be added https://www.rfc-editor.org/rfc/rfc9052.html#section-4.4-2.5 + CBOR.writeBytes(buf, payload); + + _processSignature(attestationSig, certPubKey, buf.buf.buf); + return (enclavePubKey, pcr0); + } + + /// @notice validates the attestation payload and returns the used public key, enclave public key and pcr0 used + /// @return certPubKey certificate public key + /// @return enclavePubKey enclave public key + /// @return pcr0 platform configuration register 0 + function _getAttestationDocKeysAndPCR0(bytes memory attestationPayload, uint256 maxAge) + internal + view + returns (bytes memory certPubKey, bytes memory enclavePubKey, bytes memory pcr0) + { + // TODO: validate if this check is expected? https://github.com/aws/aws-nitro-enclaves-nsm-api/blob/main/docs/attestation_process.md?plain=1#L168 + require(attestationPayload.length <= 2 ** 15, "Attestation too long"); + + // validations as per https://github.com/aws/aws-nitro-enclaves-nsm-api/blob/main/docs/attestation_process.md#32-syntactical-validation + // issuing Nitro hypervisor module ID + // GAS: decoding takes ~173.5k gas + bytes[2][] memory attestationStructure = CBORDecoding.decodeMapping(attestationPayload); + bytes memory moduleId; + bytes memory rawTimestamp; + bytes memory digest; + bytes memory rawPcrs; + bytes memory certificate; + bytes memory cabundle; + bytes memory userData; + + for (uint256 i = 0; i < attestationStructure.length; i++) { + bytes32 keyHash = keccak256(attestationStructure[i][0]); + if (keyHash == keccak256(bytes("module_id"))) { + moduleId = attestationStructure[i][1]; + continue; + } + if (keyHash == keccak256(bytes("timestamp"))) { + rawTimestamp = attestationStructure[i][1]; + continue; + } + if (keyHash == keccak256(bytes("digest"))) { + digest = attestationStructure[i][1]; + continue; + } + if (keyHash == keccak256(bytes("pcrs"))) { + rawPcrs = attestationStructure[i][1]; + continue; + } + if (keyHash == keccak256(bytes("certificate"))) { + certificate = attestationStructure[i][1]; + continue; + } + if (keyHash == keccak256(bytes("cabundle"))) { + cabundle = attestationStructure[i][1]; + continue; + } + if (keyHash == keccak256(bytes("public_key"))) { + enclavePubKey = attestationStructure[i][1]; + continue; + } + if (keyHash == keccak256(bytes("user_data"))) { + userData = attestationStructure[i][1]; + continue; + } + } + + require(moduleId.length != 0, "Invalid module id"); + + uint64 timestamp = ByteParser.bytesToUint64(rawTimestamp); + require(timestamp != 0, "invalid timestamp"); + require(timestamp + maxAge > block.timestamp, "attestation too old"); + + require(bytes32(digest) == bytes32("SHA384"), "invalid digest algo"); + + bytes[2][] memory pcrs = CBORDecoding.decodeMapping(rawPcrs); + pcr0 = _getPCR0(pcrs); + + certPubKey = _verifyCerts(certificate, cabundle); + + return (certPubKey, enclavePubKey, pcr0); + } + + /// @notice PCR0 should be first in the array but this ensures it will find it otherwise + function _getPCR0(bytes[2][] memory pcrs) internal pure returns (bytes memory) { + require(pcrs.length != 0, "no pcr specified"); + require(pcrs.length <= 32, "only 32 pcrs allowed"); + + for (uint256 i = 0; i < pcrs.length; i++) { + if (uint8(bytes1(pcrs[i][0])) == uint8(0)) { + return pcrs[i][1]; + } + } + + revert("failed to find pcr0"); + } +} diff --git a/nitro-validator/test/NitroValidator.t.sol b/nitro-validator/test/NitroValidator.t.sol new file mode 100644 index 0000000..d39709e --- /dev/null +++ b/nitro-validator/test/NitroValidator.t.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {Test, console} from "forge-std/Test.sol"; +import "../src/INitroValidator.sol"; +import "../src/NitroValidator.sol"; + +contract NitroValidatorTest is Test { + INitroValidator validator; + + function setUp() public { + vm.warp(1708930774); + validator = new NitroValidator(); + } + + function test_validateAttestation() public { + bytes memory attestation = vm.readFileBinary("./test/nitro-attestation/sample_attestation.bin"); + + (bytes memory enclavePubKey, bytes memory pcr0) = validator.validateAttestation(attestation, 365 days); + + assertEq(enclavePubKey, hex"d239fd059dd0e0a01e280bec44903bb8143bae7e578b9844c6df5fd6351eddc0"); + assertEq(pcr0, hex"17BF8F048519797BE90497001A7559A3D555395937117D76F8BAAEDF56CA6D97952DE79479BC0C76E5D176D20F663790"); + } + + function test_validateAttestation_RevertOnExpiredTime() public { + bytes memory attestation = vm.readFileBinary("./test/nitro-attestation/sample_attestation.bin"); + + // Warp time to 366 days in the future + vm.warp(block.timestamp + 366 days); + + vm.expectRevert("certificate not valid anymore"); + validator.validateAttestation(attestation, 365 days); + } +} diff --git a/nitro-validator/test/nitro-attestation/sample_attestation.bin b/nitro-validator/test/nitro-attestation/sample_attestation.bin new file mode 100644 index 0000000000000000000000000000000000000000..f7cb4867d980b01b3f636aa8548b4a031e0202d4 GIT binary patch literal 4482 zcmd6pc~lek7RR%)20~Ca5fH3`+Q4tJN2C=k8gMTkbs~yqOu_piI4X0VI6inUhYgv1meb#Dh}V`85BfYW z&A0siYtwZPJh+|f4Nq^n_j2(^yFzk5uuSG!Pp`at1J4H-JwF6KIM-6U!S!s9kJ-`h zs*6ibM|Flo?%&*>pYynq!y^T8&ey95`4pF(PP`!hc8}64Dx&^RG0;E6Q2!7k{X>lP z4>8d{#8m$flKvrP`iGe7A41kYBpMOLsU%_v0;@!!xHOQ4OGYoe29Lwya2D8Ylarq; zF0;^qD{5sm0}}(InVAc&coFb;keUsd0%HSw5W!#qE@(M40Z??ph-4B1E2AW`NR?a$ zSpak0p&`j|xd>LNVnh%bnCVVTNCrMqcpdr^5_(tkz?6jd30V%!1{|Hjlb~@LgFCFC zD&=Zb6cuuZ*DBp*5>=dB*DYe;c##q~+L+N8fJS2h7L!Y-hy5wJsAPqK)ZG6?#@@hd zBxBY%8IRY>-uM?F9#|L{4!>3$)_^!Gm4I=<3owLk<00R)-m7_+w0QyNA`MMTR&C6z zqHOLPY)m|CyfR};HY_&1SX6U-Z}8!X6Q|TaeEfqS?+43(TwZ)_OXI9L*QGu33B(x0 zAA9Y~7hu1O2ct+Z|4J#lD0*G%b+YGS(fmO zxi6y-SfG{(V?^npI4cxI3P{7|jo?VZJb~0v_Bkhha^N*o+|>>RkIfM*e7(5;g)@6# zH&Qjr@j-&P5TJ1&I*kLEC`gPE$rzS@=cC?H{NHhb-G_~8`=S&-@4c0Cj>n?f!YgR(2rkU_lANW{!RPql6TuczjxL@$4$?X~72T-AzIG?N0c)Pr{L6 z!B#Ks#+~yD9IL9`J%gG{9>!MG-8|lh5^}I_pEa>u;a1eSeZe1vkZF1D;7t62@ls zcNS~3!O#HOPawufgP}uo2%wT421I%x$Og<21l?J5I$+TNL<0;&DBv*P=n!xAxpy$b zr7TI4dCl<6Z7}y~TQ&4?*6j<=4@UQqEv-v?(|HY6{T<&1R!&}Hm)TfsBl>Oror=n; zrtCTWxrah(f4P;+a4*st3xDf~FHc+hqFlJt)$_;fkie=Pk+v3vix08R#Kab+-TylkRLP!4_C=93P#($f~<#*7npj@4CkB)SBJLDGf*VIm_N6D@J!e zCxQqd0MQL$LBrGq4>!l4R4E5oIgr&^^I;k+Z$&VruKl;u5o;Tea#2sg8r8e&OA*yq0QWMU`3E@q0>MP zuW4wj;4L4dri~b3JxBy=HENAAGxn7cP;RMzZ9~+zeKG8bG3(M>_J9NslgXh2Du>4A zQW-*yfC@8dTq>6%L?8$e3E2qk9rk!{UV3nJw${4g}!C&RTpnk<8e3rFL=kFZ;HK%y0QM7B)(UPPSh< z4(7^^oj0}hWop7}hEh5^dXhqMnW!~%`>5fFJ*+T=8t0iVMPIb8`(;JMsj@>Ft@pr_ z*DUgnVsq3;EC85nhHixzUQJO?4a zlKN<4i%&uamZ+5*oeDerU3c&OJi7Rd^yd((-t~NS)CMT`jgdAINx7++q;dJocI9|fM?gboSY^g`Ljjc9`o<_Y$O3~hC z619vsIscmv{Vwk4uwS?T9(4TJF70K%dxr||-JhmRpKcd|Y5y4AHhx|BNk$jxc}>-3 zO**5O7wN^n)-I+$H02xZd{jw}E&pE(~aE0(NLs0A?+BqCar$e+^0 zeNH@Z^+%1Pn`w_ z(CtO))%fUTigHY?`-Vl~c4hO9;_8XFFaD;g9hlnuh~8uKoL9O^2)KQ1T^@*Z&~e7U e4;PWmH+^e4U_bNzgFU!ONrA+ykT_qBwa*_1-wW~p literal 0 HcmV?d00001 diff --git a/nitro-validator/test/nitro-attestation/sample_attestation2.bin b/nitro-validator/test/nitro-attestation/sample_attestation2.bin new file mode 100644 index 0000000000000000000000000000000000000000..fc0e28d7768fd9c0ffcab39e2f1e38d7326e98b9 GIT binary patch literal 4480 zcmd6qc~lek7RQrG3;_a&SOh5`xS}Tfl1!G7hkz8^suqzNWOFi^kN{as!d3+df=Y2; zuxe!~Rist~1>0H>L~NfePmo$FD!8CfL8*#FL22J0Xi@OJ!#RELH0S*OxHofWZsvZz z_uizumqQjhX9rr2&6g`wlISQrOfHF=DrW+oP>fRa{ zf7E7~FR6`Aifntf^3cv(9Vf)i*(;Gh)_FZx72q8{GdQaUC5wR9q+o|mza{jE=Bd3- z4GA3%$IsT39{lvk#h{I%QlARjFTZ_wPrK&cT-RPxvn0>kryvnn|87pL>HV1rTeHho z)0!*GqblWxv#0h>i#F>Nj(sqtsZr(U)HwKu8v4g*NgKqmhTu zM;>B4@(`1ehnS8$gfa3Evyq3Gk37U;_}3D`44Y&>1GnQLRj_ z4A-fY?9sry&uPRk^2bq~E(&K`0JA=i34`XPKtCcbVGwsE9HtD4*K&XMEFkQ&C=B*6 zje&)#nOaq}PR3-j&{!=?Dc5OKecd7ojls3RVVoHU1~A~Vxj+cOidQC|Fu5y$e%JpZ zV-MiopD}-!jE8GwZ~O`n14h$~Uc6R_OdB;rPbE21#3btC_iya>mW645H`|8auao^$ zxT7+Co{MaawD<1Zp5MDz$Kwx1s(V-#(Rx_|I@fb|t@u%JOcP&68DATD|NG~c9n8N!B~f@vAoLD_q9sTsXY}J}N$O@t%j~utyJ_!RU3|Os>4$@J1oK}n z?tkIT4%qfrZOrf>u@N@l2-#eY5by|);C>T+Vg8kmdQ0)2L(AF^(a&_s)VKCs*>>7j z$l0M)rY-!#plL{ZZ{Y`ici_*93|E^gN=4&2Ulqj|nKzB8-{;ZhWG8K1T9a_G(Kt+H zrG7k)?%i;0G}-BP4Mv`3EPZdo`FL>(SaiyLI4gnZVy-+I(>^|XVH3$x`VWF=nw ztZC~Fntg=7?b=VTbTxRFqR(fvQ+`x+Y@P^pUN;0nl>`^3iQ_ZSru+}L7CfC|zFENi zW=e8xUd!Ob{>oat>@OU^0UgR*P^#bEQKs>4P0#u#%P;GVbK=l%&_~ zNigw_Dc$Zo+x<8H%-rPh8?uik-nEQ%T2$WD@a`uzc^T9bq))9ErJX!cRCa5B^{kWe z!TWy7)&C6ibzsaZXy|}Ous{q7;MnJzNVNghNyC%#Cs?YY&GV%fx)tGOwhXVbE6w4I zOR6DYMfKg!k%|KhpxI&BA?t%=I-Po!t1F>Z=Ic$LiD{x(S}qHHF--cjjHwi^gwquj z!w4T>7*inObD1zlEMX!zB4zRrE?0<&`2wjBW)T+-riBtt0ul%$Dy!g+KPqkaJD#^L zZLi|t!4J2Dq}{v{EG+oK?ZU?5s_yhE8~-PBG#l@ej1OfwWI#ZWHMS`s?ct7lOXF&8 z9=FpL9j)3nIq%12rlx#c!AXPTxPqtzdP(rn`UraUc9-#A22$)nGR2mFICB7Em-uB_ z-D^UYeeby`Yy;#rVQ^aiFS1?&PK-|&<_ca8Pz3M@lTQrLkJ;|Pt)E&LK~N!v2mp!; zQ3MmBEMl0WQU96UH-`O?9{t)vF<{Uc&~mS#i^SW;W__!9poQC}cVaQm57U)}b5e?$ zjmlOZ<5`)A_lda8u9<$xt)HBbUYUrX44cndewaU<{wNTH%|ooCw^v+T{C;XmNW7<) zNBQl^=?BQ#!){F};OkEL-pZ`4&z}5*JoCua-vj!)!^nXAnhb{J{7)GurUw;}sI;?Z zv0BI94Z)xzGbL|`4D`wUI#>ypSSM0DSZ2p+G2w&_)t*X#M&a`+=Y-I!1BmLLF1*dR=xc zVVcWfo_iBHe%`Iv>^HG8`b;*WeZh0o1Qsw}f6qyk?UsSd$|eA`Jg+NR9w;k2{`V1n)?uZsbq4lXrCMglkiRm(HRj4XGdvp*wwtC%Vu4AU9M2e&1jDViCqiZ!#0P)CcSs$3>hg-FAYu3@q@4nOMZvU^Z{!v?J z$X>fAE-bq`B`jiXQ>AUf_N$Dg-tF$Adrwa1oicu0C0%9Qu1@SoYYXx|e8i+W c6WX;oaPQa0q#g52R+mO>X}oc4rLhS5H#7MSlmGw# literal 0 HcmV?d00001 diff --git a/remappings.txt b/remappings.txt index ccc66b8..2e692ab 100644 --- a/remappings.txt +++ b/remappings.txt @@ -4,6 +4,7 @@ @lib-keccak/=lib/optimism/packages/contracts-bedrock/lib/lib-keccak/contracts/lib/ @openzeppelin/contracts-upgradeable/=lib/optimism/packages/contracts-bedrock/lib/openzeppelin-contracts-upgradeable/contracts/ @rari-capital/solmate/=lib/optimism/packages/contracts-bedrock/lib/solmate/ +@nitro-validator/=nitro-validator/ src/cannon/interfaces/=lib/optimism/packages/contracts-bedrock/src/cannon/interfaces/ src/L1/=lib/optimism/packages/contracts-bedrock/src/L1/ src/L2/=lib/optimism/packages/contracts-bedrock/src/L2/ diff --git a/script/DeploySystem.s.sol b/script/DeploySystem.s.sol index ea1a9e7..f38ac6d 100644 --- a/script/DeploySystem.s.sol +++ b/script/DeploySystem.s.sol @@ -21,6 +21,7 @@ import { DeployChain } from "src/DeployChain.sol"; import { Constants } from "@eth-optimism-bedrock/src/libraries/Constants.sol"; import { ResourceMetering } from "@eth-optimism-bedrock/src/L1/ResourceMetering.sol"; import { IResourceMetering } from "@eth-optimism-bedrock/src/L1/interfaces/IResourceMetering.sol"; +import "../nitro-validator/src/INitroValidator.sol"; import { console2 as console } from "forge-std/console2.sol"; @@ -134,8 +135,21 @@ contract DeploySystem is Deploy { } function deploySystemConfigGlobal() public broadcast returns (address addr_) { + string memory filePath = string(abi.encodePacked( + "nitro-validator/deployments/", + vm.toString(block.chainid), + "-nitro-validator-deploy.json" + )); + + if (!vm.exists(filePath)) { + revert("NitroValidator.json not found. Please deploy nitro-validator first."); + } + + address nitroValidatorAddress = vm.parseJsonAddress(filePath, ".address"); + INitroValidator nitroValidator = INitroValidator(nitroValidatorAddress); + console.log("Deploying SystemConfigGlobal implementation"); - addr_ = address(new SystemConfigGlobal{ salt: _implSalt() }()); + addr_ = address(new SystemConfigGlobal{ salt: _implSalt() }(nitroValidator)); save("SystemConfigGlobal", addr_); console.log("SystemConfigGlobal deployed at %s", addr_); } diff --git a/src/SystemConfigGlobal.sol b/src/SystemConfigGlobal.sol index 3a58568..f3e4975 100644 --- a/src/SystemConfigGlobal.sol +++ b/src/SystemConfigGlobal.sol @@ -4,8 +4,12 @@ pragma solidity ^0.8.0; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import { ISemver } from "@eth-optimism-bedrock/src/universal/interfaces/ISemver.sol"; +import "@nitro-validator/src/INitroValidator.sol"; contract SystemConfigGlobal is OwnableUpgradeable, ISemver { + /// @notice The AWS Nitro validator. + INitroValidator public immutable nitroValidator; + /// @notice The address of the proposer. address public proposer; @@ -21,7 +25,8 @@ contract SystemConfigGlobal is OwnableUpgradeable, ISemver { return "0.0.1"; } - constructor() { + constructor(INitroValidator _nitroValidator) { + nitroValidator = _nitroValidator; initialize({ _owner: address(0xdEaD) }); @@ -45,14 +50,11 @@ contract SystemConfigGlobal is OwnableUpgradeable, ISemver { } function registerSigner(bytes calldata attestation) external onlyOwner { - // TODO validate AWS attestation, check PCR0, then add public key to mapping of valid signers - // https://github.com/marlinprotocol/NitroProver - revert("Not implemented"); - } + (bytes memory enclavePublicKey, bytes memory pcr0) = nitroValidator.validateAttestation(attestation, 10 minutes); + require (validPCR0s[keccak256(pcr0)], "invalid pcr0 in attestation"); - // TODO remove this method once the above method is implemented - function registerSignerAddress(address signer) external onlyOwner { - validSigners[signer] = true; + address enclaveAddress = address(uint160(uint256(keccak256(enclavePublicKey)))); + validSigners[enclaveAddress] = true; } function deregisterSigner(address signer) external onlyOwner { diff --git a/test/SystemGlobalConfig.t.sol b/test/SystemGlobalConfig.t.sol new file mode 100644 index 0000000..d968e3d --- /dev/null +++ b/test/SystemGlobalConfig.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {Test, console} from "forge-std/Test.sol"; + +import "@nitro-validator/src/INitroValidator.sol"; +import "../src/SystemConfigGlobal.sol"; + +// Mock NitroValidator contract +contract MockNitroValidator is INitroValidator { + bytes public pcr0; + bytes public publicKey; + + function setValidationResult(bytes memory _publicKey, bytes memory _pcr0) external { + pcr0 = _pcr0; + publicKey = _publicKey; + } + function validateAttestation(bytes memory, uint256) external view returns (bytes memory, bytes memory) { + return (publicKey, pcr0); + } +} + +contract NitroValidatorTest is Test { + MockNitroValidator mockValidator; + SystemConfigGlobal systemConfigGlobal; + + function setUp() public { + // Create a mock contract + mockValidator = new MockNitroValidator(); + + // create system config global + systemConfigGlobal = new SystemConfigGlobal(mockValidator); + } + + function test_validateAttestation() public { + vm.startPrank(systemConfigGlobal.owner()); + + mockValidator.setValidationResult(hex"d239fd059dd0e0a01e280bec44903bb8143bae7e578b9844c6df5fd6351eddc0", hex"17BF8F048519797BE90497001A7559A3D555395937117D76F8BAAEDF56CA6D97952DE79479BC0C76E5D176D20F663790"); + + string memory attestationFile = vm.readFile("./test/nitro-attestation/sample_attestation.json"); + bytes memory attestation = abi.decode(vm.parseJson(attestationFile, ".attestation"), (bytes)); + + systemConfigGlobal.registerPCR0(hex"17BF8F048519797BE90497001A7559A3D555395937117D76F8BAAEDF56CA6D97952DE79479BC0C76E5D176D20F663790"); + + systemConfigGlobal.registerSigner(attestation); + + address expectedSigner = 0xe04d808785d2BBdE18E9D0C01c05FB8CE0711f2d; + assertTrue(systemConfigGlobal.validSigners(expectedSigner)); + } +}