diff --git a/web3-android-mpc-provider/build.gradle b/web3-android-mpc-provider/build.gradle index ca4c401..80588d8 100644 --- a/web3-android-mpc-provider/build.gradle +++ b/web3-android-mpc-provider/build.gradle @@ -1,5 +1,6 @@ plugins { id 'com.android.library' + id 'maven-publish' } android { @@ -34,7 +35,32 @@ dependencies { implementation 'com.github.grvgoel81:tss-client-android:0.0.8' //Web3j implementation 'org.web3j:core:4.9.5' + testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' +} + +task javadoc(type: Javadoc) { + failOnError(false) + source = android.sourceSets.main.java.srcDirs + classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) + exclude '*R.java' + exclude { + it.file.path.contains('web3-android-mpc-provider') + } +} + +afterEvaluate { + javadoc.classpath += files(android.libraryVariants.collect { variant -> + variant.javaCompileProvider.get().classpath.files + }) + + publishing { + publications { + release(MavenPublication) { + from components.release + } + } + } } \ No newline at end of file diff --git a/web3-android-mpc-provider/src/androidTest/java/com/web3auth/web3_android_mpc_provider/MpcProviderTests.java b/web3-android-mpc-provider/src/androidTest/java/com/web3auth/web3_android_mpc_provider/MpcProviderTests.java index 2f5647e..9cc56ed 100644 --- a/web3-android-mpc-provider/src/androidTest/java/com/web3auth/web3_android_mpc_provider/MpcProviderTests.java +++ b/web3-android-mpc-provider/src/androidTest/java/com/web3auth/web3_android_mpc_provider/MpcProviderTests.java @@ -1,16 +1,21 @@ package com.web3auth.web3_android_mpc_provider; -import static org.junit.Assert.assertNotNull; - import androidx.test.ext.junit.runners.AndroidJUnit4; import com.web3auth.tss_client_android.client.TSSClientError; import org.junit.Test; import org.junit.runner.RunWith; +import org.web3j.protocol.Web3j; +import org.web3j.protocol.core.DefaultBlockParameterName; +import org.web3j.protocol.core.methods.response.EthChainId; +import org.web3j.protocol.core.methods.response.EthGasPrice; +import org.web3j.protocol.core.methods.response.EthGetTransactionCount; +import org.web3j.protocol.http.HttpService; import java.io.IOException; import java.math.BigInteger; +import java.security.SignatureException; import java.util.concurrent.ExecutionException; @RunWith(AndroidJUnit4.class) @@ -20,7 +25,7 @@ public class MpcProviderTests { System.loadLibrary("dkls-native"); } - final String example1 = "{\"types\":{\"EIP712Domain\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"version\",\"type\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\"}],\"Person\":[{\"name\":\"name\",\"type\":\"string\"},{{\"name\":\"wallet\",\"type\":\"address\"}]],\"Mail\":[{\"name\":\"from\",\"type\":\"Person\"},{\"name\":\"to\",\"type\":\"Person\"},{\"name\":\"contents\",\"type\":\"string\"}]},\"primaryType\":\"Mail\",\"domain\":{\"name\":\"Ether Mail\",\"version\":\"1\",\"chainId\":1,\"verifyingContract\":\"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC\"},\"message\":{\"from\":{\"name\":\"Account\",\"wallet\":\"0x048975d4997d7578a3419851639c10318db430b6\"},\"to\":{\"name\":\"Bob\",\"wallet\":\"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB\"},\"contents\":\"Hello, Bob!\"}}"; + final String example1 = "{\"types\":{\"EIP712Domain\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"version\",\"type\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\"}],\"Person\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"wallet\",\"type\":\"address\"}],\"Mail\":[{\"name\":\"from\",\"type\":\"Person\"},{\"name\":\"to\",\"type\":\"Person\"},{\"name\":\"contents\",\"type\":\"string\"}]},\"primaryType\":\"Mail\",\"domain\":{\"name\":\"Ether Mail\",\"version\":\"1\",\"chainId\":1,\"verifyingContract\":\"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC\"},\"message\":{\"from\":{\"name\":\"Account\",\"wallet\":\"0x048975d4997d7578a3419851639c10318db430b6\"},\"to\":{\"name\":\"Bob\",\"wallet\":\"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB\"},\"contents\":\"Hello,Bob!\"}}"; final String fullAddress = "04238569d5e12caf57d34fb5b2a0679c7775b5f61fd18cd69db9cc600a651749c3ec13a9367380b7a024a67f5e663f3afd40175c3223da63f6024b05d0bd9f292e"; final String factorKey = "3b4af35bc4838471f94825f34c4f649904a258c0907d348bed653eb0c94ec6c0"; @@ -58,12 +63,59 @@ public void testSigningMessage() throws TSSClientError, CustomSigningError { } @Test - public void testSigningTransaction() throws TSSClientError, CustomSigningError, IOException, ExecutionException, InterruptedException { + public void testSignTypedData() throws TSSClientError, IOException, CustomSigningError { + EthTssAccountParams params = new EthTssAccountParams( + fullAddress, factorKey, tssNonce, tssShare, tssIndex, selected_tag, verifier, verifierId, + nodeIndexs, tssEndpoints, sigs); + + EthereumTssAccount account = new EthereumTssAccount(params); + + account.signTypedData(example1); + } + + @Test + public void testSigningLegacyTransaction() throws TSSClientError, CustomSigningError, ExecutionException, InterruptedException, IOException { + EthTssAccountParams params = new EthTssAccountParams( + fullAddress, factorKey, tssNonce, tssShare, tssIndex, selected_tag, verifier, verifierId, + nodeIndexs, tssEndpoints, sigs); + EthereumTssAccount account = new EthereumTssAccount(params); + // setup Web3j + String url = "https://rpc.ankr.com/eth_goerli"; + Web3j web3j = Web3j.build(new HttpService(url)); + EthGetTransactionCount ethGetTransactionCount = web3j.ethGetTransactionCount( + account.evmAddress, + DefaultBlockParameterName.LATEST + ).send(); + BigInteger nonce = ethGetTransactionCount.getTransactionCount(); + BigInteger gasLimit = BigInteger.valueOf(21000); + EthChainId chainIdResponse = web3j.ethChainId().sendAsync().get(); + BigInteger chainId = chainIdResponse.getChainId(); + + String toAddress = "0xE09543f1974732F5D6ad442dDf176D9FA54a5Be0"; + account.signLegacyTransaction(chainId, toAddress, 0.001, null, nonce, gasLimit); + } + + @Test + public void testSigningTransaction() throws TSSClientError, CustomSigningError, SignatureException, ExecutionException, InterruptedException, IOException { EthTssAccountParams params = new EthTssAccountParams( fullAddress, factorKey, tssNonce, tssShare, tssIndex, selected_tag, verifier, verifierId, nodeIndexs, tssEndpoints, sigs); EthereumTssAccount account = new EthereumTssAccount(params); - String toAddress = "0x048975d4997D7578A3419851639c10318db430b6"; - account.signTransaction(new BigInteger("5"), toAddress, 0.001, null, new BigInteger("0"), new BigInteger("21000" ), new BigInteger("21000"), new BigInteger("21000")); + // setup Web3j + String url = "https://rpc.ankr.com/eth_goerli"; + Web3j web3j = Web3j.build(new HttpService(url)); + EthGetTransactionCount ethGetTransactionCount = web3j.ethGetTransactionCount( + account.evmAddress, + DefaultBlockParameterName.LATEST + ).send(); + BigInteger nonce = ethGetTransactionCount.getTransactionCount(); + BigInteger gasLimit = BigInteger.valueOf(21000); + EthChainId chainIdResponse = web3j.ethChainId().sendAsync().get(); + BigInteger chainId = chainIdResponse.getChainId(); + EthGasPrice gasPriceResponse = web3j.ethGasPrice().send(); + BigInteger gasPrice = gasPriceResponse.getGasPrice(); + + String toAddress = "0xE09543f1974732F5D6ad442dDf176D9FA54a5Be0"; + account.signTransaction(chainId, toAddress, 0.001, null, nonce, gasLimit, gasPrice, gasPrice); } } diff --git a/web3-android-mpc-provider/src/main/java/com/web3auth/web3_android_mpc_provider/EthereumTssAccount.java b/web3-android-mpc-provider/src/main/java/com/web3auth/web3_android_mpc_provider/EthereumTssAccount.java index f0bf30c..e848d01 100644 --- a/web3-android-mpc-provider/src/main/java/com/web3auth/web3_android_mpc_provider/EthereumTssAccount.java +++ b/web3-android-mpc-provider/src/main/java/com/web3auth/web3_android_mpc_provider/EthereumTssAccount.java @@ -19,6 +19,7 @@ import org.web3j.crypto.Keys; import org.web3j.crypto.RawTransaction; import org.web3j.crypto.Sign; +import org.web3j.crypto.StructuredDataEncoder; import org.web3j.crypto.TransactionEncoder; import org.web3j.protocol.Web3j; import org.web3j.protocol.core.methods.response.EthSendTransaction; @@ -27,6 +28,7 @@ import java.io.IOException; import java.math.BigInteger; +import java.security.SignatureException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -59,14 +61,58 @@ public String signMessage(String message) throws TSSClientError, CustomSigningEr return TSSHelpers.hexSignature(signatureResult.getFirst(), signatureResult.getSecond(), v); } - //todo: Method for signing eip712 structured data + public String signTypedData(String jsonData) throws IOException, TSSClientError, CustomSigningError { + StructuredDataEncoder dataEncoder = new StructuredDataEncoder(jsonData); + byte[] hashStructuredData = dataEncoder.hashStructuredData(); + String structuredData = android.util.Base64.encodeToString(hashStructuredData, Base64.NO_WRAP); + Triple signatureResult = sign(structuredData); + Byte v = signatureResult.getThird(); + if (v < 27) { + v = (byte) (v + 27); + } + return TSSHelpers.hexSignature(signatureResult.getFirst(), signatureResult.getSecond(), v); + } - //todo Method for signing eip1559 transactions, note that v follows a formula here and is not just modified with 27 - public String signTransaction(BigInteger chainID, String toAddress, Double amount, @Nullable String data, BigInteger nonce, BigInteger gasLimit, BigInteger maxPriorityFeePerGas, BigInteger maxFeePerGas) throws TSSClientError, CustomSigningError { + public String signLegacyTransaction(BigInteger chainID, String toAddress, Double amount, @Nullable String data, BigInteger nonce, BigInteger gasLimit) throws TSSClientError, CustomSigningError { + BigInteger value = Convert.toWei(Double.toString(amount), Convert.Unit.ETHER).toBigInteger(); + + String txData = ""; + if (data != null) { + txData = data; + } + + RawTransaction rawTransaction = RawTransaction.createTransaction( + chainID, + nonce, + gasLimit, + toAddress, + value, + txData + ); + + byte[] encodedTransaction = TransactionEncoder.encode(rawTransaction); + String encodedTransactionString = Base64.encodeToString(Hash.sha3(encodedTransaction), Base64.NO_WRAP); + + Triple signatureResult = sign(encodedTransactionString); + + Byte v = signatureResult.getThird(); + if (v < 27) { + v = (byte) (v + 27); + } + + Sign.SignatureData signatureData = new Sign.SignatureData(v, + signatureResult.getSecond().toByteArray(), + signatureResult.getFirst().toByteArray()); + + byte[] signedTransaction = TransactionEncoder.encode(rawTransaction, signatureData); + + return Numeric.toHexString(signedTransaction); + } + + public String signTransaction(BigInteger chainID, String toAddress, Double amount, @Nullable String data, BigInteger nonce, BigInteger gasLimit, BigInteger maxPriorityFeePerGas, BigInteger maxFeePerGas) throws TSSClientError, CustomSigningError, SignatureException { BigInteger value = Convert.toWei(Double.toString(amount), Convert.Unit.ETHER).toBigInteger(); - // todo: this appears to be a bug in web3j, if data is null it throws but is marked as nullable String txData = ""; if (data != null) { txData = data; @@ -88,12 +134,18 @@ public String signTransaction(BigInteger chainID, String toAddress, Double amoun Triple signatureResult = sign(encodedTransactionString); - Sign.SignatureData signatureData = new Sign.SignatureData((byte) (signatureResult.getThird() + 27), + Byte v = signatureResult.getThird(); + if (v < 35) { + v = (byte) ((chainID.byteValue() * 2) + (v + 35)); + } + + Sign.SignatureData signatureData = new Sign.SignatureData(v, signatureResult.getSecond().toByteArray(), signatureResult.getFirst().toByteArray()); - byte[] signedMsg = TransactionEncoder.encode(rawTransaction, signatureData); - return Numeric.toHexString(signedMsg); + byte[] signedTransaction = TransactionEncoder.encode(rawTransaction, signatureData); + + return Numeric.toHexString(signedTransaction); } public void sendTransaction(Web3j web3j, String signedTx) throws IOException, CustomSigningError { @@ -176,6 +228,7 @@ private Pair> bootstrapTssClient(EthTssAccountPar return new Pair<>(client, coeffs); } + private EndpointsData generateEndpoints(int parties, int clientIndex, List tssEndpoints) { List endpoints = new ArrayList<>(); List tssWSEndpoints = new ArrayList<>();