diff --git a/build.gradle b/build.gradle index cf03a44..3a362af 100644 --- a/build.gradle +++ b/build.gradle @@ -90,7 +90,7 @@ dependencies { exclude group: "io.netty" exclude group: 'org.fisco-bcos', module: 'tcnative' } - + implementation('org.fisco-bcos.java-sdk:fisco-bcos-sdk-abi:2.10.0-SNAPSHOT') implementation 'org.web3j:core:4.9.8' // implementation('org.fisco-bcos.java-sdk:fisco-bcos-sdk-abi:2.10.0-SNAPSHOT') diff --git a/src/main/java/com/webank/wecross/stub/web3/Web3BaseStubFactory.java b/src/main/java/com/webank/wecross/stub/web3/Web3BaseStubFactory.java index 945be0b..bf312af 100644 --- a/src/main/java/com/webank/wecross/stub/web3/Web3BaseStubFactory.java +++ b/src/main/java/com/webank/wecross/stub/web3/Web3BaseStubFactory.java @@ -19,7 +19,7 @@ public void init(WeCrossContext weCrossContext) {} @Override public Driver newDriver() { - return null; + return new Web3Driver(); } @Override diff --git a/src/main/java/com/webank/wecross/stub/web3/Web3Connection.java b/src/main/java/com/webank/wecross/stub/web3/Web3Connection.java index f61145c..6f617ff 100644 --- a/src/main/java/com/webank/wecross/stub/web3/Web3Connection.java +++ b/src/main/java/com/webank/wecross/stub/web3/Web3Connection.java @@ -246,7 +246,6 @@ private void handleAsyncCallRequest(Request request, Callback callback) { String from = transactionParams.getFrom(); String to = transactionParams.getTo(); String data = transactionParams.getData(); - BigInteger nonce = clientWrapper.getNonce(from); // build Transaction org.web3j.protocol.core.methods.request.Transaction transaction = @@ -337,7 +336,7 @@ private void handleAsyncTransactionRequest(Request request, Callback callback) { } } - private synchronized void refreshStubConfig(Web3StubConfig web3StubConfig) throws IOException { + private synchronized void refreshStubConfig(Web3StubConfig web3StubConfig) { this.resourceInfoList = web3StubConfig.convertToResourceInfos(); addProperty(Web3Constant.WEB3_PROPERTY_CHAIN_ID, chainId.toString()); diff --git a/src/main/java/com/webank/wecross/stub/web3/Web3Driver.java b/src/main/java/com/webank/wecross/stub/web3/Web3Driver.java new file mode 100644 index 0000000..bb396ce --- /dev/null +++ b/src/main/java/com/webank/wecross/stub/web3/Web3Driver.java @@ -0,0 +1,421 @@ +package com.webank.wecross.stub.web3; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.webank.wecross.stub.Account; +import com.webank.wecross.stub.Block; +import com.webank.wecross.stub.BlockManager; +import com.webank.wecross.stub.Connection; +import com.webank.wecross.stub.Driver; +import com.webank.wecross.stub.Path; +import com.webank.wecross.stub.Request; +import com.webank.wecross.stub.ResourceInfo; +import com.webank.wecross.stub.Transaction; +import com.webank.wecross.stub.TransactionContext; +import com.webank.wecross.stub.TransactionException; +import com.webank.wecross.stub.TransactionRequest; +import com.webank.wecross.stub.TransactionResponse; +import com.webank.wecross.stub.web3.account.Web3Account; +import com.webank.wecross.stub.web3.client.ClientWrapper; +import com.webank.wecross.stub.web3.common.ObjectMapperFactory; +import com.webank.wecross.stub.web3.common.Web3RequestType; +import com.webank.wecross.stub.web3.common.Web3StatusCode; +import com.webank.wecross.stub.web3.common.Web3StubException; +import com.webank.wecross.stub.web3.contract.BlockUtility; +import com.webank.wecross.stub.web3.protocol.request.TransactionParams; +import com.webank.wecross.stub.web3.protocol.response.TransactionPair; +import com.webank.wecross.stub.web3.uaproof.Signer; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.fisco.bcos.sdk.abi.ABICodec; +import org.fisco.bcos.sdk.abi.wrapper.ABICodecJsonWrapper; +import org.fisco.bcos.sdk.abi.wrapper.ABIDefinition; +import org.fisco.bcos.sdk.abi.wrapper.ABIDefinitionFactory; +import org.fisco.bcos.sdk.abi.wrapper.ABIObject; +import org.fisco.bcos.sdk.abi.wrapper.ABIObjectFactory; +import org.fisco.bcos.sdk.abi.wrapper.ContractABIDefinition; +import org.fisco.bcos.sdk.crypto.CryptoSuite; +import org.fisco.bcos.sdk.model.CryptoType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.web3j.crypto.Credentials; +import org.web3j.crypto.RawTransaction; +import org.web3j.crypto.TransactionDecoder; +import org.web3j.crypto.TransactionEncoder; +import org.web3j.protocol.core.methods.response.EthCall; +import org.web3j.protocol.core.methods.response.TransactionReceipt; +import org.web3j.utils.Numeric; + +public class Web3Driver implements Driver { + private static final Logger logger = LoggerFactory.getLogger(Web3Driver.class); + private final ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper(); + private final ABICodecJsonWrapper codecJsonWrapper; + private final ABICodec abiCodec; + private final ABIDefinitionFactory abiDefinitionFactory; + + public Web3Driver() { + CryptoSuite cryptoSuite = new CryptoSuite(CryptoType.ECDSA_TYPE); + this.codecJsonWrapper = new ABICodecJsonWrapper(true); + this.abiCodec = new ABICodec(cryptoSuite, true); + this.abiDefinitionFactory = new ABIDefinitionFactory(cryptoSuite); + } + + @Override + public ImmutablePair decodeTransactionRequest(Request request) { + + int requestType = request.getType(); + if ((requestType != Web3RequestType.CALL) + && (requestType != Web3RequestType.SEND_TRANSACTION)) { + return new ImmutablePair<>(false, null); + } + try { + TransactionParams transactionParams = + objectMapper.readValue(request.getData(), TransactionParams.class); + if (logger.isTraceEnabled()) { + logger.trace(" TransactionParams: {}", transactionParams); + } + Objects.requireNonNull( + transactionParams.getTransactionRequest(), "TransactionRequest is null"); + Objects.requireNonNull(transactionParams.getData(), "Data is null"); + Objects.requireNonNull(transactionParams.getSubType(), "type is null"); + TransactionRequest transactionRequest = transactionParams.getTransactionRequest(); + TransactionParams.SUB_TYPE subType = transactionParams.getSubType(); + String contractAbi = transactionParams.getAbi(); + String[] args = transactionRequest.getArgs(); + String method = transactionRequest.getMethod(); + String encodedFromInput; + String encodedFromNow = + abiCodec.encodeMethodFromString( + contractAbi, method, args != null ? Arrays.asList(args) : new ArrayList<>()); + switch (subType) { + case SEND_TX: + String data = transactionParams.getData(); + RawTransaction rawTransaction = TransactionDecoder.decode(data); + encodedFromInput = rawTransaction.getData(); + break; + case CALL: + encodedFromInput = transactionParams.getData(); + break; + default: + { + return new ImmutablePair<>(true, null); + } + } + if (Numeric.cleanHexPrefix(encodedFromNow).equals(Numeric.cleanHexPrefix(encodedFromInput))) { + return new ImmutablePair<>(true, transactionRequest); + } + logger.warn( + " encodedFromInput not meet expectations, encodedFromInput:{}, encodedFromNow:{}", + encodedFromInput, + encodedFromNow); + return new ImmutablePair<>(true, null); + + } catch (Exception e) { + logger.error("decodeTransactionRequest error: ", e); + return new ImmutablePair<>(true, null); + } + } + + @Override + public List getResources(Connection connection) { + if (connection instanceof Web3Connection) { + return ((Web3Connection) connection).getResourceInfoList(); + } + logger.error("Not Web3 connection, connection name: {}", connection.getClass().getName()); + return new ArrayList<>(); + } + + @Override + public void asyncCall( + TransactionContext context, + TransactionRequest request, + boolean byProxy, + Connection connection, + Callback callback) { + + try { + String from = context.getAccount().getIdentity(); + Path path = context.getPath(); + String name = path.getResource(); + Web3Connection web3Connection = (Web3Connection) connection; + Map properties = connection.getProperties(); + String contractAbi = web3Connection.getAbi(name); + String contractAddress = properties.get(name); + checkContract(name, contractAddress, contractAbi); + String[] args = request.getArgs(); + String method = request.getMethod(); + String encodedMethodWithArgs = + abiCodec.encodeMethodFromString( + contractAbi, method, args != null ? Arrays.asList(args) : new ArrayList<>()); + TransactionParams transaction = + new TransactionParams( + request, TransactionParams.SUB_TYPE.CALL, encodedMethodWithArgs, contractAbi); + transaction.setFrom(from); + transaction.setTo(contractAddress); + transaction.setAbi(contractAbi); + Request req = + Request.newRequest(Web3RequestType.CALL, objectMapper.writeValueAsBytes(transaction)); + connection.asyncSend( + req, + response -> { + try { + if (response.getErrorCode() != Web3StatusCode.Success) { + callback.onTransactionResponse( + new TransactionException(response.getErrorCode(), response.getErrorMessage()), + null); + return; + } + TransactionResponse transactionResponse = new TransactionResponse(); + EthCall ethCall = objectMapper.readValue(response.getData(), EthCall.class); + ContractABIDefinition contractABIDefinition = + abiDefinitionFactory.loadABI(contractAbi); + ABIDefinition abiDefinition = + contractABIDefinition.getFunctions().get(method).stream() + .filter( + function -> + function.getInputs().size() == (args == null ? 0 : args.length)) + .findFirst() + .orElseThrow( + () -> + new Web3StubException( + Web3StatusCode.MethodNotExist, "method not exist: " + method)); + ABIObject outputObject = ABIObjectFactory.createOutputObject(abiDefinition); + transactionResponse.setErrorCode(Web3StatusCode.Success); + transactionResponse.setMessage( + Web3StatusCode.getStatusMessage(Web3StatusCode.Success)); + transactionResponse.setResult( + codecJsonWrapper.decode(outputObject, ethCall.getValue()).toArray(new String[0])); + callback.onTransactionResponse(null, transactionResponse); + } catch (Exception e) { + logger.warn(" e: ", e); + callback.onTransactionResponse( + new TransactionException(Web3StatusCode.UnclassifiedError, e.getMessage()), null); + } + }); + } catch (Web3StubException wse) { + logger.warn(" e: ", wse); + callback.onTransactionResponse( + new TransactionException(wse.getErrorCode(), wse.getMessage()), null); + } catch (Exception e) { + logger.warn(" e: ", e); + callback.onTransactionResponse( + new TransactionException(Web3StatusCode.UnclassifiedError, e.getMessage()), null); + } + } + + @Override + public void asyncSendTransaction( + TransactionContext context, + TransactionRequest request, + boolean byProxy, + Connection connection, + Callback callback) { + try { + Credentials credentials = ((Web3Account) context.getAccount()).getCredentials(); + Path path = context.getPath(); + String name = path.getResource(); + Web3Connection web3Connection = (Web3Connection) connection; + Map properties = connection.getProperties(); + String contractAddress = properties.get(name); + String contractAbi = web3Connection.getAbi(name); + checkContract(name, contractAddress, contractAbi); + String[] args = request.getArgs(); + String method = request.getMethod(); + String encodedMethodWithArgs = + abiCodec.encodeMethodFromString( + contractAbi, method, args != null ? Arrays.asList(args) : new ArrayList<>()); + ClientWrapper clientWrapper = web3Connection.getClientWrapper(); + BigInteger nonce = clientWrapper.getNonce(context.getAccount().getIdentity()); + BigInteger gasLimit = clientWrapper.ethGasLimit(); + BigInteger gasPrice = clientWrapper.ethGasPrice(); + BigInteger chainId = web3Connection.getChainId(); + RawTransaction rawTransaction = + RawTransaction.createTransaction( + nonce, gasPrice, gasLimit, contractAddress, encodedMethodWithArgs); + byte[] signedMessage = + TransactionEncoder.signMessage(rawTransaction, chainId.longValue(), credentials); + TransactionParams transaction = + new TransactionParams( + request, + TransactionParams.SUB_TYPE.SEND_TX, + Numeric.toHexString(signedMessage), + contractAbi); + transaction.setFrom(credentials.getAddress()); + transaction.setTo(contractAddress); + transaction.setAbi(contractAbi); + Request req = + Request.newRequest( + Web3RequestType.SEND_TRANSACTION, objectMapper.writeValueAsBytes(transaction)); + connection.asyncSend( + req, + response -> { + try { + if (response.getErrorCode() != Web3StatusCode.Success) { + callback.onTransactionResponse( + new TransactionException(response.getErrorCode(), response.getErrorMessage()), + null); + return; + } + TransactionResponse transactionResponse = new TransactionResponse(); + TransactionReceipt receipt = + objectMapper.readValue(response.getData(), TransactionReceipt.class); + transactionResponse.setHash(receipt.getTransactionHash()); + transactionResponse.setBlockNumber(receipt.getBlockNumber().longValue()); + transactionResponse.setErrorCode(Web3StatusCode.Success); + transactionResponse.setMessage( + Web3StatusCode.getStatusMessage(Web3StatusCode.Success)); + callback.onTransactionResponse(null, transactionResponse); + } catch (Exception e) { + logger.warn(" e: ", e); + callback.onTransactionResponse( + new TransactionException(Web3StatusCode.UnclassifiedError, e.getMessage()), null); + } + }); + + } catch (Web3StubException wse) { + logger.warn(" e: ", wse); + callback.onTransactionResponse( + new TransactionException(wse.getErrorCode(), wse.getMessage()), null); + } catch (Exception e) { + logger.warn(" e: ", e); + callback.onTransactionResponse( + new TransactionException(Web3StatusCode.UnclassifiedError, e.getMessage()), null); + } + } + + private static void checkContract(String name, String contractAddress, String contractAbi) + throws Web3StubException { + if (StringUtils.isBlank(contractAbi)) { + throw new Web3StubException(Web3StatusCode.ABINotExist, "resource ABI not exist: " + name); + } + if (StringUtils.isBlank(contractAddress)) { + throw new Web3StubException( + Web3StatusCode.AddressNotExist, "resource address not exist: " + name); + } + } + + @Override + public void asyncGetBlockNumber(Connection connection, GetBlockNumberCallback callback) { + Request request = Request.newRequest(Web3RequestType.GET_BLOCK_NUMBER, ""); + connection.asyncSend( + request, + response -> { + if (response.getErrorCode() != Web3StatusCode.Success) { + logger.warn( + " errorCode: {}, errorMessage: {}", + response.getErrorCode(), + response.getErrorMessage()); + callback.onResponse(new Exception(response.getErrorMessage()), -1); + } else { + BigInteger blockNumber = new BigInteger(response.getData()); + logger.debug(" blockNumber: {}", blockNumber); + callback.onResponse(null, blockNumber.longValue()); + } + }); + } + + @Override + public void asyncGetBlock( + long blockNumber, boolean onlyHeader, Connection connection, GetBlockCallback callback) { + Request request = + Request.newRequest( + Web3RequestType.GET_BLOCK_BY_NUMBER, BigInteger.valueOf(blockNumber).toByteArray()); + connection.asyncSend( + request, + response -> { + if (response.getErrorCode() != Web3StatusCode.Success) { + logger.warn( + " asyncGetBlock, errorCode: {}, errorMessage: {}", + response.getErrorCode(), + response.getErrorMessage()); + callback.onResponse(new Exception(response.getErrorMessage()), null); + } else { + try { + Block block = BlockUtility.convertToBlock(response.getData(), onlyHeader); + callback.onResponse(null, block); + } catch (Exception e) { + logger.warn(" blockNumber: {}, e: ", blockNumber, e); + callback.onResponse(e, null); + } + } + }); + } + + @Override + public void asyncGetTransaction( + String transactionHash, + long blockNumber, + BlockManager blockManager, + boolean isVerified, + Connection connection, + GetTransactionCallback callback) { + Request request = Request.newRequest(Web3RequestType.GET_TRANSACTION, transactionHash); + connection.asyncSend( + request, + response -> { + try { + if (logger.isDebugEnabled()) { + logger.debug("Request get Transaction, transactionHash: {}", transactionHash); + } + if (response.getErrorCode() != Web3StatusCode.Success) { + callback.onResponse( + new Web3StubException(response.getErrorCode(), response.getErrorMessage()), null); + return; + } + TransactionPair transactionPair = + objectMapper.readValue(response.getData(), TransactionPair.class); + org.web3j.protocol.core.methods.response.Transaction transactionResponse = + transactionPair.getTransaction(); + TransactionReceipt transactionReceipt = transactionPair.getTransactionReceipt(); + byte[] txBytes = objectMapper.writeValueAsBytes(transactionResponse); + byte[] receiptBytes = objectMapper.writeValueAsBytes(transactionReceipt); + Transaction transaction = new Transaction(); + transaction.setReceiptBytes(receiptBytes); + transaction.setTxBytes(txBytes); + transaction.setAccountIdentity(transactionReceipt.getFrom()); + transaction.setTransactionByProxy(false); + transaction.getTransactionResponse().setHash(transactionHash); + transaction + .getTransactionResponse() + .setBlockNumber(transactionReceipt.getBlockNumber().longValue()); + callback.onResponse(null, transaction); + if (logger.isDebugEnabled()) { + logger.debug(" transactionHash: {}, transaction: {}", transactionHash, transaction); + } + } catch (Exception e) { + callback.onResponse( + new Web3StubException(Web3StatusCode.UnclassifiedError, e.getMessage()), null); + } + }); + } + + @Override + public void asyncCustomCommand( + String command, + Path path, + Object[] args, + Account account, + BlockManager blockManager, + Connection connection, + CustomCommandCallback callback) {} + + @Override + public byte[] accountSign(Account account, byte[] message) { + if (!(account instanceof Web3Account)) { + throw new UnsupportedOperationException( + "Not Web3Account, account name: " + account.getClass().getName()); + } + Credentials credentials = ((Web3Account) account).getCredentials(); + return Signer.sign(credentials.getEcKeyPair(), message); + } + + @Override + public boolean accountVerify(String identity, byte[] signBytes, byte[] message) { + return Signer.verify(signBytes, message, identity); + } +} diff --git a/src/main/java/com/webank/wecross/stub/web3/common/ObjectMapperFactory.java b/src/main/java/com/webank/wecross/stub/web3/common/ObjectMapperFactory.java index 8730f1c..794e05b 100644 --- a/src/main/java/com/webank/wecross/stub/web3/common/ObjectMapperFactory.java +++ b/src/main/java/com/webank/wecross/stub/web3/common/ObjectMapperFactory.java @@ -36,6 +36,7 @@ private static ObjectMapper configureObjectMapper(ObjectMapper objectMapper) { SimpleModule simpleModule = new SimpleModule(); simpleModule.addSerializer(EthBlock.TransactionHash.class, new TransactionHashSerialize()); simpleModule.addSerializer(Transaction.class, new TransactionSerialize()); + objectMapper.registerModule(simpleModule); return objectMapper; } diff --git a/src/main/java/com/webank/wecross/stub/web3/common/Web3SignatureException.java b/src/main/java/com/webank/wecross/stub/web3/common/Web3SignatureException.java new file mode 100644 index 0000000..88210bc --- /dev/null +++ b/src/main/java/com/webank/wecross/stub/web3/common/Web3SignatureException.java @@ -0,0 +1,11 @@ +package com.webank.wecross.stub.web3.common; + +public class Web3SignatureException extends RuntimeException { + public Web3SignatureException(String message) { + super(message); + } + + public Web3SignatureException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/webank/wecross/stub/web3/common/Web3StatusCode.java b/src/main/java/com/webank/wecross/stub/web3/common/Web3StatusCode.java index 78ed486..267af13 100644 --- a/src/main/java/com/webank/wecross/stub/web3/common/Web3StatusCode.java +++ b/src/main/java/com/webank/wecross/stub/web3/common/Web3StatusCode.java @@ -30,7 +30,7 @@ public class Web3StatusCode { public static final int ABINotExist = 2040; public static final int EncodeAbiFailed = 2041; public static final int MethodNotExist = 2042; - + public static final int AddressNotExist = 2043; public static final int UnsupportedRPC = 2050; public static final int UnclassifiedError = 2100; diff --git a/src/main/java/com/webank/wecross/stub/web3/contract/BlockUtility.java b/src/main/java/com/webank/wecross/stub/web3/contract/BlockUtility.java new file mode 100644 index 0000000..191aa71 --- /dev/null +++ b/src/main/java/com/webank/wecross/stub/web3/contract/BlockUtility.java @@ -0,0 +1,71 @@ +package com.webank.wecross.stub.web3.contract; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.webank.wecross.stub.Block; +import com.webank.wecross.stub.BlockHeader; +import com.webank.wecross.stub.Transaction; +import com.webank.wecross.stub.web3.common.ObjectMapperFactory; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.web3j.protocol.core.methods.response.EthBlock; + +public class BlockUtility { + + private static final Logger logger = LoggerFactory.getLogger(BlockUtility.class); + + public static BlockHeader convertToBlockHeader(EthBlock.Block block) { + BlockHeader blockHeader = new BlockHeader(); + blockHeader.setHash(block.getHash()); + blockHeader.setPrevHash(block.getParentHash().isEmpty() ? null : block.getParentHash()); + blockHeader.setNumber(block.getNumber().longValue()); + blockHeader.setReceiptRoot(block.getReceiptsRoot()); + blockHeader.setStateRoot(block.getStateRoot()); + blockHeader.setTransactionRoot(block.getTransactionsRoot()); + blockHeader.setTimestamp(block.getTimestamp().longValue()); + return blockHeader; + } + + public static Block convertToBlock(EthBlock.Block block, boolean onlyHeader) + throws JsonProcessingException { + Block stubBlock = new Block(); + + /** BlockHeader */ + BlockHeader blockHeader = convertToBlockHeader(block); + stubBlock.setBlockHeader(blockHeader); + + List transactionList = new ArrayList<>(); + if (!onlyHeader + && !block.getTransactions().isEmpty() + && block.getTransactions().get(0) instanceof EthBlock.TransactionObject) { + for (int i = 0; i < block.getTransactions().size(); i++) { + EthBlock.TransactionObject transactionObject = + (EthBlock.TransactionObject) block.getTransactions().get(i); + byte[] txBytes = ObjectMapperFactory.getObjectMapper().writeValueAsBytes(transactionObject); + String transactionHash = transactionObject.getHash(); + Transaction transaction = new Transaction(); + transaction.setTxBytes(txBytes); + transaction.setAccountIdentity(transactionObject.getFrom()); + transaction.setTransactionByProxy(false); + transaction.getTransactionResponse().setHash(transactionHash); + transaction.getTransactionResponse().setBlockNumber(block.getNumber().longValue()); + transactionList.add(transaction); + } + } + stubBlock.setTransactionsWithDetail(transactionList); + return stubBlock; + } + + public static Block convertToBlock(byte[] blockBytes, boolean onlyHeader) throws IOException { + EthBlock.Block block = + ObjectMapperFactory.getObjectMapper().readValue(blockBytes, EthBlock.Block.class); + if (logger.isDebugEnabled()) { + logger.debug("blockNumber: {}, blockHash: {}", block.getNumber(), block.getHash()); + } + Block stubBlock = convertToBlock(block, onlyHeader); + stubBlock.setRawBytes(blockBytes); + return stubBlock; + } +} diff --git a/src/main/java/com/webank/wecross/stub/web3/uaproof/Signer.java b/src/main/java/com/webank/wecross/stub/web3/uaproof/Signer.java new file mode 100644 index 0000000..4f2bd5d --- /dev/null +++ b/src/main/java/com/webank/wecross/stub/web3/uaproof/Signer.java @@ -0,0 +1,42 @@ +package com.webank.wecross.stub.web3.uaproof; + +import com.webank.wecross.stub.web3.common.Web3SignatureException; +import java.math.BigInteger; +import java.util.Arrays; +import org.apache.commons.lang3.StringUtils; +import org.web3j.crypto.ECKeyPair; +import org.web3j.crypto.Hash; +import org.web3j.crypto.Keys; +import org.web3j.crypto.Sign; +import org.web3j.utils.Numeric; + +public class Signer { + public static byte[] sign(ECKeyPair keyPair, byte[] srcData) { + byte[] hashData = Hash.sha3(srcData); + Sign.SignatureData signatureData = Sign.signPrefixedMessage(hashData, keyPair); + byte[] r = signatureData.getR(); + byte[] s = signatureData.getS(); + byte[] v = signatureData.getV(); + byte[] signByte = Arrays.copyOf(r, v.length + r.length + s.length); + System.arraycopy(s, 0, signByte, r.length, s.length); + System.arraycopy(v, 0, signByte, r.length + s.length, v.length); + return signByte; + } + + public static boolean verify(byte[] signData, byte[] srcData, String address) { + byte[] hashData = Hash.sha3(srcData); + Sign.SignatureData signatureData = + new Sign.SignatureData( + signData[64], + Arrays.copyOfRange(signData, 0, 32), + Arrays.copyOfRange(signData, 32, 64)); + BigInteger publicKey; + try { + publicKey = Sign.signedPrefixedMessageToKey(hashData, signatureData); + } catch (Exception e) { + throw new Web3SignatureException("verify failed: " + e.getMessage()); + } + return StringUtils.equalsIgnoreCase( + Keys.getAddress(publicKey), Numeric.cleanHexPrefix(address)); + } +}