diff --git a/src/main/java/com/moonstoneid/siwe/SiweMessage.java b/src/main/java/com/moonstoneid/siwe/SiweMessage.java index 654d818..7206d25 100644 --- a/src/main/java/com/moonstoneid/siwe/SiweMessage.java +++ b/src/main/java/com/moonstoneid/siwe/SiweMessage.java @@ -116,7 +116,7 @@ public void verify(String domain, String nonce, String signature) throws SiweExc * @param domain RFC 4501 dns authority that is requesting the signing * @param nonce The nonce issued by the backend * @param signature A valid signature for this message - * @param provider A {@link Web3j} provider instance to conduct EIP-1271 signature check + * @param provider A {@link Web3j} provider instance to conduct EIP-6492 signature check * * @throws SiweException if the signature is invalid or if fields ar missing */ diff --git a/src/main/java/com/moonstoneid/siwe/validator/EIP1271.java b/src/main/java/com/moonstoneid/siwe/validator/EIP1271.java deleted file mode 100644 index 73074f7..0000000 --- a/src/main/java/com/moonstoneid/siwe/validator/EIP1271.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.moonstoneid.siwe.validator; - -import java.math.BigInteger; -import java.util.Arrays; -import org.web3j.abi.TypeReference; -import org.web3j.abi.datatypes.Function; -import org.web3j.abi.datatypes.Type; -import org.web3j.abi.datatypes.generated.Bytes4; -import org.web3j.crypto.Credentials; -import org.web3j.protocol.Web3j; -import org.web3j.protocol.core.RemoteFunctionCall; -import org.web3j.tx.Contract; -import org.web3j.tx.TransactionManager; -import org.web3j.tx.gas.ContractGasProvider; - -public class EIP1271 extends Contract { - - public static final String BINARY = "Bin file was not provided"; - - public static final String FUNC_ISVALIDSIGNATURE = "isValidSignature"; - - @Deprecated - protected EIP1271(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, - BigInteger gasLimit) { - super(BINARY, contractAddress, web3j, credentials, gasPrice, gasLimit); - } - - protected EIP1271(String contractAddress, Web3j web3j, Credentials credentials, - ContractGasProvider contractGasProvider) { - super(BINARY, contractAddress, web3j, credentials, contractGasProvider); - } - - @Deprecated - protected EIP1271(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, - BigInteger gasLimit) { - super(BINARY, contractAddress, web3j, transactionManager, gasPrice, gasLimit); - } - - protected EIP1271(String contractAddress, Web3j web3j, TransactionManager transactionManager, - ContractGasProvider contractGasProvider) { - super(BINARY, contractAddress, web3j, transactionManager, contractGasProvider); - } - - public RemoteFunctionCall isValidSignature(byte[] _hash, byte[] _signature) { - final Function function = new Function(FUNC_ISVALIDSIGNATURE, - Arrays.asList(new org.web3j.abi.datatypes.generated.Bytes32(_hash), - new org.web3j.abi.datatypes.DynamicBytes(_signature)), - Arrays.>asList(new TypeReference() {})); - return executeRemoteCallSingleValueReturn(function, byte[].class); - } - - @Deprecated - public static EIP1271 load(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, - BigInteger gasLimit) { - return new EIP1271(contractAddress, web3j, credentials, gasPrice, gasLimit); - } - - @Deprecated - public static EIP1271 load(String contractAddress, Web3j web3j, TransactionManager transactionManager, - BigInteger gasPrice, BigInteger gasLimit) { - return new EIP1271(contractAddress, web3j, transactionManager, gasPrice, gasLimit); - } - - public static EIP1271 load(String contractAddress, Web3j web3j, Credentials credentials, - ContractGasProvider contractGasProvider) { - return new EIP1271(contractAddress, web3j, credentials, contractGasProvider); - } - - public static EIP1271 load(String contractAddress, Web3j web3j, TransactionManager transactionManager, - ContractGasProvider contractGasProvider) { - return new EIP1271(contractAddress, web3j, transactionManager, contractGasProvider); - } - -} diff --git a/src/main/java/com/moonstoneid/siwe/validator/EIP6492UniversalValidator.java b/src/main/java/com/moonstoneid/siwe/validator/EIP6492UniversalValidator.java new file mode 100644 index 0000000..fefbd01 --- /dev/null +++ b/src/main/java/com/moonstoneid/siwe/validator/EIP6492UniversalValidator.java @@ -0,0 +1,10 @@ +package com.moonstoneid.siwe.validator; + +public final class EIP6492UniversalValidator { + // https://github.com/AmbireTech/signature-validator/blob/main/contracts/DeploylessUniversalSigValidator.sol + // Outputted by solc DeploylessUniversalSigValidator.sol --bin --optimize --optimize-runs=1 + public static final String CODE = "0x60806040523480156200001157600080fd5b50604051620007003803806200070083398101604081905262000034916200056f565b6000620000438484846200004f565b9050806000526001601ff35b600080846001600160a01b0316803b806020016040519081016040528181526000908060200190933c90507f6492649264926492649264926492649264926492649264926492649264926492620000a68462000451565b036200021f57600060608085806020019051810190620000c79190620005ce565b8651929550909350915060000362000192576000836001600160a01b031683604051620000f5919062000643565b6000604051808303816000865af19150503d806000811462000134576040519150601f19603f3d011682016040523d82523d6000602084013e62000139565b606091505b5050905080620001905760405162461bcd60e51b815260206004820152601e60248201527f5369676e617475726556616c696461746f723a206465706c6f796d656e74000060448201526064015b60405180910390fd5b505b604051630b135d3f60e11b808252906001600160a01b038a1690631626ba7e90620001c4908b90869060040162000661565b602060405180830381865afa158015620001e2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200020891906200069d565b6001600160e01b031916149450505050506200044a565b805115620002b157604051630b135d3f60e11b808252906001600160a01b03871690631626ba7e9062000259908890889060040162000661565b602060405180830381865afa15801562000277573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200029d91906200069d565b6001600160e01b031916149150506200044a565b8251604114620003195760405162461bcd60e51b815260206004820152603a6024820152600080516020620006e083398151915260448201527f3a20696e76616c6964207369676e6174757265206c656e677468000000000000606482015260840162000187565b620003236200046b565b506020830151604080850151855186939260009185919081106200034b576200034b620006c9565b016020015160f81c9050601b81148015906200036b57508060ff16601c14155b15620003cf5760405162461bcd60e51b815260206004820152603b6024820152600080516020620006e083398151915260448201527f3a20696e76616c6964207369676e617475726520762076616c75650000000000606482015260840162000187565b6040805160008152602081018083528a905260ff83169181019190915260608101849052608081018390526001600160a01b038a169060019060a0016020604051602081039080840390855afa1580156200042e573d6000803e3d6000fd5b505050602060405103516001600160a01b031614955050505050505b9392505050565b60006020825110156200046357600080fd5b508051015190565b60405180606001604052806003906020820280368337509192915050565b6001600160a01b03811681146200049f57600080fd5b50565b634e487b7160e01b600052604160045260246000fd5b60005b83811015620004d5578181015183820152602001620004bb565b50506000910152565b600082601f830112620004f057600080fd5b81516001600160401b03808211156200050d576200050d620004a2565b604051601f8301601f19908116603f01168101908282118183101715620005385762000538620004a2565b816040528381528660208588010111156200055257600080fd5b62000565846020830160208901620004b8565b9695505050505050565b6000806000606084860312156200058557600080fd5b8351620005928162000489565b6020850151604086015191945092506001600160401b03811115620005b657600080fd5b620005c486828701620004de565b9150509250925092565b600080600060608486031215620005e457600080fd5b8351620005f18162000489565b60208501519093506001600160401b03808211156200060f57600080fd5b6200061d87838801620004de565b935060408601519150808211156200063457600080fd5b50620005c486828701620004de565b6000825162000657818460208701620004b8565b9190910192915050565b828152604060208201526000825180604084015262000688816060850160208701620004b8565b601f01601f1916919091016060019392505050565b600060208284031215620006b057600080fd5b81516001600160e01b0319811681146200044a57600080fd5b634e487b7160e01b600052603260045260246000fdfe5369676e617475726556616c696461746f72237265636f7665725369676e6572"; + + private EIP6492UniversalValidator() { + } +} diff --git a/src/main/java/com/moonstoneid/siwe/validator/SignatureValidator.java b/src/main/java/com/moonstoneid/siwe/validator/SignatureValidator.java index bb673ee..d6a58b4 100644 --- a/src/main/java/com/moonstoneid/siwe/validator/SignatureValidator.java +++ b/src/main/java/com/moonstoneid/siwe/validator/SignatureValidator.java @@ -1,8 +1,17 @@ package com.moonstoneid.siwe.validator; import com.moonstoneid.siwe.SiweMessage; +import org.web3j.abi.DefaultFunctionEncoder; +import org.web3j.abi.TypeEncoder; +import org.web3j.abi.datatypes.Address; +import org.web3j.abi.datatypes.Bytes; +import org.web3j.abi.datatypes.DynamicBytes; +import org.web3j.abi.datatypes.generated.Bytes32; import org.web3j.crypto.*; import org.web3j.protocol.Web3j; +import org.web3j.protocol.core.DefaultBlockParameterName; +import org.web3j.protocol.core.methods.request.Transaction; +import org.web3j.protocol.core.methods.response.EthCall; import org.web3j.tx.gas.ContractGasProvider; import org.web3j.utils.Numeric; @@ -14,8 +23,6 @@ public class SignatureValidator { - // EIP-1271 magic value - private static final String EIP1271_MAGIC_VALUE = "0x1626ba7e"; private static final BigInteger GAS_LIMIT = BigInteger.valueOf(6721975L); private static final BigInteger GAS_PRICE = BigInteger.valueOf(20000000000L); private static Credentials credentials = null; @@ -35,10 +42,9 @@ private SignatureValidator() { /** * Validates the signature for the given message. * - * @param msg The {@link SiweMessage} - * @param sig The signature for the given message - * @param provider Optional {@link Web3j} instance to check signature of smart contract wallets (EIP-1271) - * + * @param msg The {@link SiweMessage} + * @param sig The signature for the given message + * @param provider Optional {@link Web3j} instance to check signature of smart contract wallets (EIP-6492) * @return true if the signature is correct, else false */ public static boolean isValidSignature(SiweMessage msg, String sig, Web3j provider) { @@ -61,7 +67,7 @@ private static List isEOAWalletSignatureInternally(String msg, String si byte[] signatureBytes = Numeric.hexStringToByteArray(sig); // A valid signature must have a length of 65 bytes - if(signatureBytes.length != 65){ + if (signatureBytes.length != 65) { return matchedAddresses; } @@ -79,7 +85,7 @@ private static List isEOAWalletSignatureInternally(String msg, String si try { publicKey = Sign.recoverFromSignature((byte) i, new ECDSASignature( new BigInteger(1, sd.getR()), new BigInteger(1, sd.getS())), msgHash); - } catch (Exception e){ + } catch (Exception e) { return matchedAddresses; } @@ -91,25 +97,31 @@ private static List isEOAWalletSignatureInternally(String msg, String si return matchedAddresses; } - // Conducts an EIP-1271 signature check + // Conducts an EIP-6492 signature check private static boolean isContractWalletSignature(Web3j provider, SiweMessage message, String signature) { - // If provider is missing, EIP-1271 signature validation is skipped - if(provider == null) { + // If provider is missing, EIP-6492 signature validation is skipped + if (provider == null) { return false; } try { - String contractAddress = message.getAddress(); - EIP1271 contract = EIP1271.load(contractAddress, provider, credentials, contractGasProvider); - byte[] msgHash = Sign.getEthereumMessageHash(message.toMessage().getBytes(StandardCharsets.UTF_8)); - byte[] sig = Numeric.hexStringToByteArray(signature); - - byte[] response = contract.isValidSignature(msgHash, sig).sendAsync().get(); - if(response == null) { + String signerAddress = message.getAddress(); + String data = "%s%s".formatted( + EIP6492UniversalValidator.CODE, + DefaultFunctionEncoder.encodeConstructor(List.of( + new Address(signerAddress), + new Bytes32(Sign.getEthereumMessageHash(message.toMessage().getBytes(StandardCharsets.UTF_8))), + new DynamicBytes(Numeric.hexStringToByteArray(signature)) + )) + ); + EthCall result = provider + .ethCall(new Transaction(null, null, null, null, null, null, data), DefaultBlockParameterName.LATEST) + .send(); + + if (result.hasError()) { return false; } - String responseAsHex = Numeric.toHexString(response); - // Check if response matches EIP-1271 magic value - return responseAsHex.equalsIgnoreCase(EIP1271_MAGIC_VALUE); + + return "0x01".equals(result.getValue()); } catch (Exception e) { return false; } diff --git a/src/test/java/com/moonstoneid/siwe/validator/EIP1271Tests.java b/src/test/java/com/moonstoneid/siwe/validator/EIP1271Tests.java deleted file mode 100644 index 8b87d81..0000000 --- a/src/test/java/com/moonstoneid/siwe/validator/EIP1271Tests.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.moonstoneid.siwe.validator; - -import com.moonstoneid.siwe.SiweMessage; -import com.moonstoneid.siwe.error.SiweException; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class EIP1271Tests { - - private static final String M1_MESSAGE = "example.com wants you to sign in with your Ethereum account:" + - "\n0x9D7e5B049f5dc02D2A3a744972978e77586520Df\n\nSign in to use the app.\n\nURI: https://example.com" + - "\nVersion: 1\nChain ID: 1\nNonce: AnX5ELrm2ap11uiNE0MR\nIssued At: 2022-11-11T23:49:55.928Z" + - "\nExpiration Time: 2322-01-11T23:49:55.128Z\nNot Before: 2015-07-30T12:12:12.928Z" + - "\nRequest ID: 260cbfd5-4d74-42fc\nResources:\n- https://example.com/my-web2-claim.json" + - "\n- ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/"; - private static final String M1_SIGNATURE = "0x437c6f6ec1eb544ced0a6fca44165af9f31e18e" + - "95e2ec314361a58aeb008fa464ae449099994a5c76994a20b3fb8508e7ecc1e6b133c86dc42731de45fd69e781b"; - private static final String M1_SIGNATURE_INVALID_LENGTH = "0x437c6f6ec1eb"; - private static final String M1_SIGNATURE_INVALID_V = "0x437c6f6ec1eb544ced0a6fca44165af9f31e18e" + - "95e2ec314361a58aeb008fa464ae449099994a5c76994a20b3fb8508e7ecc1e6b133c86dc42731de45fd69e000a"; - - private static final String M2_MESSAGE = "127.0.0.1:8080 wants you to sign in with your Ethereum account:" + - "\n0x9778f927127886106c6D11Ced2A1a59CbBC4D259\n\nSign in to use the app.\n\nURI: http://127.0.0.1:8080\n" + - "Version: 1\nChain ID: 5\nNonce: IiFpdzBTnZOthb2xGENG\nIssued At: 2022-11-17T18:26:27.127849Z"; - private static final String M2_SIGNATURE = "0x"; - private static final String M2_SIGNATURE_INVALID = "0x123"; - - // --- Tests for validating SignatureValidator --- - - @Test - void testIsValidSignature() throws SiweException{ - SiweMessage siweMsg = new SiweMessage.Parser().parse(M1_MESSAGE); - String contractAddress = siweMsg.getAddress(); - } - -} diff --git a/src/test/java/com/moonstoneid/siwe/validator/SignatureValidatorTests.java b/src/test/java/com/moonstoneid/siwe/validator/SignatureValidatorTests.java index 150408f..4e9f3b1 100644 --- a/src/test/java/com/moonstoneid/siwe/validator/SignatureValidatorTests.java +++ b/src/test/java/com/moonstoneid/siwe/validator/SignatureValidatorTests.java @@ -4,24 +4,24 @@ import com.moonstoneid.siwe.error.SiweException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; -import org.web3j.crypto.Credentials; import org.web3j.protocol.Web3j; -import org.web3j.protocol.core.RemoteFunctionCall; -import org.web3j.protocol.http.HttpService; -import org.web3j.utils.Numeric; +import org.web3j.protocol.core.Request; +import org.web3j.protocol.core.Response; +import org.web3j.protocol.core.methods.response.EthCall; + +import java.io.IOException; + import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) public class SignatureValidatorTests { - - private static final String EIP1271_MAGIC_VALUE = "0x1626ba7e"; - private static final String EIP1271_INCORRECT_MAGIC_VALUE = "0x11111111"; + private static final String EIP6492_CONTRACT_SUCCESS = "0x01"; + private static final String EIP6492_CONTRACT_FAILURE = "0x00"; // EOA wallet messages & signature private static final String M1_MESSAGE = "example.com wants you to sign in with your Ethereum account:" + @@ -43,9 +43,6 @@ public class SignatureValidatorTests { private static final String M2_SIGNATURE = "0x"; private static final String M2_SIGNATURE_INVALID = "0x123"; - @Mock - protected EIP1271 eip1271; - // --- Tests for validating SignatureValidator --- @Test @@ -69,31 +66,43 @@ void testisValidSignatureEOASignatureVNegative() throws SiweException { } @Test - void testisValidSignatureContractWallet() throws SiweException { + void testisValidSignatureContractWallet() throws SiweException, IOException { SiweMessage siweMsg = new SiweMessage.Parser().parse(M2_MESSAGE); - Web3j dummyWeb3j = Web3j.build(new HttpService("")); - Mockito.when(eip1271.isValidSignature(any(),any())).thenReturn( - new RemoteFunctionCall<>(null, () -> Numeric.hexStringToByteArray(EIP1271_MAGIC_VALUE))); - - try (MockedStatic mock = Mockito.mockStatic(EIP1271.class, Mockito.CALLS_REAL_METHODS)) { - mock.when(() -> EIP1271.load(any(), any(), (Credentials) any(), any())).thenReturn(eip1271); - assertTrue(SignatureValidator.isValidSignature(siweMsg, M2_SIGNATURE, dummyWeb3j), - "Signature validation failed"); - } + + EthCall ethCall = new EthCall(); + ethCall.setResult(EIP6492_CONTRACT_SUCCESS); + Web3j web3j = web3jWithEthCallResult(ethCall); + + assertTrue(SignatureValidator.isValidSignature(siweMsg, M2_SIGNATURE, web3j), + "Signature validation failed"); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private Web3j web3jWithEthCallResult(EthCall ethCall) throws IOException { + Request request = mock(Request.class); + when(request.send()).thenReturn(ethCall); + Web3j web3j = mock(Web3j.class); + when(web3j.ethCall(any(), any())).thenReturn((Request) request); + return web3j; } @Test - void testisValidSignatureContractWalletNegative() throws SiweException { + void testisValidSignatureContractWalletNegative() throws SiweException, IOException { SiweMessage siweMsg = new SiweMessage.Parser().parse(M2_MESSAGE); - Web3j dummyWeb3j = Web3j.build(new HttpService("")); - Mockito.when(eip1271.isValidSignature(any(),any())).thenReturn( - new RemoteFunctionCall<>(null, () -> Numeric.hexStringToByteArray(EIP1271_INCORRECT_MAGIC_VALUE))); - - try (MockedStatic mock = Mockito.mockStatic(EIP1271.class, Mockito.CALLS_REAL_METHODS)) { - mock.when(() -> EIP1271.load(any(), any(), (Credentials) any(), any())).thenReturn(eip1271); - assertFalse(SignatureValidator.isValidSignature(siweMsg, M2_SIGNATURE_INVALID, dummyWeb3j), - "Signature validation failed"); - } + EthCall ethCall = new EthCall(); + ethCall.setResult(EIP6492_CONTRACT_FAILURE); + Web3j web3j = web3jWithEthCallResult(ethCall); + assertFalse(SignatureValidator.isValidSignature(siweMsg, M2_SIGNATURE_INVALID, web3j), + "Signature validation failed"); } + @Test + void testisValidSignatureContractWalletError() throws SiweException, IOException { + SiweMessage siweMsg = new SiweMessage.Parser().parse(M2_MESSAGE); + EthCall ethCall = new EthCall(); + ethCall.setError(new Response.Error(1234, "test error")); + Web3j web3j = web3jWithEthCallResult(ethCall); + assertFalse(SignatureValidator.isValidSignature(siweMsg, M2_SIGNATURE_INVALID, web3j), + "Signature validation failed"); + } }