Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 131 additions & 47 deletions CAIPs/caip-25.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,55 @@ application through a provider connecting to a wallet.
## Specification

The session is defined by a wallet's response to a provider's request, and
updated, extended, closed, etc by successive calls and events. These are out of
scope of this CAIP interface and will be specified in a forthcoming one.

Within that session model, this interface outlines the authorization of an
injected provider per namespace. These authorization call/responses should be
idempotent, assuming the provider is tracking a session property, referred to by
a `sessionIdentifier` as defined in [CAIP-171][]. If a wallet needs to initiate
a new session, whether due to user input, security policy, or session expiry
reasons, it can simply generate a new session identifier to signal this event to
the calling provider.

The application interfaces with a provider to populate a session with a base
state describing authorized chains, methods, event, and accounts. This
negotation takes place by sending the application's REQUIRED and REQUESTED
properties of the session. If any requirements are not met, a failure response
expressive of one or more specific failure states will be sent (see below).
updated, extended, closed, etc by successive calls and events. The exact
parameters and assumptions of that session abstraction are defined in
[CAIP-171][], but note that a string identifier referring to it is absent from
the initial call (if authorization is granted) and present in both the initial
response and all future responses.

Given the session model of [CAIP-171][], this interface outlines the
authorization of a provider to handle a set of interfaces grouped into
namespaces, as well as to interact with a session abstraction used by both
caller and respondent to manage the authorization over time. The
`sessionIdentifier` defined in [CAIP-171][] enables this mutual management and
alignment across calls that are idempotent if identical. If a respondent (e.g. a
wallet) needs to initiate a new session, whether due to user input, security
policy, or session expiry reasons, it can simply generate a new session
identifier to signal this event to the calling provider; if a caller needs to
initiate a new session, it can do so by sending a new request without
`sessionIdentifier`. In such cases, a respondent (e.g. wallet) may choose to
explicitly close all sessions upon generation of a new one from the same origin,
or leave it to time-out; maintaining concurrent sessions is discouraged (see
Security Considerations).

In the initial call, the application interfaces with a provider to populate a
session with a base state describing authorized chains, methods, event, and
accounts. This negotation takes place by sending the application's REQUIRED and
REQUESTED authorizations of the session, grouped into objects scoping those
authorizations which in turn are grouped into two top-level arrays (named
`requiredScopes` and `optionalScopes` respectively). These two arrays are not
mutually exclusive (i.e., additional properties of a required scope may be
requested under the same keyed scope object key in the requested array). Note
that scopes can be keyed to an entire [CAIP-104][] "namespace", meaning
applicable to *any* current or future [CAIP-2][] chainID within that namespace,
or keyed to a specific [CAIP-2][] within that namespace.

If any properties in the required scope(s) are not authorized by the
respondent (e.g. wallet), a failure response expressive of one or more specific
failure states will be sent (see [#### failure states](#failure-states) below),
with the exception of user denying consent. For privacy reasons, an `undefined`
Copy link
Collaborator

Choose a reason for hiding this comment

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

This needs to be updated in line with @pedrouid's comment from last week, not sure if this is a point of contention or not, but I agree with his point.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sure-- will finesse the language in a new commit

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Copy link
Collaborator

Choose a reason for hiding this comment

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

Perfect! Just wondering, how come the error code # change?

response (or no response, depending on implementation) should be sent to prevent
incentivizing unwanted requests and to minimize the surface for fingerprinting
of public web traffic (See Privacy Considerations below).

Conversely, a succesful response will contain all the required properties *and
the provider's choice of the optional properties* expressed as a unified set of
parameters.
parameters. In the case of identically-keyed scopes appearing in both arrays in
the request where properties from both are returned as authorized, the two
scopes MUST be merged in the response (see examples below). However, respondents
MUST NOT restructure scopes (e.g., by folding properties from a [CAIP2][]-keyed,
chain-specific scope object into a [CAIP-104][]-keyed, namespace-wide scope
object) as this may introduce ambiguities (See Security Considerations below).

### Request

Expand All @@ -65,7 +95,7 @@ Example:
"jsonrpc": "2.0",
"method": "provider_authorization",
"params": {
Copy link
Collaborator

@hmalik88 hmalik88 Mar 2, 2023

Choose a reason for hiding this comment

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

shouldn't params be an array of two arrays containing various scope objects? (per our description)

type Scope = {
methods: string[];
events: string[];
}
type ChainScope = Scope;
type NamespaceScope = Scope & { chains: Array<CAIP2Id>; }
type RequiredScope = NamespaceScope | ChainScope;
type OptionalScope = RequiredScope;

type CAIP25Params = [NonEmptyArray<RequiredScope>, NonEmptyArray<OptionalScope> | undefined]

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ooh, so the params is an object in the examples and an array in the text? I hesitate to fix that either way by myself, let me check out if JSON-RPC syntax will make my job easier by disqualifying one of the two options 😅

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

https://www.jsonrpc.org/specification#parameter_structures
^ Seems it can be array ("by-position") OR object ("by-name"), and "by-name" sounds more appropriate to this kind of finicky, versioned, open-world interoperability use-case... by-position sounds too brittle and easy to confuse or break.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

so i'm personally leaning towards keep object and make the text make the example :D

Copy link
Collaborator

Choose a reason for hiding this comment

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

haha yes, I would also prefer object-based -- well that solves that :)

"requiredNamespaces": {
"requiredScopes": {
"eip155": {
"chains": ["eip155:1", "eip155:137"],
"methods": ["eth_sendTransaction", "eth_signTransaction", "eth_sign", "get_balance", "personal_sign"],
Expand All @@ -79,7 +109,7 @@ Example:
...
}
},
"optionalNamespaces":{
"optionalScopes":{
"eip155:42161": {
"methods": ["eth_sendTransaction", "eth_signTransaction", "get_balance", "personal_sign"],
"events": ["accountsChanged", "chainChanged"]
Expand All @@ -93,20 +123,20 @@ Example:
```

The JSON-RPC method is labelled as `provider_authorization` and both the
"requiredNamespaces" and "optionalNamespaces" arrays are populated with
`namespace` objects each named after the scope of authorization:
1. EITHER an entire ChainAgnostic [namespace][]
2. OR a specific [CAIP-2][] in that namespace.
"requiredScopes" and "optionalScopes" arrays are populated with
"scope objects" each named after the scope of authorization requested:
1. EITHER an entire [CAIP-104][] [namespace][]
2. OR a specific [CAIP-2][]-identified chain in a specific namespace.

Each `namespace` object contains the following parameters:
Each scope object contains the following parameters:
- chains - array of [CAIP-2][]-compliant `chainId`'s. This parameter MAY be
omitted if a single-chain scope is already declared in the index of the object.
- methods - array of JSON-RPC methods expected to be used during the session
Copy link
Member

@ligi ligi Feb 9, 2023

Choose a reason for hiding this comment

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

As discussed in today's editorial call: maybe using method names is not completely covering it - there is different behavior in methods between different clients (@TimDaub surfaced this)
My idea to attack it would be to create a registry (like we have namespaces) of possible methods - maybe defining them like here: https://github.com/ethereum/execution-apis/blob/main/src/eth/block.yaml - so we could cover e.g. the case of having one method_name with different parameters
This could even be "backward compatible" - so the id could just be the method name in the default case. Just if there are 2 behaviors the id could be e.g. <method_name>_clientXYZ - so we can have the same method name with different call parameters.

Copy link
Collaborator Author

@bumblefudge bumblefudge Feb 10, 2023

Choose a reason for hiding this comment

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

I agree that discrepancies in APIs is an issue worth attacking-- my counterargument, though, is that CASA shouldn't host or run the registry that attacks it! I think the way 211 is written, it presumes that, for example, if geth or erigon use slightly different versions of a method, they can each publish rpcDocuments somewhere canonical* and a dapp can request the the geth RPC doc be prioritized over the execution-apis RPC doc, or a list of Geth clients be requested as rpcEndpoints. the fact that they're ordered arrays allows the wallet a lot of freedom in how it replies to that request, beyond just y/n.

* = that "somewhere canonical" is probably for the EF to decide, right? it could be an eip155-wide registry or domain controlled by a central party, or it could just be https://github.com/ethereum/go-ethereum-rpcDocs/ ; I tried to write 211 such that RPC docs are just URLs, and trust decisions about categorizing or sorting those can happen based on origins, longevity, etc.

Copy link
Member

Choose a reason for hiding this comment

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

Actually I disagree with this as clients should not be changing behavior for methods without renaming them

If a client behaves differently for example with eth_call then the clients need to coordinate in a standard spec or the method is renamed

adding a client suffix is only going to incentivize more fragmentation and less collaboration

if clients created this problem then they should fix it because they are mis-implementing methods

can you imagine if every wallet implemented eth_sendTransaction differently??

Copy link
Member

Choose a reason for hiding this comment

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

I agree it is not ideal - but it is reality and something we need to deal with unfortunately. What is your counter-proposal to deal with the situation?

Copy link
Member

Choose a reason for hiding this comment

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

At least with this solution we can detect with a handshake that something would fail and maybe can fallback or handle it gracefully instead of having weird unpredictable errors.

Copy link
Collaborator Author

@bumblefudge bumblefudge Feb 21, 2023

Choose a reason for hiding this comment

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

the way I see it, there does not need to be a registry of multiple RPC documents-- there is the one RPC document defined by execution-apis, which namespaces/eip155/caip211 can define as the "default" definition of any methods authorized in eip155 scope objects by CAIP-25 negotiations... and dapps and wallets are free to agree to use additional documents instead of or in addition to the namespace-wide default.

Can you make the RPC WG next week to discuss further, Ligi? We can always invite Shane back to discuss how he/the openRPC team sees it

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Merging this PR as-is, but this entire thread is still very pertinent to #211 which lays out the RPC document stuff (in a way that can be profiled for each namespace!), so let's continue the thread there!

- events - array of JSON-RPC message/events expected to be emitted during the
session

The `requiredNamespaces` array MUST contain 1 or more of these objects, if present; the `optionalNamespaces` array MUST contain 1 or more of them, if
present.
The `requiredScopes` array MUST contain 1 or more of these objects, if present;
the `optionalScopes` array MUST contain 1 or more of them, if present.

A third object is the `sessionProperties` object, all of whose properties MUST
be in the interpreted as optional, since requesting applications cannot mandate
Expand All @@ -130,21 +160,22 @@ The wallet can respond to this method with either a success result or an error m
The succesfull reslt contains one mandatory string (keyed as `sessionId` with a value
conformant to [CAIP-171][]) and two session objects, both mandatory and non-empty.

The first is called `sessionNamespaces` and contains 1 or more namespace objects.
* All required namespaces and all, none, or some of the optional namespaces (at the
discretion of the provider) MUST be included if successful.
* As in the request, each namespace object MUST contain `methods` and `events` objects,
and a `chains` object if a specific chain is not specified in the object's index.
* Unlike the request, each namespace object MUST also contain an `accounts` array,
containing 0 or more [CAIP-10][] conformant accounts authorized for the session and valid
in the namespace and chain(s) authorized by the object they are in. Additional constraints
on the accounts authorized for a given session MAY be specified in the corresponding
[namespaces][] specification.

A `sessionProperties` object MAY also be present, and its contents MAY correspond to the
properties requested in the response or not (at the discretion of the provider) but MUST
conform to the properties names and value constraints described in [CAIP-170][]; any other
MUST be dropped by the requester.
The first is called `sessionScopes` and contains 1 or more scope objects.
* All required scope objects and all, none, or some of the optional scope object
(at the discretion of the provider) MUST be included if successful.
* As in the request, each scope object object MUST contain `methods` and
`events` objects, and a `chains` object if a specific chain is not specified in
the object's index.
* Unlike the request, each scope object MUST also contain an `accounts` array,
containing 0 or more [CAIP-10][] conformant accounts authorized for the session
and valid in the namespace and chain(s) authorized by the scope object they are
in. Additional constraints on the accounts authorized for a given session MAY be
specified in the corresponding [CAIP-104][] namespaces specification.

A `sessionProperties` object MAY also be present, and its contents MAY
correspond to the properties requested in the response or not (at the discretion
of the provider) but MUST conform to the property names and value constraints
described in [CAIP-170][]; any other MUST be dropped by the requester.

An example of a successful response follows:

Expand All @@ -154,7 +185,7 @@ An example of a successful response follows:
"jsonrpc": "2.0",
"result": {
"sessionId": "0xdeadbeef",
"sessionNamespaces": {
"sessionScopes": {
"eip155": {
"chains": ["eip155:1", "eip155:137"],
"methods": ["eth_sendTransaction", "eth_signTransaction", "get_balance", "eth_sign", "personal_sign"]
Expand Down Expand Up @@ -196,15 +227,15 @@ An example of an error response should match the following format:
"jsonrpc": "2.0",
"error": {
"code": 5000,
"message": "User disapproved requested chains"
"message": "Unknown error"
}
}
```

The valid error messages codes are the following:
* When user disapproves exposing accounts to requested chains
* Unknown error OR no requested scopes were authorized
* code = 5000
* message = "User disapproved requested chains"
* message = "Unknown error"
* When user disapproves accepting calls with the request methods
* code = 5001
* message = "User disapproved requested methods"
Expand All @@ -229,9 +260,61 @@ The valid error messages codes are the following:
* Invalid Session Properties Object
* code = 5200
* message = "Invalid Session Properties requested"
* Required Session Properties
* Session Properties requested outside of Session Properties Object
* code = 5201
* message = "Session Properties can only be optional"
* message = "Session Properties can only be optional and global"

## Security Considerations

The crucial security function of a shared session negotiated and maintained by a
series of CAIP-25 calls is to reduce ambiguity in authorization. This requires
a potentially counterintuitive structuring of the building-blocks of a
Chain-Agnostic session into scopes at the "namespace-wide" ([CAIP-104][]) or at
the "chain-specific" ([CAIP-2][]) level; for this reason, requests and responses
are structures as arrays of objects keyed to these scopes, formatted either as a
[CAIP-104][] scheme OR as a full [CAIP-2][]. While internal systems are free to
translate this object into other structures, preserving it in the CAIP-25
interface is crucial to the unambiguous communication between caller and
respondent about what exact authorization is granted.

## Privacy Considerations

One major risk in browser-based or HTTP-based communications is "fingerprinting
risk", i.e. the risk that public or intercepted traffic can be used to
deanonymize browsers and/or wallets deductively based on response times, error
codes, etc. To minimize this risk, and to minimize the data (including
behavioral data) leaked by responses to potentially malicious CAIP-25 calls,
respondents are recommended to ignore calls
1. which the respondent does not authorize,
2. which are rejected by policy, or
3. requests which are rejected for unknown reasons.

"Ignoring" these calls means responding to all three in a way that is
*indistinguishable* to a malicious caller or observer which might deduce
information from differences in those responses (including the time taken to
provide them). Effectively, this means allowing requests in all three cases to
time out even if the end-user experience might be better served by
differentiating them, particularly in complex multi-party architectures where
parties on one side of this interface need to have a shared understanding of why
a request did not receive a response. At scale, however, better user experiences in a single architecture or context can contribute to a systemic erosion of anonymity.

Given this "silent time out" behavior, the best strategy to ensure good user
experience is not to request too many properties in the initial establishment of
a session and to iteratively and incrementally expand session authorization over
time. This also contributes to a more consentful experience overall and
encourages progressive trust establishment across complex architectures with
many distinct actors and agents.

Another design pattern that accomodates the "silent time out" behavior is minor
updates to the session. For example, a caller sending a request identical to a
previous request (or a previous response) except for a new session expiry
further in the future could expect one of exactly three responses:
1. An identical response to the previous request (meaning the session extension was denied);
2. A response identical expect that it includes the new, extended session expiry; or,
3. A silent time out (meaning the calling behavior was malformed in ways the
respondent cannot understand, or the respondent choses not to make explicit how
the request was malformed, or the end-user rejected them, or the request itself
was in violation of policy).

## Changelog

Expand All @@ -251,6 +334,7 @@ The valid error messages codes are the following:
[CAIP-10]: https://chainagnostic.org/CAIPs/caip-10
[CAIP-25]: https://chainagnostic.org/CAIPs/caip-25
[CAIP-75]: https://chainagnostic.org/CAIPs/caip-75
[CAIP-104]: https://chainagnostic.org/CAIPs/caip-104
[CAIP-171]: https://chainagnostic.org/CAIPs/caip-171
[namespaces]: https://namespaces.chainagnostic.org
[RFC3339]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
Expand Down
15 changes: 10 additions & 5 deletions caip-template.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
# Every document starts with a front matter in YAML enclosed by triple dashes.
# See https://jekyllrb.com/docs/front-matter/ to learn more about this concept.
caip: <to be assigned>
caip: CAIP-X <X will be changed to the PR number if accepted>
title: <CAIP title>
author: <a list of the author's or authors' name(s) and/or username(s), or name(s) and email(s), e.g. (use with the parentheses or triangular brackets): FirstName LastName (@GitHubUsername), FirstName LastName <foo@bar.com>, FirstName (@GitHubUsername) and GitHubUsername (@GitHubUsername)>
discussions-to: <URL>
Expand Down Expand Up @@ -40,14 +40,19 @@ The technical specification should describe the standard in detail. The specific
<!--The rationale fleshes out the specification by describing what motivated the design and why particular design decisions were made. It should describe alternate designs that were considered and related work, e.g. how the feature is supported in other languages. The rationale may also provide evidence of consensus within the community, and should discuss important objections or concerns raised during discussion.-->
The rationale fleshes out the specification by describing what motivated the design and why particular design decisions were made. It should describe alternate designs that were considered and related work, e.g. how the feature is supported in other languages. The rationale may also provide evidence of consensus within the community, and should discuss important objections or concerns raised during discussion.-->

## Test Cases
<!--Please add diverse test cases here if applicable. Any normative definition of an interface requires test cases to be implementable. -->

## Security Considerations
<!--Please add an explicit list of intra-actor assumptions and known risk factors if applicable. Any normative definition of an interface requires these to be implementable; assumptions and risks should be at both individual interaction/use-case scale and systemically, should the interface specified gain ecosystem-namespace adoption. -->

## Privacy Considerations
<!--Please add an explicit list of intra-actor assumptions and known risk factors if applicable. Any normative definition of an interface requires these to be implementable; assumptions and risks should be at both individual interaction/use-case scale and systemically, should the interface specified gain ecosystem-namespace adoption. -->

## Backwards Compatibility
<!--All CAIPs that introduce backwards incompatibilities must include a section describing these incompatibilities and their severity. The CAIP must explain how the author proposes to deal with these incompatibilities. CAIP submissions without a sufficient backwards compatibility treatise may be rejected outright.-->
All CAIPs that introduce backwards incompatibilities must include a section describing these incompatibilities and their severity. The CAIP must explain how the author proposes to deal with these incompatibilities. CAIP submissions without a sufficient backwards compatibility treatise may be rejected outright.

## Test Cases
<!--Please add test cases here if applicable.-->
Please add test cases here if applicable.

## Links
<!--Links to external resources that help understanding the CAIP better. This can e.g. be links to existing implementations.-->
Links to external resources that help understanding the CAIP better. This can e.g. be links to existing implementations.
Expand Down