Skip to content

base/nitro-validator

Repository files navigation

Solidity AWS Nitro Attestation validator

This repo provides solidity contracts for the verification of attestations generated by AWS Nitro Enclaves, as outlined in this doc.

AWS's attestation verification documentation disables CRL checks in its sample flow here. This library supports operational revocation with an authorized revoker: operators monitor AWS CRLs off-chain and call CertManager.revokeCert / revokeCerts for affected certificate identity keys.

Hinted P-384 verification

Nitro attestations are signed with ECDSA over the NIST P-384 curve. Verifying a P-384 signature on-chain requires modular inversion, which is computed via the MODEXP precompile — and the Fusaka upgrade (EIP-7883, live on Base) raises MODEXP pricing enough that the old fully on-chain flow no longer fits in a block.

This library therefore uses hinted verification: the modular inverses are computed off-chain and supplied in calldata as "hints". The contract does not trust them — before using each hint inv for a value b modulo m, it checks b · inv ≡ 1 (mod m) and reverts ("bad inverse hint") otherwise. Because the moduli are prime the inverse is unique, so a wrong hint can only cause a revert, never a forged signature. The acceptance rule is identical to a standard on-chain ECDSA-384 verification — hints are purely a gas optimization.

The legacy non-hinted entrypoints (verifyCACert, verifyClientCert, validateAttestation) are retained only as reverting stubs; use the *WithHints functions below.

For the full design, security argument, and measured gas, see docs/hinted-p384-nitro-attestation.md.

Usage

Deployment

Deploy in this order (the verifier references are immutable):

  1. P384Verifier
  2. CertManager(p384Verifier) — pins the AWS Nitro root CA and sets the deployer as owner/revoker.
  3. NitroValidator(certManager, p384Verifier)

After deployment, move ownership to the production admin and set the operational revoker with transferOwnership / setRevoker.

Verification flow

Verification has two phases. Certificate chains are reused across many attestations from the same enclave, so the chain is verified and cached once, after which each attestation only pays for its own document signature.

  1. Cold phase — verify & cache the certificate chain (once per chain). For each CA cert in the cabundle, then the leaf cert, compute the inverse hints off-chain and submit them:
    • certManager.verifyCACertWithHints(caCert, parentCertHash, hints) (use ROOT_CA_CERT_HASH / keccak256(rootCert) for the first non-root CA; 0 is only for the pinned root itself)
    • certManager.verifyClientCertWithHints(leafCert, parentCertHash, hints)
  2. Validation — verify the document signature. Once the chain is cached:
    • validator.validateAttestationWithHints(attestationTbs, signature, attestationHints)
    • Precondition: the whole cabundle + leaf must already be cached from phase 1. This call re-walks the chain with empty hints (relying on the cache), so an uncached chain reverts with "inverse hint underflow".

Splitting attestation into attestationTbs (to-be-signed bytes) and signature is cheapest off-chain, but validator.decodeAttestationTbs(attestation) is available on-chain too.

Computing hints off-chain

tools/p384_hints.js is a reference generator (Node.js, no dependencies):

node tools/p384_hints.js cert        --cert <0x DER cert>   --pubkey <0x parent pubkey>
node tools/p384_hints.js attestation --attestation <0x COSE> --pubkey <0x leaf pubkey>

Production callers should reimplement this in their backend language; the contract re-verifies every hint, so the generator is trusted only for liveness, never for correctness.

Revocation operations

CertManager does not fetch or parse AWS CRLs on-chain. Instead, an authorized revoker address marks certificates revoked after checking AWS CRLs off-chain. Revocation is keyed by the certificate's (issuer, serial) identitykeccak256(issuerHash, serialHash), the same identity AWS CRLs use to list revoked certs — not by keccak256(certBytes). Raw cert bytes are not a stable identity: ECDSA signatures are malleable (the (r, n-s) twin also verifies) and DER is re-encodable, so a byte-keyed revocation could be bypassed by a re-encoded twin of the revoked cert. Keying on the signature-protected (issuer, serial) pair closes that gap and lets operators revoke straight from CRL data. Compute the key with CertManager.computeCertId(certDER) (or replicate it off-chain). Revoked certificates are rejected on both cold verification and cached reuse, independently of notAfter. Cached descendants are also rejected when their cached parent chain contains a revoked certificate.

  • The deployer starts as both owner and revoker.
  • The owner can call transferOwnership, setRevoker, unrevokeCert, and revoke ROOT_CA_CERT_HASH as an emergency global halt (the root is identified by its pinned hash, since it is never parsed on-chain).
  • The revoker can call revokeCert or revokeCerts for non-root certificate identity keys.
  • loadVerified is a raw cache read; returned metadata does not imply the certificate is currently trusted.

Example consumer

pragma solidity ^0.8.0;

import {NitroValidator} from "@nitro-validator/NitroValidator.sol";
import {CertManager} from "@nitro-validator/CertManager.sol";
import {CborDecode} from "@nitro-validator/CborDecode.sol";

contract Validator {
    using CborDecode for bytes;

    uint256 public constant MAX_AGE = 60 minutes;
    bytes32 public constant PCR0 = keccak256("some PCR0 value");

    NitroValidator public immutable validator;

    constructor(NitroValidator validator_) {
        validator = validator_;
    }

    // Assumes the attestation's certificate chain has already been cached on the CertManager via
    // verifyCACertWithHints / verifyClientCertWithHints (see the cold phase above).
    function registerSigner(
        bytes calldata attestationTbs,
        bytes calldata signature,
        bytes calldata attestationHints // computed off-chain
    ) external {
        NitroValidator.Ptrs memory ptrs =
            validator.validateAttestationWithHints(attestationTbs, signature, attestationHints);

        bytes32 pcr0 = attestationTbs.keccak(ptrs.pcrs[0]);
        require(pcr0 == PCR0, "invalid pcr0 in attestation");
        require(ptrs.timestamp / 1000 + MAX_AGE > block.timestamp, "attestation too old");

        bytes memory publicKey = attestationTbs.slice(ptrs.publicKey);
        // do something with the public key, user data, etc
    }
}

Installation

As a Foundry dependency:

forge install base/nitro-validator

Then map the @nitro-validator/ prefix used in the examples above to the package's src/ in your remappings.txt:

@nitro-validator/=lib/nitro-validator/src/

The library vendors its only third-party dependency (the P-384 verifier) under src/vendor/, so no additional submodules are required beyond forge-std.

Security considerations

Verification proves an attestation is genuine; some properties are intentionally left to the integrator (see docs):

  • Freshness / replay — the contract does not compare the attestation timestamp (milliseconds) to block.timestamp (seconds) or match the nonce to a challenge. Enforce freshness yourself if you need it.
  • Signature malleability — low-S is not enforced (AWS does not emit low-S), so the malleable twin (r, n-s) also verifies. Never use the signature as a uniqueness key; dedupe on canonical attestation fields instead.
  • Enclave policy — checking pcrs / moduleID against the enclave image(s) you trust is your responsibility.
  • Revocation operations — the contract enforces the on-chain revoked set, but an off-chain operator must monitor AWS CRLs and submit the affected certificate identity keys (keccak256(issuerHash, serialHash), computed via computeCertId or directly from the CRL's issuer/serial entries).

Build

forge build

Test

forge test

The off-chain witness generator is cross-checked for byte-identical parity against the on-chain Solidity reference under FFI (requires Node.js):

NITRO_RUN_FFI=true forge test --ffi --match-test test_OffchainWitness

About

AWS Nitro enclave attestation verifier in Solidity, used by:

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors