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

Passkeys #499

Merged
merged 25 commits into from
Jun 14, 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
2 changes: 2 additions & 0 deletions .github/styles/config/vocabularies/default/accept.txt
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ EIP
EOA
EOAs
ERC
ESLint
EURe
EVM
EdgeEVM
Expand Down Expand Up @@ -241,6 +242,7 @@ npm
onboarding
onchain
pluggable
precompile(?s)
saltNonce
textWrap
trace_block
Expand Down
Binary file added assets/next.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/safe-passkeys-app-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/safe-passkeys-app-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions pages/home/4337-guides/permissionless-detailed.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@ This guide focuses on how user operations are built and what happens under the h

Install [viem](https://npmjs.com/viem) and [permissionless](https://npmjs.com/permissionless) dependencies by running the following command:

{/* <!-- vale off --> */}

```bash
pnpm install viem permissionless
```

{/* <!-- vale on --> */}

## Steps

<Steps>
Expand Down
17 changes: 16 additions & 1 deletion pages/home/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,20 @@
"4337-overview": "Overview",
"4337-safe": "Safe and ERC-4337",
"4337-supported-networks": "Supported Networks",
"4337-guides": "Guides"
"4337-guides": "Guides",
"-- Passkeys": {
"type": "separator",
"title": "Passkeys"
},
"passkeys-overview": "Overview",
"passkeys-safe": "Safe and Passkeys",
"passkeys-supported-networks": "Supported Networks",
"passkeys-guides": "Guides",
"passkeys-tutorials": "Tutorials",
"passkeys-faqs": {
"title": "FAQs",
"theme": {
"toc": false
}
}
}
26 changes: 26 additions & 0 deletions pages/home/passkeys-faqs.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Passkeys FAQs

## Which devices support passkeys?

Apple and Android devices both support passkeys and syncing. If a device uses [Cross-device authentication (CDA)](https://passkeys.dev/docs/reference/terms/#cross-device-authentication-cda), its passkeys will be portable to other devices. You can read more about device support [here](https://passkeys.dev/device-support/#matrix).

## How can I sync a passkey across devices?

Passkeys can be synced across devices through secure cloud services provided by device manufacturers and operating system vendors. Most platforms support passkey syncing natively and automatically, meaning that a passkey used to authenticate a user on one device will also be available on this user’s other devices, for example, via Apple ID or Google Account.

Only device-bound passkeys (a specific type of passkeys) created on Windows will be available solely on the device they were created on.

## How can I recover an account with passkeys?

Account recovery with passkeys typically involves using your synced devices or recovery methods provided by the cloud service where the passkeys are stored. The device manufacturer usually does this automatically, but services like password managers can also be used to store and access passkeys securely, independently from the manufacturer.

## Do passkeys and web3 use the same encryption schemes?

Passkeys and web3 share common principles of encryption but may use different schemes depending on the specific implementation. Generally, passkeys use public key cryptography, which is also a foundational element in many web3 protocols:

- Ethereum primarily uses the `secp256k1` elliptic curve to generate public-private key pairs. This curve was chosen for its properties that offer a good balance of security and performance in blockchain applications.
- Passkeys can support `secp256r1` (also known as `P-256`). `P-256` is commonly used for its strong security properties and compatibility with web technologies.

## Can I use passkeys with ERC-4337?

Passkeys can be integrated with ERC-4337, providing enhanced security and user experience in managing web3 accounts. See [our tutorial to build your own implementation](https://docs.safe.global/home/passkeys-tutorials/safe-passkeys-tutorial), or check out [4337 support contract for passkeys](https://github.com/safe-global/safe-modules/tree/main/modules/passkey/contracts/4337) for more information.
3 changes: 3 additions & 0 deletions pages/home/passkeys-guides/_meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"safe-sdk": "Passkeys with the Safe{Core} SDK"
}
225 changes: 225 additions & 0 deletions pages/home/passkeys-guides/safe-sdk.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
import { Steps, Tabs } from 'nextra/components'

# 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

{/* <!-- vale off --> */}

```bash
yarn add @safe-global/relay-kit
yarn add @safe-global/protocol-kit
```

{/* <!-- vale on --> */}

## Steps

<Steps>

### Imports

Here are all the necessary imports for the script we implement in this guide.

{/* <!-- vale off --> */}

```typescript
import { Safe4337Pack } from '@safe-global/relay-kit'
import { PasskeyArgType } from '@safe-global/protocol-kit'
```

{/* <!-- vale on --> */}

### Create a passkey

Firstly, we need to generate a passkey credential using the WebAuthn API in a supporting browser environment.

{/* <!-- vale off --> */}

```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',
},
})
```

{/* <!-- vale on --> */}

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.

{/* <!-- vale off --> */}

```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
}
```

{/* <!-- vale on --> */}

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.

{/* <!-- vale off --> */}

```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
}
})
```

{/* <!-- vale on --> */}

### 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.

{/* <!-- vale off --> */}

```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 })
```

{/* <!-- vale on --> */}

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.

{/* <!-- vale off --> */}

```typescript
const signedSafeOperation = await safe4337Pack.signSafeOperation(
safeTransaction
)
```

{/* <!-- vale on --> */}

### 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.

{/* <!-- vale off --> */}

```typescript
const userOperationHash = await safe4337Pack.executeTransaction({
executable: signedSafeOperation
})
```

{/* <!-- vale on --> */}

### Check the transaction status

To check the transaction status, we can use the `getTransactionReceipt()` method, which returns the transaction receipt after it's executed.

{/* <!-- vale off --> */}

```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
)
}
```

{/* <!-- vale on --> */}

In addition, we can use the `getUserOperationByHash()` method with the returned hash to retrieve the user operation object we sent to the bundler.

{/* <!-- vale off --> */}

```typescript
const userOperationPayload = await safe4337Pack.getUserOperationByHash(
userOperationHash
)
```

{/* <!-- vale on --> */}

</Steps>

## 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)
53 changes: 53 additions & 0 deletions pages/home/passkeys-overview.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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={'Passkeys eliminate the need for users to store seed phrases securely. 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 streamline the authentication process by allowing users to sign in to accounts with a biometric sensor, pin, or gesture.'}
url=""
newTab={false}
/>
</Grid>
<Grid item xs={12} md={4}>
<CustomCard
title={'Resilience'}
description={'Passkeys are stored in a device secure element, ensuring they can not be easily accessible to the internet. They can also be synced across multiple devices.'}
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

- [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)
Loading
Loading