owned this note
owned this note
Published
Linked with GitHub
# Multisigned
Multisigned is an open source digital locker for Ethereum seed phrases.
## Motivation
The self custodial Ethereum wallets' user experience is bad and hasn't notably improved over the last few years. While this is not exactly a problem for crypto currency applications, for decentralized social media apps this is a death sentence as our web2 counter parts are alive and innovating players who keep shipping the best in class user experiences. For essentially all of crypto-related decentralized social media, over the course of the last 2-3 years, this has meant TAM-constraint growth and zero sum customer acquisition.
The fact of the matter is: Crypto-based decentralized social media cannot grow with the Ethereum self custody wallets as a substrate. That is not even a failure of Ethereum wallet providers. The fault rather lies in the fact that building crypto-based decentralized social media and nation-state-secure crypto currencies have different goals.
Hence, we think it is urgently necessary for crypto-based decentralized social media to branch out into its own wallet vertical and to solve their problems with bespoke solutions.
In the document below we outline a concept to client-side-encrypt and store Ethereum seed phrases on open source servers to immensely improve the UX of crypto-based decentralized social media applications.
## User Experience
There are a few things to say about the user experience of using passkeys in a crypto-based decentralized social media application versus using a self custodial Ethereum wallet.
For the sake of this document, it is simply important to note that, for users, interacting with Passkeys doesn't appear as a risk to their funds. While there are a few sites that allow people to transact funds with Passkeys, generally we trust that users are intuitively aware of the domain separation Passkeys implement and hence do not associate a Passkey dialogue with a possibility of endangering their savings.
Passkeys obviously also come pre-installed on all major operating systems now. The PRF extension is [widely available](https://www.passkeyprf.com/). Meanwhile crypto currency wallets will continue to try to lock down users private keys and seed phrases.
### Passkeys to sign smart account transactions
The $1.4B Bybit hack famously happened because the victim signed a tampered payload in their browser. A years-long, wide-ranging discussion has already happened about the caveats of blind signing on hardware wallets.
Passkeys are not meant to be used to sign individual crypto currency transactions. They have primarily been designed to log users into website and app sessions. When we use Passkeys to sign individual transactions we are misappropriating the standard.
Passkey adoption to sign individual transactions will further accelerate the trend towards blind signing, forcing users to trust that the app's input to the Passkey dialog is valid and not secretly changed by a hacker.
Knowing what's being signed is important. Virtually all self custodial wallets have now implemented transaction simulations making signing safer. These integrations highlight how important it is for users to know what they're signing. By signing individual transactions with the Passkey log in dialog we're going back to blind signing.
## Safety Considerations and Limitations
It's important to highlight that we intend this protocol to be used for crypto-based decentralized social media applications. These usually allow users, for example, to delete prior statements. The potential to do irrevocable damage to a crypto currency account is obviously very high and so wallet providers do their utter most to lock down seed phrases. In our view, this potential to do irrevocable damage is much lower for crypto-based decentralized social media accounts. And in cases where the stakes are high, dapps can always choose to allow users to use self-custodial Ethereum wallets.
## Protocol
### Registration
1. The protocol uses envelope encryption.
2. When the user opens a website, sees and clicks a "Sign up" button, an operating-system-specific Passkey dialog opens asking the user to create a Passkey for the website. The user confirms.
3. With this dialogue, the user is registering a public private key pair with the multisigned server. In return for signing up they receive a 15 minute valid JWT session token. A salted PRF value is generated from the credential creation dialogue too. It's used as an input to generate an AES encryption key (KEK, key encryption key).
4. The user's browser now generates another AES encryption key that we call data encryption key (DEK). The seed value of this DEK is entirely random.
5. An Ethereum seed phrase is generated and wrapped with the DEK. We call this the vault.
6. The DEK itself is wrapped with the Passkey-PRF-derived KEK.
7. Using the JWT session token from step 3, the user's browser now sends the wrapped DEK and the vault to the server.
8. The server stores the vault in one table and the DEK in reference to the vault ID.
9. The Ethereum seed phrase is stored for the time of the browser or app's session in the user browser's local storage. Upon closing the browser tab or app, the Ethereum seed phrase is deleted from the device.
```mermaid
sequenceDiagram
actor User
participant Browser
participant OS as OS/Passkey Provider
participant Server
User->>Browser: Click "Sign up"
Browser->>Server: POST /register/begin
Note over Server: Generate lockboxId (UUID)<br/>Generate registration challenge
Server->>Browser: Return registration options<br/>(challenge, rpID, rpName, etc)
Browser->>OS: navigator.credentials.create()<br/>with PRF extension
OS->>User: Show Passkey dialog
User->>OS: Confirm creation
Note over OS: Generate public/private keypair<br/>Generate PRF salt output
OS->>Browser: Return credential response<br/>(credentialId, publicKey, PRF output)
Note over Browser: Derive KEK from PRF output
Note over Browser: Generate random DEK
Note over Browser: Generate Ethereum seed phrase
Note over Browser: Encrypt seed phrase with DEK<br/>(creates vault)
Note over Browser: Encrypt DEK with KEK<br/>(creates wrapped DEK)
Browser->>Server: POST /register/complete<br/>(credential response)
Note over Server: Verify registration response<br/>Create lockbox with lockboxId<br/>Store passkey credential
Server->>Browser: Return JWT token (15min)
Browser->>Server: PUT /lockbox<br/>(wrapped DEK + vault, with JWT)
Note over Server: Store encryptedData in lockbox
Server->>Browser: 200 OK
Note over Browser: Store plaintext seed phrase<br/>in session storage<br/>(deleted on close)
```
### Login
1. When the user opens a website and is prompted to sign a login challenge with their Passkey. In return for completing the challenge, the server sends back a 15 minute valid JWT session token. A salted PRF value is also generated during the Passkey challenge dialogue. It's used as an input to generate an AES encryption key (KEK, key encryption key).
2. Using the JWT session token, the user now requests to access the wrapped DEK and the vault. The server sends both.
3. The user's browser unwraps the DEK, the unwrapped DEK is then used to unwrap the vault. The DEK is discarded.
4. The vault's Ethereum seed phrase is stored for the time of the browser or app's session in the user browser's local storage. Upon closing the browser tab or app, the Ethereum seed phrase is deleted from the device.
```mermaid
sequenceDiagram
actor User
participant Browser
participant OS as OS/Passkey Provider
participant Server
User->>Browser: Open website, click "Login"
Browser->>Server: POST /login/begin
Note over Server: Generate authentication challenge
Server->>Browser: Return authentication options<br/>(challenge, rpID, etc)
Browser->>OS: navigator.credentials.get()<br/>with PRF extension
OS->>User: Show Passkey dialog
User->>OS: Confirm authentication
Note over OS: Sign challenge with private key<br/>Generate PRF salt output
OS->>Browser: Return assertion response<br/>(credentialId, signature, PRF output)
Browser->>Server: POST /login/complete<br/>(assertion response)
Note over Server: Verify signature<br/>Lookup passkey by credentialId<br/>Update counter
Server->>Browser: Return JWT token (15min)
Note over Browser: Derive KEK from PRF output
Browser->>Server: GET /lockbox (with JWT)
Note over Server: Lookup lockbox by lockboxId from JWT
Server->>Browser: Return encryptedData<br/>(wrapped DEK + vault)
Note over Browser: Unwrap DEK with KEK
Note over Browser: Unwrap vault with DEK
Note over Browser: Discard plaintext DEK
Note over Browser: Store plaintext seed phrase<br/>in session storage<br/>(deleted on close)
```
### Adding a second Passkey
A user's identity should not permanently bind to a Passkey as they are not easily portable, e.g., across ecosystems.
The user now has two devices: phone1 and phone2. Assume the user has successfully registered and is logged into the app with phone1. Both devices are of different Passkey ecosystems (e.g. Apple and Google). The user now wants to add phone2 as a second device.
1. On phone2, the user navigates to the app and clicks "Add recovery option." The user is presented with a Passkey registration dialogue. Upon completing the registration challenge, phone2 receives a 15 minute valid JWT session token. A PRF value is generated from the Passkey dialogue too and is used as an input to generate an AES encryption key (KEK).
2. An ephemeral ECDH keypair (public + private) is generated on phone2. The ECDH's public key, along with the public key of phone2's Passkey credential are shown as a QR code along with the message "Scan this QR code with phone1"
3. On phone1 (already logged in), the user scans the QR code in the app and deserializes phone2's ECDH public key and phone2's Passkey's public key.
4. To get the DEK, phone1 now repeats the Login proceedure, receives the DEK but doesn't discard it in step 3.
5. Instead, phone1 wraps the plaintext DEK using phone2's public ECDH key and sends it to a storage slot on the server that can only be accessed again by phone2 logging in through its Passkey. The server then also allows phone2's Passkey to access the vault.
9. Using the JWT session key from step 1, phone2 polls the server's ephemeral storage slot and finds the towards-itself encrypted DEK.
10. Phone2 requests and downloads phone1's encrypted vault as well as the towards itself wrapped DEK.
11. Phone2 unwraps the DEK using the ECDH private key and discards the ECDH keypair.
12. Phone2 now wraps the plaintext DEK with its own KEK.
13. Phone2 unwraps the vault and stores the Ethereum seed phrase in the app's session storage.
14. Phone2 sends the newly wrapped DEK to the server where it is stored along side the vault and DEKs.
```mermaid
sequenceDiagram
actor User
participant Phone2 as Phone2 Browser
participant OS2 as Phone2 OS/Passkey
participant Server
participant Phone1 as Phone1 Browser (logged in)
participant OS1 as Phone1 OS/Passkey
Note over Phone1: Phone1 is already logged in
User->>Phone2: Click "Add recovery option"
Phone2->>Server: POST /register/begin
Server->>Phone2: Return registration options
Phone2->>OS2: navigator.credentials.create()<br/>with PRF extension
OS2->>User: Show Passkey dialog
User->>OS2: Confirm creation
Note over OS2: Generate keypair<br/>Generate PRF output
OS2->>Phone2: Return credential response
Phone2->>Server: POST /register/complete
Server->>Phone2: Return JWT token (15min)
Note over Phone2: Derive KEK2 from PRF output<br/>Generate ephemeral ECDH keypair
Phone2->>Phone2: Display QR code<br/>(ECDH public key + credential public key)
User->>Phone1: Scan QR code with Phone1
Phone1->>Phone1: Parse ECDH public key<br/>and credential public key
Note over Phone1: Repeat login procedure to get DEK
Phone1->>Server: POST /login/begin
Server->>Phone1: Return authentication options
Phone1->>OS1: navigator.credentials.get()<br/>with PRF extension
OS1->>User: Show Passkey dialog
User->>OS1: Confirm authentication
OS1->>Phone1: Return assertion + PRF output
Phone1->>Server: POST /login/complete
Server->>Phone1: Return JWT token
Note over Phone1: Derive KEK1 from PRF output
Phone1->>Server: GET /lockbox (with JWT)
Server->>Phone1: Return encryptedData
Note over Phone1: Unwrap DEK with KEK1<br/>Keep plaintext DEK in memory<br/>(don't discard yet)
Note over Phone1: Encrypt DEK with Phone2's ECDH public key
Phone1->>Server: POST /recovery/transfer<br/>(encrypted DEK, Phone2 credential pubkey, with JWT)
Note over Server: Store in ephemeral slot<br/>Authorize Phone2 credential<br/>to access lockbox
Server->>Phone1: 200 OK
Note over Phone1: Now discard plaintext DEK
Phone2->>Server: GET /recovery/transfer (with JWT, polling)
Server->>Phone2: Return ECDH-encrypted DEK
Phone2->>Server: GET /lockbox (with JWT)
Server->>Phone2: Return vault
Note over Phone2: Decrypt DEK with ECDH private key<br/>Discard ECDH keypair
Note over Phone2: Wrap DEK with KEK2
Phone2->>Server: PUT /lockbox/add-key<br/>(wrapped DEK2, with JWT)
Note over Server: Store Phone2's wrapped DEK<br/>alongside existing data
Server->>Phone2: 200 OK
Note over Phone2: Unwrap vault with DEK<br/>Store seed phrase in session storage
```
#### Considerations
It is important that phone1 learns about phone2's decryption key through a QR code as this then guarantees that the server never gets a chance to steal or see the Ethereum seed phrase.
### Rotating the DEK
[TODO]
## Server Architecture
As the encrypted blob storage is for many users the only way to access their application credentials, then it is important to write a few words about how we intend to design the server architecture.
We think that decentralized social mediabuilders have a shared interest in keeping users' identities safe, so we think that them running their own instances of these encrypted blob servers makes sense.
We can also envision dapp-independent providers of this blob storage to emerge. Which is fine, but also not something we intend to design for in this first iteration.
## Feasibility
To understand the feasibility to implement out above outlined scheme we have to understand how widely supported Passkeys with the PRF extension are. This is not exactly an easy task as there are many nuances to consider.
Generally speaking, we believe that Passkeys and PRF will find its way into most operating systems and browsers. So we are optimistic that our scheme will be widely supported over the long term. However, we think it's also important to thoroughly validate some potential pitfalls. A few examples of Passkeys+PRF support being sub-optimal are:
1. Passkeys and PRF are be unavailable on a logged out version of Chrome.
2. Authenticators like iCloud Keychain are shipped along with operating system upgrades, and so while browsers may already support Passkeys and PRF, hence suggesting a large addressable market, an outdated authenticator can still fail the process, shrinking the addressable market.
## Call to Action
We have written this document for it to be circulated among potential stakeholders and investors.
We think such a system could be built now pretty easily, the technology is ready and it seems no one has built it yet. But we'd like to work on this as a community of interested parties to make this safe, co-owned and successful.
If such a system already exists, we don't want to build it, but reuse and adjust the existing system.
We are most likely not interested in just using Ethereum smart accounts or newly developed Ethereum crypto currency wallets as they tend to favor moving user funds around which is a misaligned goal to what we intend to use Ethereum keys for. This, however, is by no means meant to exclude the Ethereum wallet community of which we consider ourselves a part too.
If you would like to help drive this project forward, please reach out to @freeatnet or @timdaub on Telegram. Please also join our Telegram group: https://t.me/+MT5G5BZUQTU2ODRk
## Conclusion
These days it almost feels as if it was over for crypto-based decentralized social media.
Not even Ethereum leadership seems to embrace its vision anymore. This can be a curse if we assume their help to prosper.
Seeking a split in how we design wallets for decentralized social media, we'd like to argue, can maybe also be a blessing. We can finally pave our own path, solve our own problems and we may still benefit from the excellent research pipeline that crypto currencies has generated.