Skip to content

Commit f2064f0

Browse files
feat: introduce signer abstraction for secret key operations (#196)
* initial implementation * initial implementation * initial implementation * initial implementation * javadoc * renaming * renaming * renaming * renaming * renaming * fix tests * fix tests
1 parent 57a4ede commit f2064f0

36 files changed

Lines changed: 260 additions & 152 deletions

src/main/java/org/ethereum/beacon/discovery/DiscoveryManagerImpl.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import org.apache.logging.log4j.LogManager;
2020
import org.apache.logging.log4j.Logger;
2121
import org.apache.tuweni.bytes.Bytes;
22-
import org.apache.tuweni.crypto.SECP256K1.SecretKey;
22+
import org.ethereum.beacon.discovery.crypto.Signer;
2323
import org.ethereum.beacon.discovery.message.FindNodeMessage;
2424
import org.ethereum.beacon.discovery.message.PingMessage;
2525
import org.ethereum.beacon.discovery.message.TalkReqMessage;
@@ -79,7 +79,7 @@ public DiscoveryManagerImpl(
7979
final List<NettyDiscoveryServer> discoveryServers,
8080
final KBuckets nodeBucketStorage,
8181
final LocalNodeRecordStore localNodeRecordStore,
82-
final SecretKey homeNodeSecretKey,
82+
final Signer signer,
8383
final NodeRecordFactory nodeRecordFactory,
8484
final Scheduler taskScheduler,
8585
final ExpirationSchedulerFactory expirationSchedulerFactory,
@@ -94,7 +94,7 @@ public DiscoveryManagerImpl(
9494
nodeSessionManager =
9595
new NodeSessionManager(
9696
localNodeRecordStore,
97-
homeNodeSecretKey,
97+
signer,
9898
nodeBucketStorage,
9999
outgoingPipeline,
100100
expirationSchedulerFactory);

src/main/java/org/ethereum/beacon/discovery/DiscoverySystemBuilder.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import java.util.function.Function;
3030
import java.util.stream.Collectors;
3131
import java.util.stream.Stream;
32-
import org.apache.tuweni.crypto.SECP256K1.SecretKey;
32+
import org.ethereum.beacon.discovery.crypto.Signer;
3333
import org.ethereum.beacon.discovery.liveness.LivenessChecker;
3434
import org.ethereum.beacon.discovery.liveness.LivenessChecker.Pinger;
3535
import org.ethereum.beacon.discovery.message.handler.DefaultExternalAddressSelector;
@@ -52,7 +52,7 @@ public class DiscoverySystemBuilder {
5252
private List<NodeRecord> bootnodes = Collections.emptyList();
5353
private Optional<List<InetSocketAddress>> listenAddresses = Optional.empty();
5454
private NodeRecord localNodeRecord;
55-
private SecretKey secretKey;
55+
private Signer signer;
5656
private NodeRecordFactory nodeRecordFactory = NodeRecordFactory.DEFAULT;
5757
private Schedulers schedulers;
5858
private NodeRecordListener localNodeRecordListener = NodeRecordListener.NOOP;
@@ -89,8 +89,8 @@ public DiscoverySystemBuilder listen(final InetSocketAddress... listenAddresses)
8989
return this;
9090
}
9191

92-
public DiscoverySystemBuilder secretKey(final SecretKey secretKey) {
93-
this.secretKey = secretKey;
92+
public DiscoverySystemBuilder signer(final Signer signer) {
93+
this.signer = signer;
9494
return this;
9595
}
9696

@@ -215,7 +215,7 @@ private void createDefaults() {
215215
newAddress,
216216
oldTcpAddress.map(InetSocketAddress::getPort),
217217
oldQuicAddress.map(InetSocketAddress::getPort),
218-
secretKey));
218+
signer));
219219
});
220220
schedulers = requireNonNullElseGet(schedulers, Schedulers::createDefault);
221221
final List<InetSocketAddress> serverListenAddresses =
@@ -245,7 +245,7 @@ private void createDefaults() {
245245
localNodeRecordStore,
246246
() ->
247247
new LocalNodeRecordStore(
248-
localNodeRecord, secretKey, localNodeRecordListener, newAddressHandler));
248+
localNodeRecord, signer, localNodeRecordListener, newAddressHandler));
249249
nodeBucketStorage =
250250
requireNonNullElseGet(
251251
nodeBucketStorage, () -> new KBuckets(clock, localNodeRecordStore, livenessChecker));
@@ -280,7 +280,7 @@ public MutableDiscoverySystem buildMutable() {
280280

281281
private DiscoverySystemImpl buildImpl() {
282282
checkNotNull(localNodeRecord, "Missing local node record");
283-
checkNotNull(secretKey, "Missing secret key");
283+
checkNotNull(signer, "Missing signer");
284284
createDefaults();
285285

286286
// Check local node record is valid
@@ -315,7 +315,7 @@ DiscoveryManagerImpl buildDiscoveryManager() {
315315
discoveryServers,
316316
nodeBucketStorage,
317317
localNodeRecordStore,
318-
secretKey,
318+
signer,
319319
nodeRecordFactory,
320320
schedulers.newSingleThreadDaemon("discovery-client-" + clientNumber),
321321
expirationSchedulerFactory,
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*/
4+
5+
package org.ethereum.beacon.discovery.crypto;
6+
7+
import org.apache.tuweni.bytes.Bytes;
8+
import org.apache.tuweni.bytes.Bytes32;
9+
import org.apache.tuweni.crypto.SECP256K1.SecretKey;
10+
import org.ethereum.beacon.discovery.util.Functions;
11+
12+
/** In-memory {@link Signer} implementation backed by a SECP256K1 {@link SecretKey}. */
13+
public class DefaultSigner implements Signer {
14+
private final SecretKey secretKey;
15+
16+
public DefaultSigner(final SecretKey secretKey) {
17+
this.secretKey = secretKey;
18+
}
19+
20+
/** {@inheritDoc} */
21+
@Override
22+
public Bytes deriveECDHKeyAgreement(final Bytes peerPublicKey) {
23+
return Functions.deriveECDHKeyAgreement(secretKey, peerPublicKey);
24+
}
25+
26+
/** {@inheritDoc} */
27+
@Override
28+
public Bytes sign(final Bytes32 messageHash) {
29+
return Functions.sign(secretKey, messageHash);
30+
}
31+
32+
/** {@inheritDoc} */
33+
@Override
34+
public Bytes deriveCompressedPublicKeyFromPrivate() {
35+
return Functions.deriveCompressedPublicKeyFromPrivate(secretKey);
36+
}
37+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*/
4+
5+
package org.ethereum.beacon.discovery.crypto;
6+
7+
import org.apache.tuweni.bytes.Bytes;
8+
import org.apache.tuweni.bytes.Bytes32;
9+
10+
/**
11+
* Abstraction for performing private-key operations.
12+
*
13+
* <p>Implementations perform signing and ECDH key agreement without exposing private key material.
14+
*/
15+
public interface Signer {
16+
17+
/**
18+
* Creates a signature of message `x`.
19+
*
20+
* @param messageHash message, hashed
21+
* @return ECDSA signature with properties merged together: r || s
22+
*/
23+
Bytes sign(final Bytes32 messageHash);
24+
25+
/**
26+
* Derives a shared secret using ECDH with the given peer public key.
27+
*
28+
* @param destPubKey the destination peer's public key
29+
* @return the derived shared secret
30+
*/
31+
Bytes deriveECDHKeyAgreement(Bytes destPubKey);
32+
33+
/**
34+
* Derives the compressed public key corresponding to the private key held by this module.
35+
*
36+
* @return the compressed public key
37+
*/
38+
Bytes deriveCompressedPublicKeyFromPrivate();
39+
}

src/main/java/org/ethereum/beacon/discovery/packet/HandshakeMessagePacket.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import java.util.Optional;
88
import org.apache.tuweni.bytes.Bytes;
99
import org.apache.tuweni.bytes.Bytes32;
10-
import org.apache.tuweni.crypto.SECP256K1.SecretKey;
10+
import org.ethereum.beacon.discovery.crypto.Signer;
1111
import org.ethereum.beacon.discovery.message.V5Message;
1212
import org.ethereum.beacon.discovery.packet.HandshakeMessagePacket.HandshakeAuthData;
1313
import org.ethereum.beacon.discovery.packet.impl.HandshakeMessagePacketImpl;
@@ -65,12 +65,12 @@ static Bytes signId(
6565
final Bytes challengeData,
6666
final Bytes ephemeralPubKey,
6767
final Bytes32 destNodeId,
68-
final SecretKey homeNodeSecretKey) {
68+
final Signer signer) {
6969

7070
Bytes32 idSignatureInput =
7171
CryptoUtil.sha256(
7272
Bytes.wrap(ID_SIGNATURE_PREFIX, challengeData, ephemeralPubKey, destNodeId));
73-
return Functions.sign(homeNodeSecretKey, idSignatureInput);
73+
return signer.sign(idSignatureInput);
7474
}
7575

7676
Bytes32 getSourceNodeId();

src/main/java/org/ethereum/beacon/discovery/pipeline/handler/HandshakeMessagePacketHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public void handle(Envelope envelope) {
8181
Functions.hkdfExpand(
8282
session.getNodeId(),
8383
session.getHomeNodeId(),
84-
session.getStaticNodeKey(),
84+
session.getSigner(),
8585
ephemeralPubKeyCompressed,
8686
whoAreYouChallenge);
8787
// Swap keys because we are not initiator, other side is

src/main/java/org/ethereum/beacon/discovery/pipeline/handler/NodeSessionManager.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import org.apache.logging.log4j.LogManager;
1919
import org.apache.logging.log4j.Logger;
2020
import org.apache.tuweni.bytes.Bytes;
21-
import org.apache.tuweni.crypto.SECP256K1.SecretKey;
21+
import org.ethereum.beacon.discovery.crypto.Signer;
2222
import org.ethereum.beacon.discovery.pipeline.Envelope;
2323
import org.ethereum.beacon.discovery.pipeline.EnvelopeHandler;
2424
import org.ethereum.beacon.discovery.pipeline.Field;
@@ -42,7 +42,7 @@ public class NodeSessionManager implements EnvelopeHandler {
4242
private static final int REQUEST_CLEANUP_DELAY_SECONDS = 60;
4343
private static final Logger LOG = LogManager.getLogger(NodeSessionManager.class);
4444
private final LocalNodeRecordStore localNodeRecordStore;
45-
private final SecretKey staticNodeKey;
45+
private final Signer signer;
4646
private final KBuckets nodeBucketStorage;
4747
private final Map<SessionKey, NodeSession> recentSessions = new ConcurrentHashMap<>();
4848
private final Map<Bytes12, NodeSession> lastNonceToSession = new ConcurrentHashMap<>();
@@ -52,12 +52,12 @@ public class NodeSessionManager implements EnvelopeHandler {
5252

5353
public NodeSessionManager(
5454
final LocalNodeRecordStore localNodeRecordStore,
55-
final SecretKey staticNodeKey,
55+
final Signer signer,
5656
final KBuckets nodeBucketStorage,
5757
final Pipeline outgoingPipeline,
5858
final ExpirationSchedulerFactory expirationSchedulerFactory) {
5959
this.localNodeRecordStore = localNodeRecordStore;
60-
this.staticNodeKey = staticNodeKey;
60+
this.signer = signer;
6161
this.nodeBucketStorage = nodeBucketStorage;
6262
this.outgoingPipeline = outgoingPipeline;
6363
this.sessionExpirationScheduler =
@@ -159,7 +159,7 @@ private NodeSession createNodeSession(
159159
key.remoteSocketAddress,
160160
this,
161161
localNodeRecordStore,
162-
staticNodeKey,
162+
signer,
163163
nodeBucketStorage,
164164
outgoingPipeline::push,
165165
random,

src/main/java/org/ethereum/beacon/discovery/pipeline/handler/WhoAreYouPacketHandler.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,7 @@ public void handle(final Envelope envelope) {
120120
Functions.deriveCompressedPublicKeyFromPrivate(ephemeralKeyPair.secretKey());
121121

122122
Bytes idSignature =
123-
HandshakeAuthData.signId(
124-
challengeData, ephemeralPubKey, destNodeId, session.getStaticNodeKey());
123+
HandshakeAuthData.signId(challengeData, ephemeralPubKey, destNodeId, session.getSigner());
125124

126125
NodeRecord respRecord = null;
127126
UInt64 lastKnownOurEnrVer = whoAreYouPacket.getHeader().getAuthData().getEnrSeq();

src/main/java/org/ethereum/beacon/discovery/schema/IdentitySchemaInterpreter.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import java.net.InetSocketAddress;
88
import java.util.Optional;
99
import org.apache.tuweni.bytes.Bytes;
10-
import org.apache.tuweni.crypto.SECP256K1.SecretKey;
10+
import org.ethereum.beacon.discovery.crypto.Signer;
1111

1212
/**
1313
* Interprets identity schema of ethereum node record:
@@ -26,7 +26,7 @@ public interface IdentitySchemaInterpreter {
2626
IdentitySchema getScheme();
2727

2828
/* Signs nodeRecord, modifying it */
29-
void sign(NodeRecord nodeRecord, SecretKey secretKey);
29+
void sign(NodeRecord nodeRecord, Signer signer);
3030

3131
/** Verifies that `nodeRecord` is of scheme implementation */
3232
default boolean isValid(NodeRecord nodeRecord) {
@@ -53,10 +53,10 @@ NodeRecord createWithNewAddress(
5353
InetSocketAddress newAddress,
5454
Optional<Integer> newTcpPort,
5555
Optional<Integer> newQuicPort,
56-
SecretKey secretKey);
56+
Signer signer);
5757

5858
NodeRecord createWithUpdatedCustomField(
59-
NodeRecord nodeRecord, String newAddress, Bytes value, SecretKey secretKey);
59+
NodeRecord nodeRecord, String newAddress, Bytes value, Signer signer);
6060

6161
Bytes calculateNodeId(Bytes publicKey);
6262
}

src/main/java/org/ethereum/beacon/discovery/schema/IdentitySchemaV4Interpreter.java

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
import org.apache.logging.log4j.LogManager;
2626
import org.apache.logging.log4j.Logger;
2727
import org.apache.tuweni.bytes.Bytes;
28-
import org.apache.tuweni.crypto.SECP256K1.SecretKey;
2928
import org.bouncycastle.math.ec.ECPoint;
29+
import org.ethereum.beacon.discovery.crypto.Signer;
3030
import org.ethereum.beacon.discovery.util.Functions;
3131
import org.ethereum.beacon.discovery.util.Utils;
3232

@@ -86,9 +86,8 @@ private static Bytes calculateNodeIdImpl(final Bytes publicKey) {
8686
}
8787

8888
@Override
89-
public void sign(final NodeRecord nodeRecord, final SecretKey secretKey) {
90-
Bytes signature =
91-
Functions.sign(secretKey, Functions.hashKeccak(nodeRecord.serializeNoSignature()));
89+
public void sign(final NodeRecord nodeRecord, final Signer signer) {
90+
Bytes signature = signer.sign(Functions.hashKeccak(nodeRecord.serializeNoSignature()));
9291
nodeRecord.setSignature(signature);
9392
}
9493

@@ -131,7 +130,7 @@ public NodeRecord createWithNewAddress(
131130
final InetSocketAddress newAddress,
132131
final Optional<Integer> newTcpPort,
133132
final Optional<Integer> newQuicPort,
134-
final SecretKey secretKey) {
133+
final Signer signer) {
135134
final List<EnrField> fields =
136135
getAllFieldsThatMatch(
137136
nodeRecord,
@@ -146,21 +145,18 @@ public NodeRecord createWithNewAddress(
146145
NodeRecordBuilder.addFieldsForAddress(
147146
fields, newAddress.getAddress(), newAddress.getPort(), newTcpPort, newQuicPort);
148147
final NodeRecord newRecord = NodeRecord.fromValues(this, nodeRecord.getSeq().add(1), fields);
149-
sign(newRecord, secretKey);
148+
sign(newRecord, signer);
150149
return newRecord;
151150
}
152151

153152
@Override
154153
public NodeRecord createWithUpdatedCustomField(
155-
final NodeRecord nodeRecord,
156-
final String fieldName,
157-
final Bytes value,
158-
final SecretKey secretKey) {
154+
final NodeRecord nodeRecord, final String fieldName, final Bytes value, final Signer signer) {
159155
final List<EnrField> fields =
160156
getAllFieldsThatMatch(nodeRecord, field -> !field.getName().equals(fieldName));
161157
addCustomField(fields, fieldName, value);
162158
final NodeRecord newRecord = NodeRecord.fromValues(this, nodeRecord.getSeq().add(1), fields);
163-
sign(newRecord, secretKey);
159+
sign(newRecord, signer);
164160
return newRecord;
165161
}
166162

0 commit comments

Comments
 (0)