Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Signature Verification guide #21

Merged
merged 1 commit into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions docs/pages/guides/signature-verification.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Signature Verification

The are important details to verifying smart contract wallet signatures. The smart contract itself
cannot produce a signature. Instead, the contract has a function
```solidity
function isValidSignature(bytes32 _hash, bytes memory _signature) returns (bytes4 magicValue);
```
which can be called to determine if signature should be considered valid (defined in [EIP-1271](https://eips.ethereum.org/EIPS/eip-1271)).

In the case of Smart Wallet, a signature is considered valid if it was signed by a current signer (aka "owner") of the Smart Wallet.
For example, a user can sign a message with their passkey, and when `isValidSignature` is called on their Smart Wallet,
it would return that the signature is valid because their passkey is an owner.

There is also an additional complexity: users receive their Smart Wallet address immediately upon passkey registration,
and are able to begin signing for their Smart Wallet,
but their Smart Wallet is not deployed on a chain until the first transaction on that chain.

[ERC-6492](https://eips.ethereum.org/EIPS/eip-6492) describes
> With the rising popularity of account abstraction, we often find that the best user experience
for contract wallets is to defer contract deployment until the first user transaction,
therefore not burdening the user with an additional deploy step before they can use their account.
However, at the same time, many dApps expect signatures, not only for interactions, but also just for logging in.

So the challenge is, how do we verify signatures in a way that works for both deployed and undeployed Smart Wallets?
ERC-6492 has a solution for this, which Smart Wallet has adopted.
We won't go unto all the details here, read the ERC linked above, if you're looking for that.
Below we cover the minimum work needed to support on and off chain signature validation for Smart Wallet.

## Offchain
For purely offchain signature verification, simply ensure you are using
a ERC-6492-compliant signature verification library, and it all should *just work*.
We recommend [Viem's `verifyMessage`](https://viem.sh/docs/actions/public/verifyMessage#verifymessage).

## Onchain
For signatures that will be used onchain, such as with [Permit2](https://github.com/Uniswap/permit2)
or [Seaport](https://github.com/ProjectOpenSea/seaport) developers will need to inspect the signature offchain
and remove unneeded ERC-6492 data, if it is present.

:::code-group
```ts twoslash [example.ts]
import {parseERC6492Signature} from "./utils"

const signatureForDeployedSmartWallet = "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000001517cb5e6438c7a32ba9bbc6766c6705c322c4f51bf28037af8f932e30e11c92e3b6b0080d3c2626ef4c6b0415ab036b47ca75ab9905753d7d3519fe09a56c98b0000000000000000000000000000000000000000000000000000000000000025f198086b2db17256731bc456673b96bcef23f51d1fbacdd7c4379ef65465572f0500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008a7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22507a435038462d5252384b387557513241574d7650677032554f776362725774564d6e656856336f4a6530222c226f726967696e223a2268747470733a2f2f6b6579732e636f696e626173652e636f6d222c2263726f73734f726967696e223a66616c73657d00000000000000000000000000000000000000000000";
const signatureForUndeployedSmartWallet = "0x0000000000000000000000000ba5ed0c6aa8c49038f819e587e2633c4a9f428a0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000e43ffba36f0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004093a5e054721e22912d422ba5fca7ac395246e7f466b72c078bb987b71c5bf8ddf92c381340e22a301b823549d04765cd155a9b52206044121881395ab82d72c80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000001946068e132450c0189690dc29c0eb20a504e6c5a02c42ebc647c83f4e4a2e61e786bea55ea4695b4b72ef88ea133647a07ef02f2e9c0bfd39457a7eb18b519bd0000000000000000000000000000000000000000000000000000000000000025f198086b2db17256731bc456673b96bcef23f51d1fbacdd7c4379ef65465572f050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f77b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a2234446462577159355831784c6b77735133584e31754736754a526f5a3731354a4643397767643332413767222c226f726967696e223a2268747470733a2f2f6b6579732e636f696e626173652e636f6d222c2263726f73734f726967696e223a66616c73652c226f746865725f6b6579735f63616e5f62655f61646465645f68657265223a22646f206e6f7420636f6d7061726520636c69656e74446174614a534f4e20616761696e737420612074656d706c6174652e205365652068747470733a2f2f676f6f2e676c2f796162506578227d0000000000000000006492649264926492649264926492649264926492649264926492649264926492";

parseERC6492Signature(signatureForDeployedSmartWallet)
// @log: {
// @log: sigToValidate: 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000001517cb5e6438c7a32ba9bbc6766c6705c322c4f51bf28037af8f932e30e11c92e3b6b0080d3c2626ef4c6b0415ab036b47ca75ab9905753d7d3519fe09a56c98b0000000000000000000000000000000000000000000000000000000000000025f198086b2db17256731bc456673b96bcef23f51d1fbacdd7c4379ef65465572f0500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008a7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22507a435038462d5252384b387557513241574d7650677032554f776362725774564d6e656856336f4a6530222c226f726967696e223a2268747470733a2f2f6b6579732e636f696e626173652e636f6d222c2263726f73734f726967696e223a66616c73657d00000000000000000000000000000000000000000000
// @log: }

parseERC6492Signature(signatureForUndeployedSmartWallet)
// @log: {
// @log: sigToValidate: 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000001946068e132450c0189690dc29c0eb20a504e6c5a02c42ebc647c83f4e4a2e61e786bea55ea4695b4b72ef88ea133647a07ef02f2e9c0bfd39457a7eb18b519bd0000000000000000000000000000000000000000000000000000000000000025f198086b2db17256731bc456673b96bcef23f51d1fbacdd7c4379ef65465572f050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f77b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a2234446462577159355831784c6b77735133584e31754736754a526f5a3731354a4643397767643332413767222c226f726967696e223a2268747470733a2f2f6b6579732e636f696e626173652e636f6d222c2263726f73734f726967696e223a66616c73652c226f746865725f6b6579735f63616e5f62655f61646465645f68657265223a22646f206e6f7420636f6d7061726520636c69656e74446174614a534f4e20616761696e737420612074656d706c6174652e205365652068747470733a2f2f676f6f2e676c2f796162506578227d000000000000000000
// @log: factory: 0x0BA5ED0c6AA8c49038F819E587E2633c4A9F428a
// @log: factoryCalldata: 0x3ffba36f0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004093a5e054721e22912d422ba5fca7ac395246e7f466b72c078bb987b71c5bf8ddf92c381340e22a301b823549d04765cd155a9b52206044121881395ab82d72c8
// @log: }

// use `ParseERC6492ReturnType.sigToValidate` for onchain validation.


```
```ts twoslash [utils.ts] filename="utils.ts"
import {type Hex, type Address, decodeAbiParameters} from 'viem'

export function isERC6492Signature(signature: Hex) : boolean {
const ERC6492_DETECTION_SUFFIX = "6492649264926492649264926492649264926492649264926492649264926492";
return signature.slice(signature.length - 64, signature.length) == ERC6492_DETECTION_SUFFIX;
}

export type ParseERC6492ReturnType = {
sigToValidate: Hex;
factory?: Address;
factoryCalldata?: Hex;
};

// NOTE: Works with non-ERC-6492 siganture
// parseERC6492Signature(eoaSignature).sigToValidate == eoaSignature
// parseERC6492Signature(deployedSmartAccountSignature).sigToValidate == deployedSmartAccountSignature
export function parseERC6492Signature(signature: Hex): ParseERC6492ReturnType {
if (!isERC6492Signature(signature)) return { sigToValidate: signature };

const [factory, factoryCalldata, sigToValidate] = decodeAbiParameters(
[
{ type: "address" },
{ type: "bytes" },
{ type: "bytes" },
],
signature,
);
return { sigToValidate, factory, factoryCalldata };
}
```
:::
4 changes: 4 additions & 0 deletions vocs.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ export default defineConfig({
text: 'Update an existing app',
link: '/guides/update-existing-app'
},
{
text: "Signature Verification",
link: '/guides/signature-verification'
},
{
text: 'Batch transactions',
link: '/guides/batch-transactions',
Expand Down
Loading