-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
76642b3
commit cfd4951
Showing
20 changed files
with
400 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/.idea/ | ||
.DS_Store | ||
/out/ | ||
/cache/ | ||
/testnet/data/ | ||
/testnet/.env | ||
/deployments/*-*-*.json | ||
/bin/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <your_rpc_url> | ||
``` | ||
|
||
|
||
### Cast | ||
|
||
```shell | ||
$ cast <subcommand> | ||
``` | ||
|
||
### Help | ||
|
||
```shell | ||
$ forge --help | ||
$ anvil --help | ||
$ cast --help | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"address": "0x20823F43A70eEE2F92A6f44cC378566C6c7d4b0E"} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Submodule NitroProver
added at
8923d3
Submodule solidity-cbor
added at
6b8683
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
@marlinprotocol/=lib/NitroProver/src/ | ||
@solidity-cbor/=lib/solidity-cbor/packages/solidity-cbor/contracts/ | ||
forge-std/=lib/forge-std/src/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.