Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/364 integretion with hsm #1602

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 8 additions & 14 deletions besu/src/main/java/org/web3j/tx/PrivateTransactionManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@
import org.web3j.protocol.core.methods.response.EthCall;
import org.web3j.protocol.core.methods.response.EthGetCode;
import org.web3j.protocol.core.methods.response.EthSendTransaction;
import org.web3j.protocol.eea.crypto.PrivateTransactionEncoder;
import org.web3j.protocol.eea.crypto.RawPrivateTransaction;
import org.web3j.service.TxSignService;
import org.web3j.service.TxSignServiceImpl;
import org.web3j.tx.response.TransactionReceiptProcessor;
import org.web3j.utils.Base64String;
import org.web3j.utils.Numeric;
Expand All @@ -36,7 +37,7 @@ public class PrivateTransactionManager extends TransactionManager {

private final Besu besu;

private final Credentials credentials;
private final TxSignService txSignService;
private final long chainId;

private final Base64String privateFrom;
Expand All @@ -56,12 +57,12 @@ public PrivateTransactionManager(
final Restriction restriction) {
super(transactionReceiptProcessor, credentials.getAddress());
this.besu = besu;
this.credentials = credentials;
this.chainId = chainId;
this.privateFrom = privateFrom;
this.privateFor = null;
this.privacyGroupId = privacyGroupId;
this.restriction = restriction;
this.txSignService = new TxSignServiceImpl(credentials);
}

public PrivateTransactionManager(
Expand All @@ -74,12 +75,12 @@ public PrivateTransactionManager(
final Restriction restriction) {
super(transactionReceiptProcessor, credentials.getAddress());
this.besu = besu;
this.credentials = credentials;
this.chainId = chainId;
this.privateFrom = privateFrom;
this.privateFor = privateFor;
this.privacyGroupId = PrivacyGroupUtils.generateLegacyGroup(privateFrom, privateFor);
this.restriction = restriction;
this.txSignService = new TxSignServiceImpl(credentials);
}

@Override
Expand All @@ -93,7 +94,7 @@ public EthSendTransaction sendTransaction(
throws IOException {

final BigInteger nonce =
besu.privGetTransactionCount(credentials.getAddress(), privacyGroupId)
besu.privGetTransactionCount(txSignService.getAddress(), privacyGroupId)
.send()
.getTransactionCount();

Expand Down Expand Up @@ -138,7 +139,7 @@ public EthSendTransaction sendEIP1559Transaction(
boolean constructor)
throws IOException {
final BigInteger nonce =
besu.privGetTransactionCount(credentials.getAddress(), privacyGroupId)
besu.privGetTransactionCount(txSignService.getAddress(), privacyGroupId)
.send()
.getTransactionCount();

Expand Down Expand Up @@ -200,14 +201,7 @@ public EthGetCode getCode(

public String sign(final RawPrivateTransaction rawTransaction) {

final byte[] signedMessage;

if (chainId > ChainIdLong.NONE) {
signedMessage =
PrivateTransactionEncoder.signMessage(rawTransaction, chainId, credentials);
} else {
signedMessage = PrivateTransactionEncoder.signMessage(rawTransaction, credentials);
}
final byte[] signedMessage = txSignService.sign(rawTransaction, chainId);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @andrii-kl , I think this change is not ok since it gets rid of the PrivateTransactionEncoder to replace it with the regular TransactionEncoder within TxSignServiceImpl .

I have run some tests in Besu using 4.9.3 and indeed the signature is different.

Let me know if I am missing something, otherwise probably it would be good to open an issue for the fix.

Thanks in advance, Miguel.

Copy link
Contributor

@antonydenyer antonydenyer Jul 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just spotted this also! #1747

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for creating the issue @antonydenyer ! If @andrii-kl is not available I could take the implementation of the fix.


return Numeric.toHexString(signedMessage);
}
Expand Down
29 changes: 29 additions & 0 deletions core/src/main/java/org/web3j/dto/HSMHTTPRequestDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2022 Web3 Labs Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.web3j.dto;

public class HSMHTTPRequestDTO {
private String message;

public HSMHTTPRequestDTO(String message) {
this.message = message;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}
}
82 changes: 82 additions & 0 deletions core/src/main/java/org/web3j/service/HSMHTTPRequestProcessor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright 2022 Web3 Labs Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.web3j.service;

import java.io.IOException;
import java.io.InputStream;

import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.ResponseBody;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.web3j.crypto.CryptoUtils;
import org.web3j.crypto.ECDSASignature;
import org.web3j.crypto.HSMHTTPPass;
import org.web3j.crypto.Sign;
import org.web3j.protocol.exceptions.ClientConnectionException;
import org.web3j.utils.Numeric;

/**
* Request processor to a HSM through the HTTP
*
* @param <T> Object with required parameters to perform request to a HSM
*/
public abstract class HSMHTTPRequestProcessor<T extends HSMHTTPPass>
implements HSMRequestProcessor<HSMHTTPPass> {

private static final Logger log = LoggerFactory.getLogger(HSMHTTPRequestProcessor.class);

public static final MediaType JSON = MediaType.parse("application/json");

private final OkHttpClient client;

public HSMHTTPRequestProcessor(OkHttpClient okHttpClient) {
this.client = okHttpClient;
}

@Override
public Sign.SignatureData callHSM(byte[] dataToSign, HSMHTTPPass pass) {
Request request = createRequest(dataToSign, pass);

try (okhttp3.Response response = client.newCall(request).execute()) {
ResponseBody responseBody = response.body();
if (response.isSuccessful()) {
if (responseBody != null) {
String signHex = readResponse(responseBody.byteStream());
byte[] signBytes = Numeric.hexStringToByteArray(signHex);
ECDSASignature signature = CryptoUtils.fromDerFormat(signBytes);

return Sign.createSignatureData(signature, pass.getPublicKey(), dataToSign);
} else {
return null;
}
} else {
int code = response.code();
String text = responseBody == null ? "N/A" : responseBody.string();
throw new ClientConnectionException(
"Invalid response received: " + code + "; " + text);
}
} catch (IOException e) {
log.error(e.getMessage(), e);
}

return null;
}

protected abstract Request createRequest(byte[] dataToSign, HSMHTTPPass pass);

protected abstract String readResponse(InputStream responseData);
}
33 changes: 33 additions & 0 deletions core/src/main/java/org/web3j/service/HSMRequestProcessor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2022 Web3 Labs Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.web3j.service;

import org.web3j.crypto.HSMPass;
import org.web3j.crypto.Sign;

/**
* Request processor to a HSM (hardware security module).
*
* @param <T> Object with required parameters to perform request to a HSM.
*/
public interface HSMRequestProcessor<T extends HSMPass> {

/**
* Call a HSM (hardware security module)
*
* @param dataToSign message hash to sign.
* @param pass Object with required parameters to perform request to a HSM.
* @return SignatureData v | r | s
*/
Sign.SignatureData callHSM(byte[] dataToSign, T pass);
}
67 changes: 67 additions & 0 deletions core/src/main/java/org/web3j/service/TxHSMSignService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2022 Web3 Labs Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.web3j.service;

import org.web3j.crypto.HSMPass;
import org.web3j.crypto.Hash;
import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.Sign;
import org.web3j.crypto.transaction.type.TransactionType;
import org.web3j.tx.ChainId;

import static org.web3j.crypto.TransactionEncoder.createEip155SignatureData;
import static org.web3j.crypto.TransactionEncoder.encode;

/** Service to sign transaction with HSM (hardware security module). */
public class TxHSMSignService<T extends HSMPass> implements TxSignService {

private final T hsmPass;
private final HSMRequestProcessor<T> hsmRequestProcessor;

public TxHSMSignService(HSMRequestProcessor<T> hsmRequestProcessor, T hsmPass) {
this.hsmPass = hsmPass;
this.hsmRequestProcessor = hsmRequestProcessor;
}

@Override
public byte[] sign(RawTransaction rawTransaction, long chainId) {
byte[] finalBytes;
byte[] encodedTransaction;
Sign.SignatureData signatureData;
boolean isNewTx =
chainId > ChainId.NONE && rawTransaction.getType().equals(TransactionType.LEGACY);

if (isNewTx) {
encodedTransaction = encode(rawTransaction, chainId);
} else {
encodedTransaction = encode(rawTransaction);
}

byte[] messageHash = Hash.sha3(encodedTransaction);

signatureData = hsmRequestProcessor.callHSM(messageHash, hsmPass);

if (isNewTx) {
signatureData = createEip155SignatureData(signatureData, chainId);
}

finalBytes = encode(rawTransaction, signatureData);

return finalBytes;
}

@Override
public String getAddress() {
return hsmPass.getAddress();
}
}
35 changes: 35 additions & 0 deletions core/src/main/java/org/web3j/service/TxSignService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2022 Web3 Labs Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.web3j.service;

import org.web3j.crypto.RawTransaction;

/** Service to sign transaction. */
public interface TxSignService {

/**
* Sign raw transaction.
*
* @param rawTransaction Raw transaction
* @param chainId Ethereum chain id, -1 is NONE
* @return Transaction signature
*/
byte[] sign(RawTransaction rawTransaction, long chainId);

/**
* Get key address of the current wallet
*
* @return Wallet address
*/
String getAddress();
}
45 changes: 45 additions & 0 deletions core/src/main/java/org/web3j/service/TxSignServiceImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2022 Web3 Labs Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.web3j.service;

import org.web3j.crypto.Credentials;
import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.TransactionEncoder;
import org.web3j.tx.ChainId;

/** Service to base sign transaction. */
public class TxSignServiceImpl implements TxSignService {

private final Credentials credentials;

public TxSignServiceImpl(Credentials credentials) {
this.credentials = credentials;
}

@Override
public byte[] sign(RawTransaction rawTransaction, long chainId) {
final byte[] signedMessage;

if (chainId > ChainId.NONE) {
signedMessage = TransactionEncoder.signMessage(rawTransaction, chainId, credentials);
} else {
signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
}
return signedMessage;
}

@Override
public String getAddress() {
return credentials.getAddress();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import org.web3j.crypto.Credentials;
import org.web3j.protocol.Web3j;
import org.web3j.service.TxSignService;
import org.web3j.tx.response.TransactionReceiptProcessor;

/**
Expand All @@ -31,6 +32,12 @@ public FastRawTransactionManager(Web3j web3j, Credentials credentials, long chai
super(web3j, credentials, chainId);
}

public FastRawTransactionManager(
Web3j web3j, TxSignService txSignService, long chainId, BigInteger nonce) {
super(web3j, txSignService, chainId);
this.nonce = nonce;
}

public FastRawTransactionManager(Web3j web3j, Credentials credentials) {
super(web3j, credentials);
}
Expand Down
Loading