Sign Messages with Custodial Wallets
Signing and verifying messages for smart accounts is different than with EOAs. There are a few reasons why:
-
With an EOA, the address is effectively the public key of the private key used for signing. Therefore, verifying a EOA signature is as simple as recovering the signature and compare the recovered public key with the address.
- With a smart account, the address is the address of a smart contract that has no cryptographic link to the signing private key. Therefore, you must use ERC-1271 to validate the message.
-
With an EOA, you don't have to deploy the account. It just exists.
- Since smart accounts need to be deployed, it may not be clear how you can validate messages against a smart account not yet deployed.
Signing messages
To sign messages:
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.openfort.io/api-keys
const Openfort = require('@openfort/openfort-node').default;
const openfort = new Openfort(YOUR_SECRET_KEY);
const _domain = {
name: "Openfort",
version: "0.5",
chainId: 13337,
verifyingContract: "0x9b5AB198e042fCF795E4a0Fa4269764A4E8037D2",
};
const types = {
Mail: [
{ name: "from", type: "Person" },
{ name: "to", type: "Person" },
{ name: "content", type: "string" },
],
Person: [
{ name: "name", type: "string" },
{ name: "wallet", type: "address" },
],
};
const mail = {
from: {
name: "Alice",
wallet: "0x2111111111111111111111111111111111111111",
},
to: {
name: "Bob",
wallet: "0x3111111111111111111111111111111111111111",
},
content: "Hello!",
};
const structHash = ethers.utils._TypedDataEncoder.hash(domain, types, mail);
const signature = await openfort.accounts.signPayload({
id: "acc_4194ad24-c818-4e5c-b003-9cc2aa7df53b",
hash: structHash,
domain: domain
value: mail,
types: types,
});
Validating signatures
You can validate signatures with ERC-1271. Here's an example with ethers:
const ethers = require("ethers");
async function verifySignature(hash, signature, address) {
let provider = new ethers.providers.JsonRpcProvider(providerUrl);
const ABI = {
inputs: [
{
internalType: "bytes32",
name: "_hash",
type: "bytes32",
},
{
internalType: "bytes",
name: "_signature",
type: "bytes",
},
],
name: "isValidSignature",
outputs: [
{
internalType: "bytes4",
name: "",
type: "bytes4",
},
],
stateMutability: "view",
type: "function",
};
const iface = new ethers.utils.Interface(ABI);
const encodedDataDeposit = iface.encodeFunctionData("isValidSignature", [
hash,
signature,
]);
const tx = {
to: address,
data: encodedDataDeposit,
};
return await provider.call(tx);
}