77import static org .ethereum .beacon .discovery .schema .NodeRecordBuilder .addCustomField ;
88
99import com .google .common .base .Preconditions ;
10+ import com .google .common .cache .Cache ;
1011import com .google .common .cache .CacheBuilder ;
1112import com .google .common .cache .CacheLoader ;
1213import com .google .common .cache .LoadingCache ;
2728import org .apache .tuweni .bytes .Bytes ;
2829import org .bouncycastle .math .ec .ECPoint ;
2930import org .ethereum .beacon .discovery .crypto .Signer ;
31+ import org .ethereum .beacon .discovery .util .CryptoUtil ;
3032import org .ethereum .beacon .discovery .util .Functions ;
3133import org .ethereum .beacon .discovery .util .Utils ;
3234
3335public class IdentitySchemaV4Interpreter implements IdentitySchemaInterpreter {
3436
3537 private static final Logger LOG = LogManager .getLogger ();
3638
39+ // key: compressed pubKey (33 bytes), value: nodeId (32 bytes)
40+ // ~165 bytes/entry with overhead, 10000 entries ≈ 1.6 MB
3741 private final LoadingCache <Bytes , Bytes > nodeIdCache =
3842 CacheBuilder .newBuilder ()
39- .maximumSize (4000 )
43+ .maximumSize (10000 )
4044 .build (CacheLoader .from (IdentitySchemaV4Interpreter ::calculateNodeIdImpl ));
4145
46+ // key: sha256(serializedNoSig) (32 bytes), value: signature (64 bytes)
47+ // ~196 bytes/entry (32 + 64 + ~100 Guava overhead), 10000 entries ≈ 1.9 MB
48+ // Only caches valid (true) results to prevent cache pollution attacks.
49+ // On cache hit, the stored signature is compared to the current to prevent reuse attacks.
50+ private final Cache <Bytes , Bytes > enrSignatureCache =
51+ CacheBuilder .newBuilder ().maximumSize (10000 ).build ();
52+
4253 private static final ImmutableSet <String > ADDRESS_IP_V4_FIELD_NAMES =
4354 ImmutableSet .of (EnrField .IP_V4 , EnrField .UDP );
4455
@@ -57,9 +68,21 @@ public boolean isValid(final NodeRecord nodeRecord) {
5768 getScheme ());
5869 return false ;
5970 }
71+ Bytes signature = nodeRecord .getSignature ();
72+ Bytes serializedNoSig = nodeRecord .serializeNoSignature ();
73+ Bytes serializedNoSigHash = CryptoUtil .sha256 (serializedNoSig );
74+ Bytes cachedSignature = enrSignatureCache .getIfPresent (serializedNoSigHash );
75+ if (cachedSignature != null && cachedSignature .equals (signature )) {
76+ return true ;
77+ }
78+
6079 Bytes pubKey = (Bytes ) nodeRecord .get (EnrField .PKEY_SECP256K1 ); // compressed
61- return Functions .verifyECDSASignature (
62- nodeRecord .getSignature (), Functions .hashKeccak (nodeRecord .serializeNoSignature ()), pubKey );
80+ boolean result =
81+ Functions .verifyECDSASignature (signature , Functions .hashKeccak (serializedNoSig ), pubKey );
82+ if (result ) {
83+ enrSignatureCache .put (serializedNoSigHash , signature );
84+ }
85+ return result ;
6386 }
6487
6588 @ Override
0 commit comments