0

I'm trying to validate signatures created by a Safe wallet using ISignatureValidator.isValidSignature. However, the validation fails with the error GS026. Below is the code I am using to generate and validate the signature.

Code

Signing the Message

I am using the following code to generate a signature with a Safe wallet:

import { ethers } from "ethers";
import EthSafeMessage from "@safe-global/protocol-kit/dist/src/utils/messages/SafeMessage";
import { SigningMethod } from "@safe-global/protocol-kit/dist/src/types/signing";

// Prepare the message
const channelId = "0x...";
const timestamp = Date.now();
const initialMessage: Uint8Array = messageToSignForChannelUpdate(channelId, 0, timestamp, [], []);
const messageHex = ethers.utils.hexlify(initialMessage);

// Sign the message using Safe wallet
const ethSafeMessage = await safe.signMessage(
  new EthSafeMessage(messageHex),
  SigningMethod.ETH_SIGN,
  safeWalletAddress
);

// Get the encoded signature
const userSignature = ethSafeMessage.encodedSignatures();

Validating the Signature

I am using the following code to validate the signature on-chain using ISignatureValidator.isValidSignature:

import { ethers } from "ethers";

const messageHash = ethers.utils.keccak256(initialMessage);
const ethSignedMessageHash = ethers.utils.hashMessage(messageHash);

const expectedSigner = safeWalletAddress.toLowerCase();
const iSignatureValidator = ISignatureValidator__factory.connect(expectedSigner, provider);

let isSignatureValid: boolean;

try {
  const result = await iSignatureValidator.isValidSignature(ethSignedMessageHash, userSignature);
  isSignatureValid = result === "0x1626ba7e"; // ERC1271_MAGICVALUE
} catch (error) {
  console.error("Failed to validate signature", error);
  isSignatureValid = false;
}

if (!isSignatureValid) {
  throw new Error("Invalid user signature");
}

Error

I receive the following error when trying to validate the signature:

[Nest] ERROR [OpenchannelService] failed to validate signature
[Nest] ERROR [OpenchannelService] Error: execution reverted: "GS026" (action="call", data="0x08c379a...", reason="GS026", transaction={ ... }, invocation=null, revert={ "args": [ "GS026" ], "name": "Error", "signature": "Error(string)" }, code=CALL_EXCEPTION, version=6.13.2)

Environment

  • Ethers.js Version: 6.13.2
  • Safe SDK Version: 4.0.3
  • Solidity Version: 0.8.x

Question

How can I correctly validate a signature created by a Safe wallet using ISignatureValidator.isValidSignature? Am I missing something in the message signing or validation process?

1 Answer 1

0

The issue arises from the subtleties in handling the magic values and the message/hashes posted to the API. Here's a detailed test case illustrating the correct approach: Originally i confused the two possible magic values and the encoding of the input/messageHash

import { ConfigService } from "@nestjs/config";
import { ethers } from "ethers";
import { SafeFactoryConfigProvider } from "@modules/blockchain/safe-factory-config.provider";
import Safe, { SafeFactory, SigningMethod } from "@safe-global/protocol-kit";
import * as dotenv from "dotenv";
import {
  ERC1271_MAGICVALUE,
  ERC1271_MAGICVALUE_BYTES
} from "@modules/common/constants";
import {
  CompatibilityFallbackHandler__factory,
  IErc1271Validator__factory,
  ISignatureValidator__factory
} from "@contracts/bindings";

dotenv.config();

describe("Safe Wallet", () => {
  let provider: ethers.JsonRpcProvider;
  let owner1: ethers.Wallet;
  let safe: Safe;
  let safeAddress: string;

  beforeAll(async () => {
    const configService = new ConfigService();
    const safeFactoryConfigProvider = new SafeFactoryConfigProvider(configService);

    provider = new ethers.JsonRpcProvider(configService.getOrThrow<string>("RPC_URL"));
    owner1 = new ethers.Wallet(configService.getOrThrow<string>("WALLET_PRIVATE_KEY"), provider);
    const owner2 = ethers.Wallet.createRandom().connect(provider);

    const balance = await provider.getBalance(owner1.address);
    if (balance === 0n) throw new Error(`Owner1 (${owner1.address}) has zero balance.`);

    const safeFactory = await SafeFactory.init(safeFactoryConfigProvider.getSafeFactoryConfig());
    const config = { owners: [owner1.address, owner2.address], threshold: 1 };
    safe = await safeFactory.deploySafe({ safeAccountConfig: config });
    safeAddress = await safe.getAddress();
  });

  it("should sign and validate a 'Hello world' message using EIP-1271", async () => {
    const message = "Hello world";
    const safeMessage = safe.createMessage(message);
    const hash = ethers.hashMessage(message);
    const ethSafeMessage = await safe.signMessage(safeMessage, SigningMethod.ETH_SIGN, safeAddress);
    const userSignature = ethSafeMessage.encodedSignatures();

    const isValid = await safe.isValidSignature(hash, userSignature);
    expect(isValid).toBe(true);
  });

  it("should validate a 'Hello world' message using EIP-1271 with different interfaces", async () => {
    const message = "Hello world";
    const hash = ethers.hashMessage(message);
    const ethSignedMessageHash = ethers.hashMessage(hash);
    const ethSafeMessage = await safe.signMessage(safe.createMessage(message), SigningMethod.ETH_SIGN, safeAddress);
    const userSignature = ethSafeMessage.encodedSignatures();

    const iSignatureValidator = ISignatureValidator__factory.connect(safeAddress, provider);
    expect(await iSignatureValidator.isValidSignature(ethSignedMessageHash, userSignature)).toBe("0x20c13b0b");

    const compatibilityFallbackHandler = CompatibilityFallbackHandler__factory.connect(safeAddress, provider);
    expect(await compatibilityFallbackHandler["isValidSignature(bytes32,bytes)"](hash, userSignature)).toBe("0x1626ba7e");

    expect(await compatibilityFallbackHandler["isValidSignature(bytes,bytes)"](hash, userSignature)).toBe("0x20c13b0b");

    const validator = IErc1271Validator__factory.connect(safeAddress, provider);
    expect(await validator.isValidSignature(hash, userSignature)).toBe("0x1626ba7e");
  });
});

Key Points:

  1. Magic Values: Ensure you are using the correct magic values for validation.
  2. Message Hashing: Correctly hash the message before signing and validating.
  3. Sign and Validate: Ensure the signing and validation methods are using compatible formats and interfaces.

By carefully handling the magic values and message/hashes, you can successfully validate signatures created by a Safe wallet using ISignatureValidator.isValidSignature.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.