Wallet Signatures
An AuthSig
is a wallet signature obtained from a user. Wallet signatures are required to communicate with the Lit Nodes and authorize requests.
Format of an AuthSig
You can use any signature compliant with EIP-4361, also known as Sign in with Ethereum (SIWE), for the AuthSig
. However, the signature must be presented in an AuthSig
object formatted like so:
{
"sig": "0x18720b54cf0d29d618a90793d5e76f4838f04b559b02f1f01568d8e81c26ae9536e11bb90ad311b79a5bc56149b14103038e5e03fee83931a146d93d150eb0f61c",
"derivedVia": "web3.eth.personal.sign",
"signedMessage": "localhost wants you to sign in with your Ethereum account:\n0x1cD4147AF045AdCADe6eAC4883b9310FD286d95a\n\nThis is a test statement. You can put anything you want here.\n\nURI: https://localhost/login\nVersion: 1\nChain ID: 1\nNonce: gzdlw7mR57zMcGFzz\nIssued At: 2022-04-15T22:58:44.754Z",
"address": "0x1cD4147AF045AdCADe6eAC4883b9310FD286d95a"
}
In the AuthSig
data structure:
sig
is the signature produced by signing thesignedMessage
derivedVia
is the method used to derive the signature (e.g., "web3.eth.personal.sign")signedMessage
is the original message that was signedaddress
is the public key address that was used to create the signature
You can refer to the AuthSig
type definition in the Lit JS SDK V2.
Obtaining an AuthSig
in the browser
Using checkAndSignAuthMessage
The Lit SDK checkAndSignAuthMessage()
function provides a convenient way to obtain an AuthSig
from an externally-owned account in a browser environment.
import { checkAndSignAuthMessage } from '@lit-protocol/lit-node-client';
const authSig = await checkAndSignAuthMessage({
chain: "ethereum",
});
When called, checkAndSignAuthMessage
triggers a wallet selection popup in the user's browser. The user is then asked to sign a message, confirming ownership of their crypto address. The signature of the signed message is returned as the authSig
variable.
The function also stores the AuthSig
in local storage, removing the need for the user to sign the message again. However, if the signature expires or becomes too old, the user may be prompted to sign the message again.
checkAndSignAuthMessage
checks the currently selected chain in the user's wallet. If user's wallet supports it, the function sends a request to the user's wallet to change to the chain specified in the checkAndSignAuthMessage()
function call. This ensures that the user is interacting with the correct blockchain network.
Using signAndSaveAuthMessage
If you prefer to implement your own wallet selection interface, you can call the signAndSaveAuthMessage()
function, which offers more customization. To use this function, pass in an instance of an ethers.js Web3Provider
object, the wallet address, the chain ID, and the signature expiration time.
import { ethConnect } from '@lit-protocol/auth-browser';
const authSig = await ethConnect.signAndSaveAuthMessage({
web3: web3Provider,
account: walletAddress,
chainId: 1,
expiration: new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString(),
});
Be sure to import cosmosConnect
and solConnect
for Cosmos and Solana respectively.
Using EIP-1271 for Account Abstraction
In general, smart contracts can't produce an AuthSig
since they don't possess a private key. However, you can generate an AuthSig
for smart contracts using EIP-1271, a standard for verifying signatures when the account is a smart contract.
Following the same data structure as above, you can format your smart contract AuthSig
like so:
sig
is the actual hex-encoded signaturederivedVia
must be "EIP1271" to inform the nodes that thisAuthSig
is for smart contractssignedMessage
is any string that you want to pass to theisValidSignature(bytes32 _hash, bytes memory _signature)
as the_hash
argument.address
is the address of the smart contract
You can present the smart contract AuthSig
object to the Lit Nodes just like any other AuthSig
.
Check out this React project for an example of how to generate and use a smart contract AuthSig
.
The smart contract must implement the isValidSignature(bytes32 _hash, bytes memory _signature)
function since the Lit Nodes will call this function to validate the AuthSig
. Refer to the EIP-1271 docs to understand the isValidSignature
function. The current behavior is having an issue as of 16/11/23. This is because of a bug detailed below in the consideration.
Current Behavior with signedMessage
The current implementation involves a specific handling of the signedMessage
parameter that may not be immediately apparent. This has led to some confusion and difficulty in implementation. The key points are:
Encoding of signedMessage
: The signedMessage
should be a string
without modifications. It's not meant to be in hexadecimal format or any other encoding.
Backend Processing:
- The backend processes the
signedMessage
by first converting it tobytes
, then encoding it in hex without the0x
prefix. - This
hex
string is then passed to thekeccak256
hash function.
The issue arises because keccak256
interprets this as a string
, not as hexadecimal bytes.
Solution to signedMessage Issue
To correctly process the signedMessage
, follow these steps:
Correct Encoding:
Convert the signedMessage
to bytes
.
Then convert these bytes
to a hex
string, then remove 0x
prefix and convert back to bytes
.
Finally, apply keccak256
to these bytes.
Example Implementation:
const message = "example message";
const hexMessage = toBytes(toHex(toBytes(message)).slice(2).toLowerCase());
const hashBytes = keccak256(hexMessage);
// ERC-1271 Signing Logic
const signature = ....
authSig = {
sig: signature, // 0x00
derivedVia: "EIP1271",
signedMessage: "test message",
address: "0x..." // abstracted wallet address
};
Clearing Local Storage
If you want to clear the AuthSig
stored in local storage, you can call the disconnectWeb3
method.
Obtaining an AuthSig
on the server-side
If you want to obtain an AuthSig
on the server-side, you can instantiate an ethers.Signer
to sign a SIWE message, which will produce a signature that can be used in an AuthSig
object.
const LitJsSdk = require('@lit-protocol/lit-node-client-nodejs');
const { ethers } = require("ethers");
const siwe = require('siwe');
async function main() {
// Initialize LitNodeClient
const litNodeClient = new LitJsSdk.LitNodeClientNodeJs();
await litNodeClient.connect();
// Initialize the signer
const wallet = new ethers.Wallet('<Your private key>');
const address = ethers.utils.getAddress(await wallet.getAddress());
// Craft the SIWE message
const domain = 'localhost';
const origin = 'https://localhost/login';
const statement =
'This is a test statement. You can put anything you want here.';
const siweMessage = new siwe.SiweMessage({
domain,
address: address,
statement,
uri: origin,
version: '1',
chainId: '1',
});
const messageToSign = siweMessage.prepareMessage();
// Sign the message and format the authSig
const signature = await wallet.signMessage(messageToSign);
const authSig = {
sig: signature,
derivedVia: 'web3.eth.personal.sign',
signedMessage: messageToSign,
address: address,
};
console.log(authSig);
}
main();