-
Notifications
You must be signed in to change notification settings - Fork 75
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: Add Passkeys section, overview and guide (#498)
* Add Passkeys section * Add Passkeys overview * Add passkeys guide * Create missing pages
- Loading branch information
1 parent
3a999f6
commit 7a2ebe4
Showing
7 changed files
with
254 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# FAQ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"safe-sdk": "Passkeys with the Safe{Core} SDK" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
# Passkeys with the Safe\{Core\} SDK | ||
|
||
This guide will teach you how to create and execute multiple Safe transactions grouped in a batch from a Safe Smart Account that uses a passkey as an owner. To have a good user experience, we will use an [ERC-4337 compatible Safe](../4337-guides/safe-sdk.mdx) with sponsored transactions using Pimlico infrastructure. During this guide, we will create a new passkey, add it to the Safe as an owner, and use it to sign the user operations. | ||
|
||
This guide uses [Pimlico](https://pimlico.io) as the service provider, but any other provider compatible with the ERC-4337 can be used. | ||
|
||
## Prerequisites | ||
|
||
- [Node.js and npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm). | ||
- A [Pimlico account](https://dashboard.pimlico.io) and an API key. | ||
- Passkeys feature is available only in [secure contexts](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts) (HTTPS), in some or all [supporting browsers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API#browser_compatibility). | ||
|
||
## Install dependencies | ||
|
||
```bash | ||
yarn add @safe-global/relay-kit | ||
yarn add @safe-global/protocol-kit | ||
``` | ||
|
||
## Steps | ||
|
||
### Imports | ||
|
||
Here are all the necessary imports for the script we implement in this guide. | ||
|
||
```typescript | ||
import { Safe4337Pack } from '@safe-global/relay-kit' | ||
import { PasskeyArgType } from '@safe-global/protocol-kit' | ||
``` | ||
|
||
### Create a passkey | ||
|
||
Firstly, we need to generate a passkey credential using the WebAuthn API in a supporting browser environment. | ||
|
||
```typescript | ||
const RP_NAME = 'Safe Smart Account' | ||
const USER_DISPLAY_NAME = 'User display name' | ||
const USER_NAME = 'User name' | ||
|
||
const passkeyCredential = await navigator.credentials.create({ | ||
publicKey: { | ||
pubKeyCredParams: [ | ||
{ | ||
alg: -7, | ||
type: 'public-key' | ||
} | ||
], | ||
challenge: crypto.getRandomValues(new Uint8Array(32)), | ||
rp: { | ||
name: RP_NAME | ||
}, | ||
user: { | ||
displayName: USER_DISPLAY_NAME, | ||
id: crypto.getRandomValues(new Uint8Array(32)), | ||
name: USER_NAME | ||
}, | ||
timeout: 60_000, | ||
attestation: 'none', | ||
}, | ||
}) | ||
``` | ||
|
||
After generating the `passkeyCredential` object, we need to create a new object with the `PasskeyArgType` type that will contain the `rawId` and the `publicKey` information. | ||
|
||
```typescript | ||
const passkeyObject = passkeyCredential as PublicKeyCredential | ||
const attestationResponse = | ||
passkeyObject.response as AuthenticatorAttestationResponse | ||
|
||
const rawId = passkeyObject.rawId | ||
const publicKey = attestationResponse.getPublicKey() | ||
|
||
const passkey: PasskeyArgType = { | ||
rawId, | ||
publicKey | ||
} | ||
``` | ||
|
||
At this point, it's critical to securely store the information in the `passkey` object in a persistent service. Losing access to this data will result in the user being unable to access their passkey and, therefore, their Safe Smart Account. | ||
|
||
### Initialize the Safe4337Pack | ||
|
||
Once the passkey is created and secured, we can use the `Safe4337Pack` class exported from the Relay Kit to create, sign, and submit Safe user operations. | ||
|
||
To instantiate this class, the static `init()` method allows connecting existing Safe accounts (as long as they have the `Safe4337Module` enabled) or setting a custom configuration to deploy a new Safe account at the time where the first Safe transaction is submitted. For this guide, we will deploy a new Safe account, configure the paymaster options to get all the transactions sponsored and connect our passkey to add it as the only owner. | ||
|
||
```typescript | ||
const PIMLICO_API_KEY = // ... | ||
const RPC_URL = 'https://rpc.ankr.com/eth_sepolia' | ||
|
||
const safe4337Pack = await Safe4337Pack.init({ | ||
provider: RPC_URL, | ||
rpcUrl: RPC_URL, | ||
signer: passkey, | ||
bundlerUrl: `https://api.pimlico.io/v1/sepolia/rpc?apikey=${PIMLICO_API_KEY}`, | ||
paymasterOptions: { | ||
isSponsored: true, | ||
paymasterUrl: `https://api.pimlico.io/v2/sepolia/rpc?apikey=${PIMLICO_API_KEY}`, | ||
paymasterAddress: '0x...', | ||
paymasterTokenAddress: '0x...', | ||
sponsorshipPolicyId // Optional value to set the sponsorship policy id from Pimlico | ||
}, | ||
options: { | ||
owners: [], | ||
threshold: 1 | ||
} | ||
}) | ||
``` | ||
|
||
### Create a user operation | ||
|
||
To create a Safe user operation, use the `createTransaction()` method, which takes the array of transactions to execute and returns a `SafeOperation` object. | ||
|
||
```typescript | ||
// Define the transactions to execute | ||
const transaction1 = { to, data, value } | ||
const transaction2 = { to, data, value } | ||
|
||
// Build the transaction array | ||
const transactions = [transaction1, transaction2] | ||
|
||
// Create the SafeOperation with all the transactions | ||
const safeOperation = await safe4337Pack.createTransaction({ transactions }) | ||
``` | ||
|
||
The `safeOperation` object has the `data` and `signatures` properties, which contain all the information about the transaction batch and the signatures of the Safe owners, respectively. | ||
|
||
### Sign a user operation | ||
|
||
Before sending the user operation to the bundler, the `safeOperation` object must be signed with the connected passkey. The user is now requested to authenticate with the associated device and sign in with a biometric sensor, PIN, or gesture. | ||
|
||
The `signSafeOperation()` method, which receives a `SafeOperation` object, generates a signature that will be checked when the `Safe4337Module` validates the user operation. | ||
|
||
```typescript | ||
const signedSafeOperation = await safe4337Pack.signSafeOperation( | ||
safeTransaction | ||
) | ||
``` | ||
|
||
### Submit the user operation | ||
|
||
Once the `safeOperation` object is signed with the passkey, we can call the `executeTransaction()` method to submit the user operation to the bundler. | ||
|
||
```typescript | ||
const userOperationHash = await safe4337Pack.executeTransaction({ | ||
executable: signedSafeOperation | ||
}) | ||
``` | ||
|
||
### Check the transaction status | ||
|
||
To check the transaction status, we can use the `getTransactionReceipt()` method, which returns the transaction receipt after it's executed. | ||
|
||
```typescript | ||
let userOperationReceipt = null | ||
|
||
while (!userOperationReceipt) { | ||
// Wait 2 seconds before checking the status again | ||
await new Promise((resolve) => setTimeout(resolve, 2000)) | ||
userOperationReceipt = await safe4337Pack.getUserOperationReceipt( | ||
userOperationHash | ||
) | ||
} | ||
``` | ||
|
||
In addition, we can use the `getUserOperationByHash()` method with the returned hash to retrieve the user operation object we sent to the bundler. | ||
|
||
```typescript | ||
const userOperationPayload = await safe4337Pack.getUserOperationByHash( | ||
userOperationHash | ||
) | ||
``` | ||
|
||
## Recap and further reading | ||
|
||
After following this guide, we are able to deploy a new ERC-4337 compatible Safe Smart Account setup with a passkey and create, sign, and execute Safe transactions signing them with the passkey. Learn more about passkeys and how Safe supports them in detail by following these links: | ||
|
||
- [Safe\{Core\} SDK demo](https://github.com/5afe/passkey-sdk-demo-dapp) | ||
- [Safe Passkeys contracts](https://github.com/safe-global/safe-modules/tree/main/modules/passkey) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { Grid } from '@mui/material' | ||
import CustomCard from '../../components/CustomCard' | ||
|
||
# What are passkeys? | ||
|
||
Passkeys are a standard authentication method designed to avoid using traditional passwords, providing a more secure and user-friendly experience. | ||
|
||
Passkeys are based on public and private key pairs to secure user authentication. The public key is stored on the server side, while the private key is secured in the user's device. The user is authenticated by proving ownership of the private key, usually with biometric sensors, without extracting it from the device at any time. This method ensures that sensitive information remains protected and reduces the risk of credential theft. | ||
|
||
## Why do we need passkeys? | ||
|
||
Passkeys offer significant security improvements over traditional passwords. In the context of web3, where secure key management is paramount, passkeys provide an efficient alternative to seed phrases, which are often considered both a security liability and a subpar user experience. | ||
|
||
<Grid | ||
container | ||
spacing={2} | ||
display='flex' | ||
alignContent='flex-start' | ||
mt={3} | ||
> | ||
<Grid item xs={12} md={4}> | ||
<CustomCard | ||
title={'Key Management'} | ||
description={'Seed phrases were already an improvement compared to storing plain-text private keys. It must be stored securely - offline or on a secure device - which can be challenging for most users. Passkeys, conversely, eliminate the need for users to remember complex seed phrases. They ensure the user \'s private key remains secure even if a server is compromised.'} | ||
url="" | ||
newTab={false} | ||
/> | ||
</Grid> | ||
<Grid item xs={12} md={4}> | ||
<CustomCard | ||
title={'User Experience'} | ||
description={'Passkeys allow users to sign in to accounts with a biometric sensor, PIN, or gesture, streamlining the authentication process. Since users don\'t have to type anything - neither passwords nor usernames - the authentication flow becomes seamless, with little to no interruption in the user journey, often leading to better conversion and increased usage of the service implementing it.'} | ||
url="" | ||
newTab={false} | ||
/> | ||
</Grid> | ||
<Grid item xs={12} md={4}> | ||
<CustomCard | ||
title={'Resilience'} | ||
description={'Passkeys are stored in a device secure element, which ensures that the data they store can not be easily accessible to the internet or the rest of the outside world. However, since passkeys can be synced across multiple devices, the security and access to a given service are not tied to a specific smartphone or computer.'} | ||
url="" | ||
newTab={false} | ||
/> | ||
</Grid> | ||
</Grid> | ||
|
||
Safe offers the capability to sign into your wallet using passkeys by implementing a dedicated module that verifies the integrity of the key provided. | ||
|
||
## Further reading | ||
|
||
- [Passkeys.io demo](https://www.passkeys.io) | ||
- [The official W3C standard](https://www.w3.org/TR/webauthn) | ||
- [WebAuthn API specification](https://webauthn.wtf/how-it-works/basics) | ||
- [Passkeys 101 by FIDO Alliance](https://fidoalliance.org/passkeys) (the original authors of the specification) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Supported Networks |