11//! `NitroEnclaveVerifier` contract bindings.
22//!
33//! Used by the registrar to revoke intermediate certificates that appear on
4- //! AWS Nitro CRL distribution lists. Only the `revokeCert` function is
5- //! needed — all other `NitroEnclaveVerifier` interactions happen through the
6- //! `TEEProverRegistry` during signer registration .
4+ //! AWS Nitro CRL distribution lists, and to consult the on-chain durable
5+ //! revocation sentinel before submitting registrations. All other
6+ //! `NitroEnclaveVerifier` interactions happen through the `TEEProverRegistry` .
77
8+ use alloy_primitives:: { Address , FixedBytes } ;
9+ use alloy_provider:: RootProvider ;
810use alloy_sol_types:: sol;
11+ use async_trait:: async_trait;
12+
13+ use crate :: ContractError ;
914
1015// Interface mirrored from the canonical contract source:
11- // https://github.com/base/contracts/blob/leopoldjoy/chain-3890-add-revoker-role-to-nitroenclaveverifier-for-automated-cert/ src/multiproof /tee/NitroEnclaveVerifier.sol
16+ // https://github.com/base/contracts/blob/main/ src/L1/proofs /tee/NitroEnclaveVerifier.sol
1217sol ! {
1318 /// `NitroEnclaveVerifier` contract interface (revocation subset).
1419 #[ sol( rpc) ]
1520 interface INitroEnclaveVerifier {
1621 /// Revokes a cached intermediate certificate by its accumulated path digest.
1722 function revokeCert( bytes32 certHash) external;
23+
24+ /// Returns whether the given accumulated-path-digest hash has been
25+ /// revoked. Persistent across cache overwrites.
26+ function revokedCerts( bytes32 certHash) external view returns ( bool ) ;
27+ }
28+ }
29+
30+ /// Reads the durable revocation sentinel from the on-chain
31+ /// `NitroEnclaveVerifier`.
32+ ///
33+ /// The on-chain `revokedCerts` mapping is set by `revokeCert` and persists
34+ /// across `_cacheNewCert` overwrites. Consulting it before submitting a
35+ /// registration prevents racing the cache rewrite that would otherwise
36+ /// re-trust a previously revoked intermediate (CHAIN-4194 / Immunefi #75608).
37+ #[ async_trait]
38+ pub trait NitroEnclaveVerifierClient : Send + Sync {
39+ /// Returns the on-chain address of the verifier contract this client
40+ /// is bound to. Used by callers that need to send transactions to the
41+ /// same contract (e.g. `revokeCert` calls go through a tx manager,
42+ /// not this client, so the destination address must be exposed
43+ /// rather than carried through a separate config field).
44+ fn address ( & self ) -> Address ;
45+
46+ /// Returns `true` if the given accumulated-path-digest hash is currently
47+ /// marked as revoked on-chain.
48+ async fn is_revoked ( & self , cert_hash : FixedBytes < 32 > ) -> Result < bool , ContractError > ;
49+ }
50+
51+ /// Concrete implementation backed by Alloy's sol-generated contract bindings.
52+ #[ derive( Debug ) ]
53+ pub struct NitroEnclaveVerifierContractClient {
54+ contract : INitroEnclaveVerifier :: INitroEnclaveVerifierInstance < RootProvider > ,
55+ }
56+
57+ impl NitroEnclaveVerifierContractClient {
58+ /// Creates a new client for the given verifier address and L1 RPC URL.
59+ pub fn new ( address : Address , l1_rpc_url : url:: Url ) -> Self {
60+ let provider = RootProvider :: new_http ( l1_rpc_url) ;
61+ let contract = INitroEnclaveVerifier :: INitroEnclaveVerifierInstance :: new ( address, provider) ;
62+ Self { contract }
63+ }
64+ }
65+
66+ #[ async_trait]
67+ impl NitroEnclaveVerifierClient for NitroEnclaveVerifierContractClient {
68+ fn address ( & self ) -> Address {
69+ * self . contract . address ( )
70+ }
71+
72+ async fn is_revoked ( & self , cert_hash : FixedBytes < 32 > ) -> Result < bool , ContractError > {
73+ self . contract . revokedCerts ( cert_hash) . call ( ) . await . map_err ( |e| ContractError :: Call {
74+ context : format ! ( "revokedCerts({cert_hash})" ) ,
75+ source : e,
76+ } )
1877 }
1978}
2079
@@ -30,20 +89,49 @@ mod tests {
3089 const TEST_CERT_HASH : FixedBytes < 32 > =
3190 b256 ! ( "0000000000000000000000000000000000000000000000000000000000000001" ) ;
3291
33- /// Expected ABI-encoded length for `revokeCert( bytes32)` :
92+ /// Expected ABI-encoded length for a ` bytes32`-only call :
3493 /// 4 bytes (selector) + 32 bytes (bytes32 argument).
35- const REVOKE_CERT_ENCODED_LEN : usize = 4 + 32 ;
94+ const BYTES32_CALL_ENCODED_LEN : usize = 4 + 32 ;
3695
96+ /// Parameterised over each `bytes32`-only call in the interface so any
97+ /// future additions only need a new `#[case]` line.
98+ ///
99+ /// `selector` is computed at case-evaluation time (not const), which is
100+ /// fine because rstest case arguments are runtime expressions.
37101 #[ rstest]
38- fn revoke_cert_abi_encodes_correctly ( ) {
39- let call = INitroEnclaveVerifier :: revokeCertCall { certHash : TEST_CERT_HASH } ;
40- let encoded = call. abi_encode ( ) ;
41- assert_eq ! ( encoded. len( ) , REVOKE_CERT_ENCODED_LEN ) ;
42- assert_eq ! ( & encoded[ ..4 ] , & INitroEnclaveVerifier :: revokeCertCall:: SELECTOR ) ;
102+ #[ case:: revoke_cert(
103+ INitroEnclaveVerifier :: revokeCertCall { certHash: TEST_CERT_HASH } . abi_encode( ) ,
104+ INitroEnclaveVerifier :: revokeCertCall:: SELECTOR ,
105+ ) ]
106+ #[ case:: revoked_certs(
107+ INitroEnclaveVerifier :: revokedCertsCall { certHash: TEST_CERT_HASH } . abi_encode( ) ,
108+ INitroEnclaveVerifier :: revokedCertsCall:: SELECTOR ,
109+ ) ]
110+ fn bytes32_call_abi_encodes_correctly (
111+ #[ case] encoded : Vec < u8 > ,
112+ #[ case] selector : [ u8 ; 4 ] ,
113+ ) {
114+ assert_eq ! ( encoded. len( ) , BYTES32_CALL_ENCODED_LEN ) ;
115+ assert_eq ! ( & encoded[ ..4 ] , & selector) ;
43116 }
44117
118+ /// Selectors for every revocation-related entry point must be non-zero
119+ /// (catches a degenerate sol! macro expansion) and distinct from each
120+ /// other (catches a copy-paste collision in the interface block).
45121 #[ rstest]
46- fn selector_is_nonzero ( ) {
47- assert_ne ! ( INitroEnclaveVerifier :: revokeCertCall:: SELECTOR , [ 0u8 ; 4 ] ) ;
122+ fn revocation_selectors_are_nonzero_and_distinct ( ) {
123+ let selectors = [
124+ INitroEnclaveVerifier :: revokeCertCall:: SELECTOR ,
125+ INitroEnclaveVerifier :: revokedCertsCall:: SELECTOR ,
126+ ] ;
127+
128+ for selector in & selectors {
129+ assert_ne ! ( selector, & [ 0u8 ; 4 ] , "selector must be non-zero: {selector:?}" ) ;
130+ }
131+ for ( i, a) in selectors. iter ( ) . enumerate ( ) {
132+ for b in & selectors[ i + 1 ..] {
133+ assert_ne ! ( a, b, "selectors must be distinct: {a:?} vs {b:?}" ) ;
134+ }
135+ }
48136 }
49137}
0 commit comments