owned this note
owned this note
Published
Linked with GitHub
# Handling Custody Transfers Safely
Farcaster users keep their fid in an Ethereum address known as the custody address. The custody address (ECDSA keypair) issues a signed message which authorizes a signer (EdDSA keypair). The signer, in turn, can issue signed messages like casts or likes for the user.
```mermaid
flowchart LR
Root((ECDSA Keypair <br/> Custody Address <br/> 0x123)):::ecdsaSigner --> SignerAdd(Message <br/> authorize 0xabc <br/> signed_by: 0x123)
SignerAdd:::message --> Signer((EdDSA Keypair <br/> Farcaster Signer <br/> 0xabc)):::eddsaSigner
Signer --> Cast1(Message <br/> hello world <br> signed_by: 0xabc):::message
Signer --> Cast2(Message <br/> it's me! <br> signed_by: 0xabc):::message
classDef ecdsaSigner fill:#C9F0FF, stroke:#333;
classDef eddsaSigner fill:#EFEFF0, stroke:#333;
classDef message fill: #EAFFFD, stroke:#333;
```
Users may transfer fids to a new address to use change clients, rotate keys for security or to transfer control. When this happens, users can simply issue a new signed message signed by their new custody address authorizing the previously known EdDSA keypair as shown below. This allows the trust chain to be preserved without having to issue a new EdDSA keypair and re-sign casts.
```mermaid
flowchart LR
Root((ECDSA Keypair <br/> Custody Address <br/> 0x123)):::ecdsaSigner --> SignerAdd(Message <br/> authorize 0xabc <br/> signed_by: 0x123)
SignerAdd:::message --> Signer((EdDSA Keypair <br/> Farcaster Signer <br/> 0xabc)):::eddsaSigner
Signer --> Cast1(Message <br/> hello world <br> signed_by: 0xabc):::message
Signer --> Cast2(Message <br/> it's me! <br> signed_by: 0xabc):::message
Root2((ECDSA Keypair <br/> Custody Address <br/> 0x456)):::ecdsaSigner --> SignerAdd2(Message <br/> authorize 0xabc <br/> signed_by: 0x456)
SignerAdd2:::message --> Signer
classDef ecdsaSigner fill:#C9F0FF, stroke:#333;
classDef eddsaSigner fill:#EFEFF0, stroke:#333;
classDef message fill: #EAFFFD, stroke:#333;
```
The problem is that the transfer of the fid from 0x123 to 0x456 and the issuance of the new signer signed by 0x456 cannot be done concurrently since they happen on different systems.
If a user transfers the fid first, then the moment the Hub sees that it can no longer trust all the messages and drops them. If the user tries to sign the message and upload it before the transfer, the Hub will reject it. Hubs cannot accept and store messages from arbitrary addresses that do not yet own an fid, otherwise its a vector for a storage ddos.
## Goals
We need to find a solution that satisfies the following constraints:
- Users can transfer an fid and without losing their history.
- Hubs do not introduce vectors for DDOS attacks
- Developers can implement Farcaster clients easily.
- Hubs remain as simple as possible.
## Proposal
Assume that Alice holds an fid in address `0x123` held in her client (sending client). She broadcasts SignerAdd A, signed by `0x123` which authorizes `0xabc` as a Signer. She broadcasts many messages using this signer. She now wants to transfer her fid to `0x456`, held in a separate Farcaster client. She must create and upload a new SignerAdd B signed by `0x456` which authorizes the signer `0xabc`, and by extension, all the messages signed by it.
Since the two operations can be seen in any order by hubs, we need to design a system that ensures messages are not lost irrecoverably while the transactions settle.
### Revoked Signer Mempool
Hubs will keep signers for 60 minutes after the custody address that signed them transfers the fid. Users can re-upload their new signers during this grace period without losing out older messages. The signers are maintained in both the storage db and the sync trie until the expiration occurs. The 60-minute persistence is also subject to the regular rules of pruning as well which may cause some messages to organically expire.
- Alice transfers the fid from the 0x123 to 0x456 using the sending client
- Alice waits for confirmation, then uploads SignerAdd B
This design has a reasonably simple user experience and doesn't introduce any significant scaling problems or vectors for attack on the hubs. Hubs will need to introduce a TTL or similar system that expires messages, but we were planning to do that for pruning anyway so the net complexity increase is small.
## Rejected Ideas
### Custody Link Message
Hubs accept a "custody-link" per fid that is signed by its custody address and points to another Ethereum address. The latter address is allowed to upload signer messages in a "dormant" state where cannot sign other messages until the address becomes valid.
- Alice uploads a custody link message signed by 0x123 pointing to 0x56
- Alice uploads SignerAddB which is accepted by the Hub as a dormant signer
- Alice transfers the fid from the 0x123 to 0x456 using the sending client
- The Hub moves SignerAddB to an active signer, preserving her messages
This design is the safest, eliminating any potential for message loss and avoiding the need for a backup. It was rejected because it compounds the state machine for the SignerStore and is much more complex to implement that the current approach. It can also be layered on at a later date, while removing it would be much harder.
### Do Nothing
Hubs delete signers (and any associated messages) as soon as the fid is transferred out of the custody address that authorized the signer. Clients are responsible for implementing backup solutions for Farcaster users.
- Alice users her receiving client to download all her messages to disk
- Alice transfers the fid from the 0x123 to 0x456 using the sending client
- Alice waits for confirmation, then uploads SignerAdd B followed by all her messages
This design is simple to implement, but is more complicated for everyone. Users wait longer and may see their messages disappear momentarily from the network. Applications must implement backup and restore feature which adds complexity. Hubs may see network thrash as thousands of messages disappear are deleted, re-uploaded and broadcast again.