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

CAIP-282 - Browser Wallet Messaging Interface #282

Open
wants to merge 31 commits into
base: main
Choose a base branch
from

Conversation

pedrouid
Copy link
Member

@pedrouid pedrouid commented May 30, 2024

Standardized messaging for wallet interface in browser environments.

@pedrouid pedrouid changed the title Browser wallet messaging interface CAIP-282 - Browser Wallet Messaging Interface May 30, 2024
CAIPs/caip-282.md Outdated Show resolved Hide resolved
CAIPs/caip-282.md Outdated Show resolved Hide resolved
CAIPs/caip-282.md Outdated Show resolved Hide resolved
CAIPs/caip-282.md Outdated Show resolved Hide resolved
CAIPs/caip-282.md Outdated Show resolved Hide resolved
CAIPs/caip-282.md Outdated Show resolved Hide resolved
CAIPs/caip-282.md Outdated Show resolved Hide resolved
CAIPs/caip-282.md Outdated Show resolved Hide resolved
CAIPs/caip-282.md Outdated Show resolved Hide resolved
CAIPs/caip-282.md Outdated Show resolved Hide resolved
Copy link
Contributor

@obstropolos obstropolos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A number of feedback across the spec - nothing too deep other than just a few consistency items / etc.

CAIPs/caip-282.md Outdated Show resolved Hide resolved
CAIPs/caip-282.md Outdated Show resolved Hide resolved
CAIPs/caip-282.md Outdated Show resolved Hide resolved
CAIPs/caip-282.md Outdated Show resolved Hide resolved
CAIPs/caip-282.md Show resolved Hide resolved
CAIPs/caip-282.md Outdated Show resolved Hide resolved
CAIPs/caip-282.md Outdated Show resolved Hide resolved
CAIPs/caip-282.md Outdated Show resolved Hide resolved
CAIPs/caip-282.md Outdated Show resolved Hide resolved
CAIPs/caip-282.md Outdated Show resolved Hide resolved
pedrouid and others added 3 commits June 7, 2024 15:40
Co-authored-by: Gregory Rocco <[email protected]>
Co-authored-by: Gregory Rocco <[email protected]>
CAIPs/caip-282.md Outdated Show resolved Hide resolved
CAIPs/caip-282.md Outdated Show resolved Hide resolved
CAIPs/caip-282.md Outdated Show resolved Hide resolved
@pedrouid
Copy link
Member Author

Just sharing here some developments on this CAIP... after discussing it with several teams who gave very valuable feedback

  1. The interfaces have been changed to follow a JSON-RPC structure to make it more agnostic and easier to use with multiple transports
  2. Enforcing window.postMessage makes this CAIP not very future-proof as Chrome Extensions are slowly migrating to externally_connectable
  3. This CAIP is more useful if it does NOT describe the actual transport and instead focuses on the discovery interfaces
  4. The usage of window.postMessage without mentions of targetOrigin creates huge security concerns

What are the next steps?

a. Separate window.postMessage from discovery interface into two CAIPs
b. Create a new CAIP which uses the discovery interface with externally_connectable
c. Create a new CAIP which uses the discovery interface with window.dispatchEvent

What is the end goal?

This CAIP intends to provide the most agnostic and interoperable interface for wallet discovery and consequentally handshake and signing.

Since CAIP-25 and CAIP-27 already describe handshake and signing then CAIP-282 will focus purely on discovery

Why create separate CAIPs for each transport?

These additional CAIPs are important for Apps and Libraries to support multiple wallet transports... most importantly the following:

  • window.postMessage for embedded wallets with iframes
  • window.dispatchEvent for legacy browser extension support
  • externally_connectable for newer browser extension support

The combination of all JSON-RPC interfaces and different transport specs will any wallet to be discoverable, connect and sign with a decentralized application for all CAIP compatible chains

@pedrouid
Copy link
Member Author

pedrouid commented Jun 26, 2024

This PR now includes 3 proposals:

  • CAIP-282 -> Browser Wallet Discovery Interface
  • CAIP-294-> Browser Wallet Messaging for Extensions (window.dispatchEvent)
  • CAIP-295 -> Browser Wallet Messaging for Iframes (window.postMessage)

Still missing another standard to be included in this PR:

  • CAIP-296 -> Browser Wallet Messaging for Extensions (externally_connectable)


The parameters `name` and `icon` are used to display to the user to be easily recognizable while the `rdns` and `uuid` are only used internally for de-duping while they must always be unique, the `rdns` will always be the same but `uuid` is ephemeral per browser session.

The only optional parameter is `scps` which is defined by CAIP-217 authorization specs that enables early discoverability and filtering of wallets based on RPC methods, notifications, documents and endpoints but also optional discovery of supported chains and even accounts.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I think this should be scopes

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed 👍

}


// Example for wallet_prompt

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I think this should be Example for wallet_announce

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch... fixed


## Privacy Considerations

Any form of wallet discoverability must alwasys take in consideration wallet fingerprinting that can happen by malicious webpages or extensions that attempt to capture user information. Wallet Providers can abstain from publishing "Announce" messages on every page load and wait for incoming "Prompt" messages. Yet this opens the possibility for race conditions where Wallet Providers could be initialized after the "Prompt" message was published and therefore be undiscoverable. It is recommended that Wallet Providers offer this more "private connect" feature that users only enable optionally, rather than set by default.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: alwasys -> always
Maybe also "always take into consideration"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed 👍


#### Discovery

Both Wallet Providers and blockchain libraries must listen to incoming messages that might be published after their initialization. Additionally both Wallet Providers and blockchain libraries must publish a message to both announce themselves and their intent to connect, respectively.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this specific CAIP only deal with wallets that announce themselves after the blockchain library loads and can receive wallet_announce messages?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wallet WILL announce itself in two instances:

  1. when it loads
  2. when it receives a wallet_prompt message

This prevents any async problems that might arise from wallet and library to load at different stages

This pattern is inspired by the work done by EIP-6963:
https://eips.ethereum.org/EIPS/eip-6963#window-events

Comment on lines +64 to +71
// Blockchain Library starts listening on init
window.addEventListener("caip294:wallet_announce", (event) => {
// when an announce message was received then the library can index it by uuid
wallets[event.detail.params.uuid] = {
params: event.detail.params,
eventName: event.detail.params.uuid
}
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar question here - how does this handle a wallet that has already announced itself before the page has loaded the blockchain library?

In wallet-standard we have a wallet-standard:app-ready event that the wallet attaches a listener for. This avoids the need for the wallet to re-announce itself every time a prompt is made.

I guess the tradeoff here is that you can do filtering in the wallet_prompt event with this design, instead of the app receiving all wallets and having to do its own filtering.

But I do think it's really important that an app can extremely quickly know all the available wallets. If an apps wants to eg autoconnect (or at least display data from) a remembered previously connected wallet, then any time you don't know if that wallet is available worsens the UX there.


#### Handshake

After the wallet has been selected by the user then the Blockchain Library MUST publish a message to share its intent to establish a connection. This can be either done as a [CAIP-25][caip-25] request.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've been questioning this a bit with wallet-standard. Generally a wallet will allow a user to authorise some set of accounts for a dapp. It'll then make those available as accounts on the wallet object (or here the scopes object mentioned in caip-282). From the dapp's perspective, the user selecting that wallet (or the dapp selecting it for them) decides which accounts/features/chains/etc are available to it. But for the wallet, it only needs to care about a connection when it needs to change those scopes. That doesn't necessarily have to happen every time the user selects the wallet in an app.


#### Handshake

After the wallet has been selected by the user then the Blockchain Library MUST publish a message to share its intent to establish a connection. This can be either done as a [CAIP-25][caip-25] request.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I think the second part of the either is missing

// prompt user to approve session
}
}
});
Copy link

@mcintyre94 mcintyre94 Jul 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could a different wallet 'hijack' these events? Eg if I'm a wallet, can I send a wallet_prompt message in the app, capture the response, find the current UUID of Metamask, and then listen to its events to eg. also display my own popup?

With wallet-standard we don't use event listeners to send messages to the wallet after discovery. Each Wallet has its own code (exposed as features), and once a wallet has been chosen you're just calling functions of that object. No other wallet can see what's going on there or inject itself into the events.


#### UUIDs

The generation of UUIDs is crucial for this messaging interface to work seamlessly for the users.
Copy link

@kewde kewde Jul 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we explain why are uuid's crucial?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UUID is local and ephemeral

RDNS is global and permanent

The two identifiers together allow wallets to be identifiable but also de-duplicated if necessary

Either one by itself would not meet those requirements


## Simple Summary

CAIP-295 defines a standardized messaging transport for browser iframe wallets.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a demand for supporting iframes from the dApp side?

I'm not sure which wallets currently support iframes. I know there are some limitations on react-native-webview (typically used in mobile wallet dApp browser) that doesn't support injecting JS into the iframes on iOS.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is common for session based wallets and believe gnosis safe uses this approach too. I think this is going to become more common as well where iframes are going to contain the DApp logic on web2 pages kind of like what we're seeing blinks do, so it makes sense to make this stuff work with iframes when possible.

CAIPs/caip-282.md Outdated Show resolved Hide resolved
interface WalletAnnounceRequestParams {
uuid: string;
name: string;
icon: string;
Copy link

@kewde kewde Jul 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we specify the allowed formats and URI?

export type WalletIcon = `data:image/${'svg+xml' | 'webp' | 'png' | 'gif'};base64,${string}`;

Is what wallet standard recommends, I think the data URIs should be the mandatory format.

Do we want to allow or specifically forbid remote images?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually yes... that is missing here!

We definitely want to forbid remote images


## Security Considerations

TODO
Copy link

@kewde kewde Jul 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's worth noting that a malicious wallet may be eavesdropping on the traffic of the other wallets by using it's uuid and racing to reply, essentially hijacking/mimicking the wallet.

Legacy injection

As a wallet you could attach the object to the window in a permanent way, window.mywallet which would become irreplaceable and undeleteable, providing a stronger security guarantee that the wallet you're talking with is the same one. It, ofcourse, doesn't guarantee authenticity of the wallet but it's at least consistent.

Object.defineProperty(window, 'mywallet', {
  value: provider,
  writable: false,
  configurable: false;
});

wallet standard

In wallet-standard, there are more guarantees at this level, since:

  • extension can create frozenWallet object, preventing tampering if done sensibly
  • once an extension announced its Wallet, the application is responsible for tracking the object collection

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i remember @kdenhartog discussing at length the pros and cons (more like, limitations) of prototype freezing in the context of JS/ECMAScript/browser security over the years when EIP-6963 was being debated... it might still be worth mentioning that other cross-chain standards use a freezing approach, but i think it's a legacy thing anyways since a communication channel outside the page is what wallets are already migrating to which sidesteps the issue.

```typescript
// for "wallet_prompt" method
interface WalletPromptRequestParams {
chains?: string[]; // compatible with CAIP-2
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The behavior for chains is undefined, should a wallet only reply if it has a chain in this list?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well we can't guarantee that behavior ALWAYS

If the wallet announces itself before the library is able to send wallet_prompt message then it can't evaluate the chains

But if the wallet does receive the wallet_prompt and chains field is specified then it would make sense to not announce itself

Copy link

@kewde kewde Jul 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we clear that up by putting it in the specification?

Copy link
Contributor

@adonesky1 adonesky1 Jul 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would networks be a more generic and flexible term here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure networks would be helpful since everything is a network when we are talking about the internet

Here we are referring to Blockchain Networks defined by CAIP-2 identifiers

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see next comment

CAIPs/caip-282.md Outdated Show resolved Hide resolved
CAIPs/caip-282.md Outdated Show resolved Hide resolved
CAIPs/caip-282.md Outdated Show resolved Hide resolved
Copy link
Contributor

@kdenhartog kdenhartog left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

few comments, I need to do a couple of passes on this to make sure I'm fully digesting it but just wanted to get these comments in there early.


## Motivation

Currently, in order for Decentralized Applications to be able to support all users they need to support all browser wallet APIs. Similarly, in order for browser wallets to support all Decentralized Applications they need to support all APIs. This is not only complicated but also results in a larger bundle size of applications.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, in order for Decentralized Applications to be able to support all users they need to support all browser wallet APIs.

This is unclear since not all DApps need to use all browser wallet APIs. Should it be that "...for all wallets to be able to support all DApps..."?


```typescript
// Defined by CAIP-217
interface AuthorizationScopes {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to freeze these in some way to prevent privilege escalation attacks? E.g. it seems like we'd want this to be set and enforced wallet side and to not allow the site or other extensions to further escalate the authorization permissions defined here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes but at the same time... is this something that should be defined on the CAIP-282 for the RPC spec or should it dependent on each transport spec such as CAIP-294, CAIP-295 or CAIP-296

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I'm not familiar with the delineation between the two or how we're splitting them. I hadn't caught those details when reviewing this. Do we expect to ever have a transport method that doesn't rely on browser JS ever to communicate between a page? If not, then it seems like a common point shared between the various transport methods would be useful and reinforced in the specs as well.


A UUID can be re-used as a `sessionId` if and only if the [CAIP-25][caip-25] procedure has been prompted to the user and the user has approved its permissions to allow the application to make future signing requests.

Once established, the UUID is used as `sessionId` for the [CAIP-27][caip-27] payloads, which can verify that incoming messages are being routed through the appropriate channels.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might open the door to steal a UUID as an object capability to privilege escalate a page by using an incorrect UUID on a different page so that the new page can have the privileges of the UUID of the old page. It's hard to tell based on the spec, but is likely something we should be paying attention to in implementations so should have some form of wording for it on the spec if my understanding isn't off here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually there have been discussions on the RPC working group to make this sessionId optional since it can be fixed for each wallet

Plus it's not necessarily required for all transports

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I wrote this I was thinking more along the context of WalletAnnoucementRequestParams.UUID property. I'm not following why a sessionId would be fixed either so I'm likely missing some context here.

sessionId: walletData.uuid,
scope: "chain:777",
request: {
method: "chain_signMessage",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm starting to wonder if we should move away from using RPC methods and RPCs as an interface here. More often than not RPCs are blockchain specific logic which doesn't necessitate it being accessible to the page. In many cases too, the RPC calls aren't actually "remote" in any way either because the wallet is handling them. Would it make sense to encapsulate logic here inside the wallet so that chain specific logic doesn't necessitate page specific logic based on the chain in use?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends on your definition of remote... because the idea of CAIP-282 is that it standarizes communication for all transports

That might be window.dispatchEvent, window.postMessage, externally_connectable, etc

Therefore there is always a concept of communication between a webpage and an external party that is remote to the webpage itself

I would not consider remote to be exclusively server-side or onchain

Copy link
Contributor

@kdenhartog kdenhartog Jul 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense, I wasn't thinking of them as exclusively as server-side/onchain either. Instead, I was thinking about it more within the context of the different software boundaries that exist and whether we should be leaking them/collapsing them and when.

For example, a page typically interacts with a remote client acting on behalf of the user (say for permission to sign or to retrieve their data) and needs some transport interface to perform that interaction. In most cases, we expect that the page will communicate client via this transport, but because we're utilizing RPCs that don't really align the permissioning model with this (since chains have different privacy models often) we end up in certain scenarios where the page may choose to bypass interacting with the client and retrieve information directly from the chain instead.

In my mind, this creates a more complex interaction model where it's not clear when the page-client interaction model is expected if the page can access the information itself via the chain which will probably produce odd side effects in a cross chain model.

For this reason, I think it be useful for us to not leak chain specific RPCs to the page and instead should be establishing an encapsulation pattern where the page uses the interface which encapsulates blockchain specific RPC logic away from the page rather than directly calling the RPC methods.

This encapsulation will help with creating a clear interface of reusable patterns to define application logic between the client and page across different blockchain ecosystems such that we can operate under the heuristic of "if the page wants the clients data it should go to the client to get it". Now, I can think of cases where this won't universally apply so I'm not sure it's the cleanest heuristic, but it does seem to be a good practice so that we can establish patterns of reuse between the client and page and make it easier for pages to interact.


## Simple Summary

CAIP-295 defines a standardized messaging transport for browser iframe wallets.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is common for session based wallets and believe gnosis safe uses this approach too. I think this is going to become more common as well where iframes are going to contain the DApp logic on web2 pages kind of like what we're seeing blinks do, so it makes sense to make this stuff work with iframes when possible.

Comment on lines +74 to +89
window.dispatchEvent(new CustomEvent(
"caip294:wallet_prompt",
{
detail: {
id: 1,
jsonrpc: "2.0"
method: "wallet_prompt",
params: {
// optionally the Blockchain Library can prompt wallets to announce matching only the chains
chains: [] // optional
// if the Blockchain Library supports CAIP-275 then it can include a name
authName: "", // optional
},
}
}
));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note I have a related CAIP that is specifically handling the case of discovery when the wallet's API is delivered via externally_connectable. Since in this case the dapp will need the extensionId in order to establish a channel of communication with the wallet, the extensionId is a critical piece of data for us to announce in this event. Perhaps it makes sense to merge this in as an optional param to this CAIP with some of the language from my CAIP to explain its use.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this new proposal be labelled as CAIP-296 which uses externally_connectable as a transport for CAIP-282 discovery??

#296

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merged/addressed here: bc49428

```typescript
// for "wallet_prompt" method
interface WalletPromptRequestParams {
chains?: string[]; // compatible with CAIP-2

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in caip-25 this is now called scopes

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
chains?: string[]; // compatible with CAIP-2
scopes?: string[]; // compatible with CAIP-2

good catch, i think it's chains in pedro's prod implementation 😅


#### Connection

After the wallet has been selected by the user then the blockchain library MUST publish a message to share its intent to establish a connection. This can be either done as a [CAIP-25][caip-25] request or [CAIP-222][caip-222] authentication.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This "can be either" as in MUST be one of? Or MAY be one, the other, some future third thing, or some fourth thing that already exists but we discourage? Probably worth being very explicit here.

Another complication that comes to mind is fallbacks, i.e. if a wallet prefers 222, but falls back to 25 after 222 times out or otherwise fails; probably worth expressing the intent to connect as a sequence of requests and responses eventually ending in success or failure rather than as a single message, if that sidesteps the enumeration of valid message types.

CAIPs/caip-282.md Outdated Show resolved Hide resolved

## Security Considerations

TODO
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i remember @kdenhartog discussing at length the pros and cons (more like, limitations) of prototype freezing in the context of JS/ECMAScript/browser security over the years when EIP-6963 was being debated... it might still be worth mentioning that other cross-chain standards use a freezing approach, but i think it's a legacy thing anyways since a communication channel outside the page is what wallets are already migrating to which sidesteps the issue.

@bumblefudge
Copy link
Collaborator

apologies, pedro, my june comments were sitting in github UI limbo waiting to be batched to a complete review 🙄

Add `extensionId` property to `wallet_announce` params object for `externally_connectable`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.