FIP: Functional Signers #262
Replies: 4 comments 1 reply
-
|
Thanks for putting this together. I think the proposal is strong but we should split this into two separate FIPs. Part 1 (Off-Chain Signer Registration) is a protocol-level change to snapchain — new message types, custody-authenticated validation, conflict resolution with onchain signers. It's well-scoped and has standalone value: removing the cost/friction barrier for signer management benefits the entire ecosystem regardless of whether MPC brokers exist yet. Part 2 (Functional Signer API) is a client-layer specification for MPC broker services. As the proposal itself notes, this requires no changes to either protocol implementation. Snapchain sees standard Ed25519 keys and signatures — it doesn't know or care whether FROST was used to produce them. From a prioritization perspective:
A couple of things that would need to be addressed in a standalone Part 1 FIP:
|
Beta Was this translation helpful? Give feedback.
-
|
How does Part 1 work with snapchain history pruning? It's not clear to me how/if snapchain is pruned but if it is or will be, what happens to messages signed by a signer when |
Beta Was this translation helpful? Give feedback.
-
|
@CassOnMars, any chance you'd want to separate out the part 1 and part 2? Don't want 1 to get blocked due to discussion on 2, and 2 seems more like a spec compared to a protocol change. I'm definitely interested in moving signers off-chain due to the extra friction of an on-chain transaction. |
Beta Was this translation helpful? Give feedback.
-
|
@CassOnMars This is a great application of MPC but it defeats the intentions behind the architecture of signers, which is to improve end-user UX. By re-introducing a mandatory signing step, we eliminate the possibility of programmatic signing for things like tips and mints and also add additional friction nearly everywhere else. I think it makes more sense to prioritize session key-like fine grained limits instead of MPC architecture. While those features don't completely overlap, Fine-grained delegations and limits both allow for greater user security without compromising UX. edit: I also think it's important that delegated session keys actually expire and we can follow an OAuth2 token-expiry pattern to regenerate the key periodically, something a user is more used to. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Important
This FIP draft is a mirror of the FIP proposed for hypersnap. It is being provided for informational purposes, in the event the owners of the snapchain repo desire to maintain 1:1 compatibility.
Abstract
This FIP introduces functional signers to the Farcaster protocol, addressing two goals:
Together, these changes enable cost-free, secure signer management without compromising the authentication guarantees of the protocol.
Problem
Today, adding a signer to a Farcaster account requires an on-chain transaction to the KeyRegistry contract on Optimism. This has several consequences:
Cost: Every signer addition requires gas fees on Optimism. While L2 fees are lower than mainnet, they are still nonzero and add up for apps and users managing many signers, especially in mini app contexts where ephemeral signers may be desirable.
Friction: On-chain transactions require wallet interactions, RPC availability, and confirmations. This creates UX friction, particularly for lightweight clients, mini apps, and onboarding flows where the user may not have ETH or an active wallet connection.
Key material exposure: The current signer model assumes a single Ed25519 private key exists somewhere -- typically on the client or app server. If that key is compromised, the signer is fully compromised. There is no threshold or split-custody model available at the protocol level.
Centralized custodianship risk: Apps that manage signers on behalf of users hold full signing authority. There is no standardized mechanism for splitting this authority between the user, the app, and/or a third-party custodian, which would reduce the blast radius of any single party's compromise.
The combination of these issues makes the current signer model expensive, fragile, and risky for the growing ecosystem of mini apps and third-party integrations – evidence of which recently occurred in the form of a Supabase compromise for the miniapp Schedly, resulting in users, including one of the founders of Neynar, having token scam posts made on their behalf, targeted at a time in which these users are likely asleep (and were). In consequence, the attacker was able to steal over $65,000 in crypto from users who mistakenly believed the posts were authentic.
High Level Solution
Part 1: Off-Chain Signer Registration
Custody-Authenticated Signer Messages
Instead of requiring an on-chain KeyRegistry transaction, we introduce a new message type that allows a user's custody account to directly authorize a signer via a signed message submitted to snapchain. The protocol validates the custody signature and registers the signer, bypassing the need for on-chain interaction entirely.
The custody account (an Ethereum address controlling the FID) produces an EIP-712 (or EIP-1271 for smart contract account) typed signature over the signer authorization payload. This signature, along with the signer's public key, is wrapped in a new protocol message type:
Validation Flow
When snapchain receives a
MESSAGE_TYPE_SIGNER_ADDmessage:SignerAddBodyand extract the custody signature.SignerEventType::Add.Once registered, the signer functions identically to an on-chain registered signer -- messages signed by it are validated via standard Ed25519 signature verification and active signer lookup.
Conflict Resolution
On-chain and off-chain signers coexist. Both pathways produce equivalent signer state. If a signer is added off-chain and subsequently added on-chain (or vice versa), the earlier registration stands, and the duplicate is a no-op. Removal via either pathway (on-chain
KeyRegistry.remove()or off-chainMESSAGE_TYPE_SIGNER_REMOVE) deactivates the signer regardless of how it was added.Part 2: Functional Signer API Specification
Overview
Functional signers are Ed25519 signers whose private key never exists as a whole. Instead, the key is generated and used via MPC threshold signing, specifically the FROST (Flexible Round-Optimized Schnorr Threshold) protocol. The protocol itself does not need to be aware that a signer is "functional" -- it only sees a standard Ed25519 public key and verifies standard Ed25519 signatures. The MPC machinery lives entirely at the client/mini app interaction layer.
This section defines a general API specification for functional signer brokers -- third-party services that participate in MPC key generation and signing alongside the client.
Architecture
The client holds one key share, the broker holds another (2-of-2 minimum). Neither party can sign alone. When a signature is needed, both parties contribute partial signatures via the FROST protocol, producing a standard Ed25519 signature that is indistinguishable from a single-party signature.
FROST Protocol Summary
This specification adopts FROST as defined in RFC 9591, with the following parameters:
The joint public key produced by DKG is what gets registered as a signer (via either on-chain or off-chain registration from Part 1).
API Specification
Brokers MUST implement the following REST API. All request and response bodies are JSON. All endpoints require authentication (mechanism is broker-specific, e.g., OAuth, API keys, or custody-signed bearer tokens).
Key Generation (DKG)
POST /v1/keysInitiates distributed key generation. Returns a session and the broker's Round 1 DKG data.
Request:
{ "curve": "Ed25519", "threshold": 2, "total_parties": 2, "client_party_id": 2 }Response:
{ "session_id": "<uuid>", "party_id": 1, "round": 1, "data": { "commitments": ["<base64>", ...], "proof_wi": "<base64>", "proof_ci": "<base64>", "p2p_share": "<base64>" } }POST /v1/keys/{session_id}/roundsSubmits the client's round data and receives the broker's next round data.
Request (Round 1 contribution from client):
{ "round": 1, "data": { "commitments": ["<base64>", ...], "proof_wi": "<base64>", "proof_ci": "<base64>", "p2p_share": "<base64>" } }Response (Round 2 output):
{ "round": 2, "complete": true, "data": { "verification_key": "<base64>", "vk_share": "<base64>" } }On completion, both parties hold:
sk_share: Their secret key share (private, never transmitted)vk_share: Their verification key share (public)verification_key: The joint Ed25519 public key (public, registered as signer)Signing
POST /v1/keys/{key_id}/signInitiates a signing session. Returns the broker's Round 1 nonce commitments.
Request:
{ "message": "<base64>", "message_type": "raw" }Response:
{ "session_id": "<uuid>", "round": 1, "data": { "di": "<base64>", "ei": "<base64>" } }POST /v1/keys/{key_id}/sign/{session_id}/roundsSubmits the client's round data and receives the broker's partial signature or final result.
Request (Round 1 contribution from client):
{ "round": 1, "data": { "di": "<base64>", "ei": "<base64>" } }Response (Round 2):
{ "round": 2, "data": { "zi": "<base64>", "vki": "<base64>" } }The client submits their Round 2 data and receives the aggregated signature:
Request (Round 2 contribution from client):
{ "round": 2, "data": { "zi": "<base64>", "vki": "<base64>" } }Response (complete):
{ "round": 3, "complete": true, "signature": "<base64, 64 bytes>" }The resulting 64-byte signature is a standard Ed25519 signature, directly usable in Farcaster protocol messages.
Key Metadata
GET /v1/keys/{key_id}Returns metadata about a functional key.
Response:
{ "key_id": "<uuid>", "curve": "Ed25519", "verification_key": "<base64, 32 bytes>", "threshold": 2, "total_parties": 2, "created_at": "<ISO 8601>" }Key Deletion
DELETE /v1/keys/{key_id}Deletes the broker's key share. This is a destructive, irreversible operation -- the functional signer will no longer be able to produce signatures after this call.
Client Integration Flow
The complete flow for a mini app using functional signers:
Key Generation: The mini app initiates DKG with a broker, producing a joint Ed25519 public key. The mini app stores its key share locally (encrypted, device-bound).
Signer Registration: The mini app requests the user's custody account to sign a
SignerAddEIP-712 message authorizing the joint public key. This is submitted to snapchain as aMESSAGE_TYPE_SIGNER_ADD-- no on-chain transaction, no gas fees.Message Signing: When the mini app needs to sign a Farcaster message, it initiates a signing session with the broker. The two parties exchange FROST protocol messages (2 rounds), producing a standard Ed25519 signature. The message is submitted to snapchain with this signature.
Signer Removal: If the user revokes the signer, the custody account signs a
SignerRemovemessage. The mini app may also callDELETE /v1/keys/{key_id}on the broker to destroy the key share.Security Properties
Alternative Solutions
Delegated signing via app-held keys
The current model allows apps to hold full Ed25519 signer keys on behalf of users. This works but concentrates trust -- a compromised app server exposes all users' signers. Functional signers solve this by ensuring no single party holds the full key.
On-chain multisig or smart contract wallets
Signer authorization could be routed through on-chain multisig contracts. However, this increases cost (more on-chain transactions, not fewer) and adds complexity for what is fundamentally a client-side key management concern. Functional signers keep MPC at the client layer where it belongs.
Passkey/WebAuthn-based signers
Passkeys provide hardware-bound key storage but use ECDSA (P-256), not Ed25519, and do not support threshold signing. They also bind the user to a specific device or platform's passkey sync infrastructure. Functional signers are device-agnostic and curve-compatible with the existing protocol. They may be worth adding at a later time, and the first part of this proposal at least provides the groundwork required to extend support to them.
Session keys with limited permissions
Some protocols implement session keys that can only perform a subset of actions. While useful, this is orthogonal to functional signers -- session keys could themselves be functional signers, combining limited scope with split custody.
Notes
Beta Was this translation helpful? Give feedback.
All reactions