Verifying signatures and cosigners
Providing multi-signature verification support for dApps
A good example can be found in this repo: off-chain signatures
Signing
All the different Starknet libs give you the ability to sign messages from an account. Let's take starknet.js or starknet-react for example.
Most of the time, you will try to make your users sign a SNIP-12 typed data message.
With starknet.js, you would do something like this:
const account = new Account(provider, address, pk);
const typedData: TypedData = {
// your typed data
}
const sig = await account.signMessage(typedData);
With starknet-react's hook, it will look like that:
// declare hook
const { signTypedDataAsync } = useSignTypedData({
// your typedData
})
// use hook
const doSomething = async () => {
// rest of the code
const signature = await signTypedDataAsync({
// your typedData
})
// do something with sig
}
You'll notice that a signature is an array of several numbers. If the account is a standard account, then the sig length will be 5 and if it's a smart-account, it will be 9 (because you have both the account and the guardian signatures). The members of the sig array are explained below.
Verifying signatures
There are several ways of verifying a signature, on-chain or off-chain. Most of the time, you will use one of the on-chain methods. The starknet.js doc has an example of off-chain verification.
On-chain verification method 1 - Calling the contract
Argent account signatures can be verified by calling the isValidSignature
or is_valid_signature
method of the account contract:
const contractAccount = new Contract(abi, accountAddress, provider);
const msgHash = typedData.getMessageHash(data, accountAddress);
await contractAccount.isValidSignature(msgHash, [signature.r, signature.s])
Be aware that most of Ready accounts will return more that one signature that should all be verified. See below.
Signature r and s are the the 4th and 5th member of the sig array so signature[3]
and signature[4]
.
On-chain verification method 2 - with Typed data
Most of the time, you will get your users to sign a typed data message following the SNIP-12 standard. There is an easy way to verify such a signature.
const provider = new RpcProvider({
nodeUrl: rpcUrl
});
const isValidSig = await provider.verifyMessageInStarknet(
typedData, // typed data json your user signed
signature, // raw user sig, no need to filter r and s
account // user address
);
This method also accepts message hash instead of the full json.
Guardians and co-signers
A guardian is a trusted party, added by the user, that acts as a cosigner/co-validator for the user's account when carrying out typical wallet operations or for recovery purposes.
For most of Ready's products e.g Ready, Web Wallet, Smart Accounts in Ready Wallet etc, the guardian is usually Ready's backend.
In the next section, let's take a look at how you can verify multi-signatures for accounts with an active guardian.
Verifying multi-signatures
From a dApp's end, explicit support has to be provided for verifying multi-signatures, or account owners with guardians will be unable to sign transactions.
The signature is verified by calling the isValidSignature
or is_valid_signature()
method. If the user has a guardian, the signature returned by the wallet will be longer and include more data.

0:
number of signers (i.e. 2 in this example)
1:
type of signer 1
2:
pubkey 1
3:
r1
4:
s1
5:
type of signer 2 (guardian)
6:
pubkey 2 (guardian)
7:
r2 (guardian)
8:
s2 (guardian)
const contractAccount = new Contract(abi, accountAddress, provider);
const msgHash = typedData.getMessageHash(data, accountAddress);
await contractAccount.isValidSignature(msgHash, [signature1.r, signature1.s, signature2.r, signature2.s])
Last updated
Was this helpful?