Skip to content
Open
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
2 changes: 1 addition & 1 deletion src/main/java/com/moonstoneid/siwe/SiweMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
74 changes: 0 additions & 74 deletions src/main/java/com/moonstoneid/siwe/validator/EIP1271.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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() {
}
}
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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;
Expand All @@ -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) {
Expand All @@ -61,7 +67,7 @@ private static List<String> 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;
}

Expand All @@ -79,7 +85,7 @@ private static List<String> 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;
}

Expand All @@ -91,25 +97,31 @@ private static List<String> 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;
}
Expand Down
38 changes: 0 additions & 38 deletions src/test/java/com/moonstoneid/siwe/validator/EIP1271Tests.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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:" +
Expand All @@ -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
Expand All @@ -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<EIP1271> 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<?, EthCall> 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<EIP1271> 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");
}
}