Skip to content
Embedded Wallet
Examples
Signing

Signing Messages

Simple Crypto's wallet is a smart contract wallet. Smart contract wallets enable many user-experience benefits, but require small changes to signing messages as smart contracts don’t have private keys to sign messages.

At a high level, there are 2 methods for signing messages you can use. The first is to sign a message that can be verified by the smart contract wallet. The second is to sign a message with the user's private key that controls their wallet.

EIP1271 Signed Messages

EIP1271 is a standard for smart contract signed messages. It allows protocols that rely on signed messages to also support smart contract wallets. It’s recommended that you use this method when

  1. Signing a message that will be verified by an EIP1271 compatible smart contract or
  2. Verifying that the user holds on-chain assets.

Example

import { ethers } from "ethers";
import SimpleCryptoSdk from "@simplecrypto/wallet-sdk";

const simpleCrypto = new SimpleCryptoSdk();

const message = "Hello World!";

(async () => {
		const provider = new ethers.providers.Web3Provider(simpleCrypto);

		const signer = provider.getSigner();

		const signature = await signer.signMessage(message);
})();

Verifying EIP1271 Signed Messages

To verify an EIP1271 signed message, call the isValidSignature method on the smart contract that signed the message.

💡

Verifying an EIP1271 signed message only works when the user's smart contract wallet has already been deployed. User's smart contract wallets are deployed when users submit their first transaction. If you trying to verifying a message before the user's smart contract wallet has been deployed an error will be thrown.

Example

import { ethers } from "ethers";

const EIP_1271_ABI = [
    {
        constant: true,
        inputs: [
            {
                name: "_messageHash",
                type: "bytes",
            },
            {
                name: "_signature",
                type: "bytes",
            },
        ],
        name: "isValidSignature",
        outputs: [
            {
                name: "magicValue",
                type: "bytes4",
            },
        ],
        payable: false,
        stateMutability: "view",
        type: "function",
    },
];

// EIP1271 stipulates that this value is returned on successful verification
const MAGIC_VALUE = ethers.utils
    .keccak256(ethers.utils.toUtf8Bytes("isValidSignature(bytes,bytes)"))
    // "0x" + first four bytes = 10 chars
    .slice(0, 10);

export async function verifyEip1271Signature(
    eip1271ContractAddress: string,
    provider: ethers.providers.Provider,
    message: string,
    signature: string,
) {
    const contract = new ethers.Contract(eip1271ContractAddress, EIP_1271_ABI, provider);
    const encodedMessage = ethers.utils.toUtf8Bytes(message);
    const result = await contract.isValidSignature(encodedMessage, signature);
    return result === MAGIC_VALUE;
}

EOA Signed Messages

You can also sign a message with the user’s externally owned account (EOA). In some cases this is recommended if you are verifying messages off chain so that you don’t need to build out support for EIP1721 signature verification as well.

Example

import { ethers } from "ethers";
import SimpleCryptoSdk from "@simplecrypto/wallet-sdk";

const simpleCrypto = new SimpleCryptoSdk();

const message = "Hello World!";

(async () => {
    /**
     * Prompt user to sign in if they aren't already.
     * This will do nothing if the user is already signed in
     */ 
    await simpleCrypto.activate();

    const signature = await simpleCrypto.signMessageFromEoa(message);
})();
Last updated on February 4, 2023