From 54e0af2d7f79bc745da2dfba8211ea27d18aedf1 Mon Sep 17 00:00:00 2001 From: metalurgical <97008724+metalurgical@users.noreply.github.com> Date: Mon, 11 Sep 2023 09:38:13 +0200 Subject: [PATCH] refactor: cleanup and common signing code --- .gitignore | 7 +- .idea/androidTestResultsUserPreferences.xml | 2 + .idea/compiler.xml | 2 +- .idea/misc.xml | 2 +- .../MpcProviderTests.java | 18 +- .../EthTssAccountParams.java | 66 +---- .../EthereumSignerError.java | 6 +- .../EthereumTssAccount.java | 252 ++++++++---------- .../web3_android_mpc_provider/Utils.java | 50 ---- 9 files changed, 134 insertions(+), 271 deletions(-) diff --git a/.gitignore b/.gitignore index aa724b7..10cfdbf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,7 @@ *.iml .gradle /local.properties -/.idea/caches -/.idea/libraries -/.idea/modules.xml -/.idea/workspace.xml -/.idea/navEditor.xml -/.idea/assetWizardSettings.xml +/.idea .DS_Store /build /captures diff --git a/.idea/androidTestResultsUserPreferences.xml b/.idea/androidTestResultsUserPreferences.xml index 9b2dee9..2a20205 100644 --- a/.idea/androidTestResultsUserPreferences.xml +++ b/.idea/androidTestResultsUserPreferences.xml @@ -10,6 +10,7 @@ + @@ -37,6 +38,7 @@ + diff --git a/.idea/compiler.xml b/.idea/compiler.xml index fb7f4a8..fcb19bf 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index bdd9278..a248a5b 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,7 @@ - + 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 bc78758..5e551f3 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 @@ -4,10 +4,14 @@ 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 java.io.IOException; import java.math.BigInteger; +import java.util.concurrent.ExecutionException; @RunWith(AndroidJUnit4.class) public class MpcProviderTests { @@ -42,7 +46,7 @@ public class MpcProviderTests { BigInteger[] nodeIndexs = {new BigInteger("1"), new BigInteger("2"), new BigInteger("3")}; @Test - public void testSigningMessage() { + public void testSigningMessage() throws TSSClientError, CustomSigningError { EthTssAccountParams params = new EthTssAccountParams( fullAddress, factorKey, tssNonce, tssShare, tssIndex, selected_tag, verifier, verifierId, nodeIndexs, tssEndpoints, sigs); @@ -55,19 +59,13 @@ public void testSigningMessage() { } @Test - public void testSigningTransaction() { + public void testSigningTransaction() throws TSSClientError, CustomSigningError, IOException, ExecutionException, InterruptedException { EthTssAccountParams params = new EthTssAccountParams( fullAddress, factorKey, tssNonce, tssShare, tssIndex, selected_tag, verifier, verifierId, nodeIndexs, tssEndpoints, sigs); - EthereumTssAccount account = new EthereumTssAccount(params); - - String fromAddress = Utils.generateAddressFromPubKey(params.getPublicKey()); - System.out.println("fromAddress: " + fromAddress); - String toAddress = "0x048975d4997D7578A3419851639c10318db430b6"; //Utils.generateAddressFromPubKey(params.getPublicKey()); - String transactionHash = account.signAndSendTransaction("https://rpc.ankr.com/eth_goerli", 0.001, - fromAddress, toAddress); - System.out.println("Transaction Hash: " + transactionHash); + String toAddress = "0x048975d4997D7578A3419851639c10318db430b6"; + String transactionHash = account.signAndSendTransaction("https://rpc.ankr.com/eth_goerli", 0.001, toAddress); assertNotNull(transactionHash); } diff --git a/web3-android-mpc-provider/src/main/java/com/web3auth/web3_android_mpc_provider/EthTssAccountParams.java b/web3-android-mpc-provider/src/main/java/com/web3auth/web3_android_mpc_provider/EthTssAccountParams.java index 56234a5..fef6a02 100644 --- a/web3-android-mpc-provider/src/main/java/com/web3auth/web3_android_mpc_provider/EthTssAccountParams.java +++ b/web3-android-mpc-provider/src/main/java/com/web3auth/web3_android_mpc_provider/EthTssAccountParams.java @@ -3,17 +3,17 @@ import java.math.BigInteger; public final class EthTssAccountParams { - private String publicKey; - private String factorKey; - private int tssNonce; - private String tssShare; - private String tssIndex; - private String selectedTag; - private String verifier; - private String verifierID; - private BigInteger[] nodeIndexes; - private String[] tssEndpoints; - private String[] authSigs; + final String publicKey; + final String factorKey; + final int tssNonce; + final String tssShare; + final String tssIndex; + final String selectedTag; + final String verifier; + final String verifierID; + final BigInteger[] nodeIndexes; + final String[] tssEndpoints; + final String[] authSigs; public EthTssAccountParams(String publicKey, String factorKey, int tssNonce, String tssShare, String tssIndex, String selectedTag, String verifier, String verifierID, BigInteger[] nodeIndexes, String[] tssEndpoints, String[] authSigs) { this.publicKey = publicKey; @@ -28,49 +28,5 @@ public EthTssAccountParams(String publicKey, String factorKey, int tssNonce, Str this.tssEndpoints = tssEndpoints; this.authSigs = authSigs; } - - public String getPublicKey() { - return publicKey; - } - - public String getFactorKey() { - return factorKey; - } - - public int getTssNonce() { - return tssNonce; - } - - public String getTssShare() { - return tssShare; - } - - public String getTssIndex() { - return tssIndex; - } - - public String getSelectedTag() { - return selectedTag; - } - - public String getVerifier() { - return verifier; - } - - public String getVerifierID() { - return verifierID; - } - - public BigInteger[] getNodeIndexes() { - return nodeIndexes; - } - - public String[] getTssEndpoints() { - return tssEndpoints; - } - - public String[] getAuthSigs() { - return authSigs; - } } diff --git a/web3-android-mpc-provider/src/main/java/com/web3auth/web3_android_mpc_provider/EthereumSignerError.java b/web3-android-mpc-provider/src/main/java/com/web3auth/web3_android_mpc_provider/EthereumSignerError.java index 33664dc..2b5a4b3 100644 --- a/web3-android-mpc-provider/src/main/java/com/web3auth/web3_android_mpc_provider/EthereumSignerError.java +++ b/web3-android-mpc-provider/src/main/java/com/web3auth/web3_android_mpc_provider/EthereumSignerError.java @@ -1,7 +1,7 @@ package com.web3auth.web3_android_mpc_provider; public class EthereumSignerError extends Error { - private ErrorType errorType; + private final ErrorType errorType; public EthereumSignerError(ErrorType errorType) { this.errorType = errorType; @@ -11,7 +11,7 @@ public String getErrorDescription() { switch (errorType) { case EMPTY_RAW_TRANSACTION: return "emptyRawTransaction"; - case INSUFFICIENT_Funds: + case INSUFFICIENT_FUNDS: return "insufficientFunds"; case UNKNOWN_ERROR: return "unknownError"; @@ -22,7 +22,7 @@ public String getErrorDescription() { public enum ErrorType { EMPTY_RAW_TRANSACTION, - INSUFFICIENT_Funds, + INSUFFICIENT_FUNDS, UNKNOWN_ERROR } } 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 fc2df29..0d64645 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 @@ -1,6 +1,5 @@ package com.web3auth.web3_android_mpc_provider; -import static com.web3auth.web3_android_mpc_provider.Utils.generateEndpoints; import static org.web3j.utils.Numeric.hexStringToByteArray; import android.util.Base64; @@ -15,6 +14,7 @@ import com.web3auth.tss_client_android.dkls.Precompute; import org.web3j.crypto.Hash; +import org.web3j.crypto.Keys; import org.web3j.crypto.RawTransaction; import org.web3j.crypto.Sign; import org.web3j.crypto.TransactionEncoder; @@ -34,6 +34,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.concurrent.ExecutionException; public class EthereumTssAccount { @@ -42,176 +43,119 @@ public class EthereumTssAccount { public EthereumTssAccount(EthTssAccountParams params) { ethAccountParams = params; - evmAddress = Utils.generateAddressFromPubKey(params.getPublicKey()); + evmAddress = Keys.toChecksumAddress(Keys.getAddress(params.publicKey)); } - public String signMessage(String message) { - String signature = null; - TSSClient client; - Map coeffs; - Pair> clientCoeffsPair; - try { - clientCoeffsPair = bootstrapTssClient(ethAccountParams); - client = clientCoeffsPair.first; - coeffs = clientCoeffsPair.second; - - boolean connected = client.checkConnected(); - if (!connected) { - throw new CustomSigningError("Unable to establish connection to TSS server"); - } - - Precompute precompute; - try { - precompute = client.precompute(coeffs, Arrays.asList(ethAccountParams.getAuthSigs())); - } catch (Exception e) { - e.printStackTrace(); - throw new EthereumSignerError(EthereumSignerError.ErrorType.UNKNOWN_ERROR); - } - - boolean ready = client.isReady(); - if (!ready) { - throw new EthereumSignerError(EthereumSignerError.ErrorType.UNKNOWN_ERROR); - } - - String signingMessage = TSSHelpers.hashMessage(message); - - Triple signatureResult; - try { - signatureResult = client.sign(signingMessage, true, "", precompute, Arrays.asList(ethAccountParams.getAuthSigs())); - } catch (TSSClientError e) { - throw new RuntimeException(e); - } - - try { - client.cleanup(ethAccountParams.getAuthSigs()); - } catch (TSSClientError e) { - throw new RuntimeException(e); - } - - boolean verified = TSSHelpers.verifySignature(signingMessage, signatureResult.getFirst(), - signatureResult.getSecond(), signatureResult.getThird(), Utils.convertToBytes(ethAccountParams.getPublicKey())); - - if (!verified) { - throw new EthereumSignerError(EthereumSignerError.ErrorType.UNKNOWN_ERROR); - } + public String signMessage(String message) throws TSSClientError, CustomSigningError { + String hash = TSSHelpers.hashMessage(message); + Triple signatureResult = sign(hash); + Byte v = signatureResult.getThird(); + if (v < 27) { + v = (byte) (v + 27); + } + return TSSHelpers.hexSignature(signatureResult.getFirst(), signatureResult.getSecond(), v); + } - signature = TSSHelpers.hexSignature(signatureResult.getFirst(), signatureResult.getSecond(), signatureResult.getThird()); - } catch (Exception e) { - throw new RuntimeException(e); + public String signAndSendTransaction(String url, Double amount, String toAddress) throws TSSClientError, CustomSigningError, IOException, ExecutionException, InterruptedException { + //setup Web3j + Web3j web3j = Web3j.build(new HttpService(url)); + EthGetTransactionCount ethGetTransactionCount = web3j.ethGetTransactionCount( + evmAddress, + DefaultBlockParameterName.LATEST + ).send(); + BigInteger nonce = ethGetTransactionCount.getTransactionCount(); + BigInteger value = Convert.toWei(Double.toString(amount), Convert.Unit.ETHER).toBigInteger(); + BigInteger gasLimit = BigInteger.valueOf(21000); + EthGasPrice gasPriceResponse = web3j.ethGasPrice().send(); + BigInteger gasPrice = gasPriceResponse.getGasPrice(); + EthChainId chainIdResponse = web3j.ethChainId().sendAsync().get(); + BigInteger chainId = chainIdResponse.getChainId(); + + RawTransaction rawTransaction = RawTransaction.createTransaction( + chainId.longValue(), + nonce, + gasLimit, + toAddress, + value, + "", + gasPrice, + gasPrice + ); + + byte[] encodedTransaction = TransactionEncoder.encode(rawTransaction); + String encodedTransactionString = Base64.encodeToString(Hash.sha3(encodedTransaction), Base64.NO_WRAP); + + Triple signatureResult = sign(encodedTransactionString); + + Sign.SignatureData signatureData = new Sign.SignatureData((byte) (signatureResult.getThird() + 27), + signatureResult.getSecond().toByteArray(), + signatureResult.getFirst().toByteArray()); + byte[] signedMsg = TransactionEncoder.encode(rawTransaction, signatureData); + + String finalSig = Numeric.toHexString(signedMsg); + + EthSendTransaction ethSendTransaction = web3j.ethSendRawTransaction(finalSig).send(); + + if (ethSendTransaction.getError() != null) { + throw new CustomSigningError(ethSendTransaction.getError().getMessage()); + } else { + return ethSendTransaction.getTransactionHash(); } - return signature; } - public String signAndSendTransaction(String url, Double amount, String fromAddress, String toAddress) { - String transactionHash; + private Triple sign(String hash) throws TSSClientError, CustomSigningError { TSSClient client; Map coeffs; Pair> clientCoeffsPair; - try { - //setup Web3j - Web3j web3j = Web3j.build(new HttpService(url)); - EthGetTransactionCount ethGetTransactionCount = web3j.ethGetTransactionCount( - fromAddress, - DefaultBlockParameterName.LATEST - ).send(); - BigInteger nonce = ethGetTransactionCount.getTransactionCount(); - BigInteger value = Convert.toWei(Double.toString(amount), Convert.Unit.ETHER).toBigInteger(); - BigInteger gasLimit = BigInteger.valueOf(21000); - EthGasPrice gasPriceResponse = web3j.ethGasPrice().send(); - BigInteger gasPrice = gasPriceResponse.getGasPrice(); - EthChainId chainIdResponse = web3j.ethChainId().sendAsync().get(); - BigInteger chainId = chainIdResponse.getChainId(); - - RawTransaction rawTransaction = RawTransaction.createTransaction( - chainId.longValue(), - nonce, - gasLimit, - toAddress, - value, - "", - gasPrice, - gasPrice - ); - - clientCoeffsPair = bootstrapTssClient(ethAccountParams); - client = clientCoeffsPair.first; - coeffs = clientCoeffsPair.second; - - boolean connected = client.checkConnected(); - if (!connected) { - throw new CustomSigningError("Unable to establish connection to TSS server"); - } - Precompute precompute; - try { - precompute = client.precompute(coeffs, Arrays.asList(ethAccountParams.getAuthSigs())); - } catch (Exception e) { - e.printStackTrace(); - throw new EthereumSignerError(EthereumSignerError.ErrorType.UNKNOWN_ERROR); - } + clientCoeffsPair = bootstrapTssClient(ethAccountParams); + client = clientCoeffsPair.first; + coeffs = clientCoeffsPair.second; - boolean ready = client.isReady(); - if (!ready) { - throw new EthereumSignerError(EthereumSignerError.ErrorType.UNKNOWN_ERROR); - } + boolean connected = client.checkConnected(); + if (!connected) { + throw new CustomSigningError("Unable to establish connection to TSS server"); + } - byte[] encodedTransaction = TransactionEncoder.encode(rawTransaction); - String encodedTransactionString = Base64.encodeToString(Hash.sha3(encodedTransaction), Base64.NO_WRAP); + Precompute precompute; + precompute = client.precompute(coeffs, Arrays.asList(ethAccountParams.authSigs)); + boolean ready = client.isReady(); + if (!ready) { + throw new EthereumSignerError(EthereumSignerError.ErrorType.UNKNOWN_ERROR); + } - Triple signatureResult; - try { - signatureResult = client.sign(encodedTransactionString, true, "", precompute, Arrays.asList(ethAccountParams.getAuthSigs())); - } catch (TSSClientError e) { - throw new RuntimeException(e); - } + Triple signatureResult; + signatureResult = client.sign(hash, true, null, precompute, Arrays.asList(ethAccountParams.authSigs)); - try { - client.cleanup(ethAccountParams.getAuthSigs()); - } catch (TSSClientError e) { - throw new RuntimeException(e); - } + client.cleanup(ethAccountParams.authSigs); - try { - Sign.SignatureData signatureData = new Sign.SignatureData((byte) (signatureResult.getThird() + 27), - signatureResult.getSecond().toByteArray(), - signatureResult.getFirst().toByteArray()); - byte[] signedMsg = TransactionEncoder.encode(rawTransaction, signatureData); - - String finalSig = Numeric.toHexString(signedMsg); - - EthSendTransaction ethSendTransaction = web3j.ethSendRawTransaction(finalSig).send(); - if (ethSendTransaction.getError() != null) { - transactionHash = ethSendTransaction.getError().getMessage(); - } else { - transactionHash = ethSendTransaction.getTransactionHash(); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } catch (Exception e) { - throw new RuntimeException(e); + boolean verified = TSSHelpers.verifySignature(hash, signatureResult.getFirst(), + signatureResult.getSecond(), signatureResult.getThird(), Utils.convertToBytes(ethAccountParams.publicKey)); + + if (!verified) { + throw new EthereumSignerError(EthereumSignerError.ErrorType.UNKNOWN_ERROR); } - return transactionHash; - } + return signatureResult; + } private Pair> bootstrapTssClient(EthTssAccountParams params) throws CustomSigningError, TSSClientError { - if (params.getPublicKey().length() < 128 || params.getPublicKey().length() > 130) { + if (params.publicKey.length() < 128 || params.publicKey.length() > 130) { throw new CustomSigningError("Public Key should be in uncompressed format"); } BigInteger randomKey = new BigInteger(1, Secp256k1.GenerateECKey()); BigInteger random = randomKey.add(BigInteger.valueOf(System.currentTimeMillis() / 1000)); - String sessionNonce = TSSHelpers.base64ToBase64url(TSSHelpers.hashMessage(random.toByteArray().toString())); - String session = TSSHelpers.assembleFullSession(params.getVerifier(), params.getVerifierID(), - params.getSelectedTag(), String.valueOf(params.getTssNonce()), sessionNonce); + String sessionNonce = TSSHelpers.base64ToBase64url(TSSHelpers.hashMessage(Arrays.toString(random.toByteArray()))); + String session = TSSHelpers.assembleFullSession(params.verifier, params.verifierID, + params.selectedTag, String.valueOf(params.tssNonce), sessionNonce); - BigInteger userTssIndex = new BigInteger(params.getTssIndex(), 16); + BigInteger userTssIndex = new BigInteger(params.tssIndex, 16); int parties = 4; int clientIndex = parties - 1; - EndpointsData endpointsData = generateEndpoints(parties, clientIndex, Arrays.asList(params.getTssEndpoints())); + EndpointsData endpointsData = generateEndpoints(parties, clientIndex, Arrays.asList(params.tssEndpoints)); List endpoints = endpointsData.getEndpoints(); List socketUrls = endpointsData.getTssWSEndpoints(); List partyIndexes = endpointsData.getPartyIndexes(); @@ -222,14 +166,32 @@ private Pair> bootstrapTssClient(EthTssAccountPar Map coeffs = TSSHelpers.getServerCoefficients(nodeInd.toArray(new BigInteger[0]), userTssIndex); - BigInteger shareUnsigned = new BigInteger(params.getTssShare(), 16); - BigInteger share = shareUnsigned; + BigInteger share = new BigInteger(params.tssShare, 16); BigInteger denormalizeShare = TSSHelpers.denormalizeShare(nodeInd.toArray(new BigInteger[0]), userTssIndex, share); TSSClient client = new TSSClient(session, clientIndex, partyIndexes.stream().mapToInt(Integer::intValue).toArray(), endpoints.toArray(new String[0]), socketUrls.toArray(new String[0]), TSSHelpers.base64Share(denormalizeShare), - TSSHelpers.base64PublicKey(hexStringToByteArray(params.getPublicKey()))); + TSSHelpers.base64PublicKey(hexStringToByteArray(params.publicKey))); return new Pair<>(client, coeffs); } + + private EndpointsData generateEndpoints(int parties, int clientIndex, List tssEndpoints) { + List endpoints = new ArrayList<>(); + List tssWSEndpoints = new ArrayList<>(); + List partyIndexes = new ArrayList<>(); + + for (int i = 0; i < parties; ++i) { + partyIndexes.add(i); + if (i == clientIndex) { + endpoints.add(null); + tssWSEndpoints.add(null); + } else { + endpoints.add(tssEndpoints.get(i)); + tssWSEndpoints.add(tssEndpoints.get(i).replace("/tss", "")); + } + } + + return new EndpointsData(endpoints, tssWSEndpoints, partyIndexes); + } } diff --git a/web3-android-mpc-provider/src/main/java/com/web3auth/web3_android_mpc_provider/Utils.java b/web3-android-mpc-provider/src/main/java/com/web3auth/web3_android_mpc_provider/Utils.java index 381518f..9f8163c 100644 --- a/web3-android-mpc-provider/src/main/java/com/web3auth/web3_android_mpc_provider/Utils.java +++ b/web3-android-mpc-provider/src/main/java/com/web3auth/web3_android_mpc_provider/Utils.java @@ -1,37 +1,6 @@ package com.web3auth.web3_android_mpc_provider; -import com.web3auth.tss_client_android.client.EndpointsData; - -import org.web3j.crypto.Keys; - -import java.util.ArrayList; -import java.util.List; - public class Utils { - - public static String generateAddressFromPubKey(String pubKey) { - return Keys.toChecksumAddress(Keys.getAddress(pubKey)); - } - - public static String stringToHex(String str) { - char[] chars = str.toCharArray(); - StringBuffer strBuffer = new StringBuffer(); - for (int i = 0; i < chars.length; i++) { - strBuffer.append(Integer.toHexString((int) chars[i])); - } - return strBuffer.toString(); - } - - public static String padLeft(String inputString, Character padChar, int length) { - if (inputString.length() >= length) return inputString; - StringBuilder sb = new StringBuilder(); - while (sb.length() < length - inputString.length()) { - sb.append(padChar); - } - sb.append(inputString); - return sb.toString(); - } - public static byte[] convertToBytes(String s) { String tmp; byte[] b = new byte[s.length() / 2]; @@ -42,23 +11,4 @@ public static byte[] convertToBytes(String s) { } return b; } - - public static EndpointsData generateEndpoints(int parties, int clientIndex, List tssEndpoints) { - List endpoints = new ArrayList(); - List tssWSEndpoints = new ArrayList(); - List partyIndexes = new ArrayList(); - - for (int i = 0; i < parties; ++i) { - partyIndexes.add(i); - if (i == clientIndex) { - endpoints.add(null); - tssWSEndpoints.add(null); - } else { - endpoints.add(tssEndpoints.get(i)); - tssWSEndpoints.add(tssEndpoints.get(i).replace("/tss", "")); - } - } - - return new EndpointsData(endpoints, tssWSEndpoints, partyIndexes); - } }