-
Notifications
You must be signed in to change notification settings - Fork 162
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
base: main
Are you sure you want to change the base?
CAIP-282 - Browser Wallet Messaging Interface #282
Conversation
Co-authored-by: Chris Smith <[email protected]>
Co-authored-by: Chris Smith <[email protected]>
…om/pedrouid/CAIPs into browser-wallet-messaging-interface
Co-authored-by: Derek <[email protected]>
Co-authored-by: Derek <[email protected]>
Co-authored-by: Derek <[email protected]>
Co-authored-by: Derek <[email protected]>
Co-authored-by: Derek <[email protected]>
There was a problem hiding this 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.
Co-authored-by: Gregory Rocco <[email protected]>
Co-authored-by: Gregory Rocco <[email protected]>
Co-authored-by: Gregory Rocco <[email protected]>
Just sharing here some developments on this CAIP... after discussing it with several teams who gave very valuable feedback
What are the next steps? a. Separate window.postMessage from discovery interface into two CAIPs 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:
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 |
This PR now includes 3 proposals:
Still missing another standard to be included in this PR:
|
CAIPs/caip-282.md
Outdated
|
||
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. |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed 👍
CAIPs/caip-282.md
Outdated
} | ||
|
||
|
||
// Example for wallet_prompt |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch... fixed
CAIPs/caip-282.md
Outdated
|
||
## 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. |
There was a problem hiding this comment.
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"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed 👍
CAIPs/caip-282.md
Outdated
|
||
#### 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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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:
- when it loads
- 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
// 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 | ||
} | ||
}); |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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 | ||
} | ||
} | ||
}); |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
interface WalletAnnounceRequestParams { | ||
uuid: string; | ||
name: string; | ||
icon: string; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 frozen
Wallet
object, preventing tampering if done sensibly - once an extension announced its
Wallet
, the application is responsible for tracking the object collection
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see next comment
Co-authored-by: kewde <[email protected]>
There was a problem hiding this 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. |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
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 | ||
}, | ||
} | ||
} | ||
)); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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??
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
chains?: string[]; // compatible with CAIP-2 | |
scopes?: string[]; // compatible with CAIP-2 |
good catch, i think it's chains
in pedro's prod implementation 😅
CAIPs/caip-282.md
Outdated
|
||
#### 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. |
There was a problem hiding this comment.
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.
|
||
## Security Considerations | ||
|
||
TODO |
There was a problem hiding this comment.
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.
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`
are we discussing this in person in Bangkok, btw, @adonesky1 @pedrouid ? |
interface WalletData { | ||
// Required properties | ||
uuid: string; | ||
name: string; | ||
icon: string; | ||
rdns: string; | ||
|
||
// Optional properties | ||
extensionId?: string; | ||
scopes?: Caip217AuthorizationScopes; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
interface WalletData { | |
// Required properties | |
uuid: string; | |
name: string; | |
icon: string; | |
rdns: string; | |
// Optional properties | |
extensionId?: string; | |
scopes?: Caip217AuthorizationScopes; | |
} | |
interface WalletData { | |
// Required properties | |
uuid: string; | |
name: string; | |
icon: string; | |
rdns: string; | |
// Optional properties | |
target?: { | |
origin?: string; | |
extensionId?: string; | |
} | |
scopes?: Caip217AuthorizationScopes; | |
} |
Standardized messaging for wallet interface in browser environments.