diff --git a/spec/_diagrams/1.0/authorize_and_sign.plantuml b/spec/_diagrams/1.0/authorize_and_sign.plantuml new file mode 100644 index 000000000..5edeb7d87 --- /dev/null +++ b/spec/_diagrams/1.0/authorize_and_sign.plantuml @@ -0,0 +1,53 @@ +@startuml + +!theme cerulean +skinparam backgroundColor #FFFFFF + +hide footbox + +participant Solana as "Solana\nnetwork" +participant Dapp +participant Wallet + +note across : Dapp has established a session with Wallet for the first time + +activate Dapp + +== Authorize for transaction signing == + +Dapp -> Dapp : i = DappIdentity() +Dapp -> Wallet ++-- : authorize {i, ["sign_transactions"]} +Wallet -> Wallet : validate Dapp identity i +Wallet -> Wallet : Generate new auth token\nat = GenerateAuthToken(i, ["sign_transactions"]) +Wallet -> Wallet : pub = base58encode(GetPublicKey()) +alt if wallet endpoint has an endpoint-specific URI + Wallet -> Wallet : uri = EndpointBaseURI() +end +Wallet --> Dapp ++-- : response {at, pub, uri} +Dapp -> Dapp : Store(at, pub, uri) + +== Submit transaction for signing == + +Dapp -> Dapp : t = CreateTransaction(pub, ...) +Dapp -> Wallet ++-- : sign_transactions {at, t} +Wallet -> Wallet : validate auth token at +Wallet -> Wallet : validate transaction t +Wallet -> Wallet : sign transaction\nst = SignSolanaTransactions(t) +Wallet --> Dapp ++-- : response {st} + +== Submit signed transaction to network == + +create Solana +Dapp --> Solana -- : {st} + +== Complete == + +legend +| i | the dapp identity (name, icon, website URI) | +| at | a persistent authentication token issued by the wallet to this dapp | +| pub | the wallet account public key for which this dapp is authorized | +| t | the transaction that the dapp wishes to have signed\nwith the private key corresponding to pub | +| st | t, signed with the private key corresponding to pub | +end legend + +@enduml diff --git a/spec/_diagrams/1.0/reauthorize_and_sign.plantuml b/spec/_diagrams/1.0/reauthorize_and_sign.plantuml new file mode 100644 index 000000000..127be4f4b --- /dev/null +++ b/spec/_diagrams/1.0/reauthorize_and_sign.plantuml @@ -0,0 +1,62 @@ +@startuml + +!theme cerulean +skinparam backgroundColor #FFFFFF + +hide footbox + +participant Solana as "Solana\nnetwork" +participant Dapp +participant Wallet + +note across : Dapp has established a session with Wallet for transaction signing using a previously obtained auth token + +activate Dapp + +Dapp -> Dapp : at, pub = Load() +Dapp -> Dapp : t = CreateTransaction(pub, ...) + +== Submit for signing with cached auth token == + +Dapp -> Wallet ++-- : sign_transactions {at, t} +Wallet -X Wallet : validate auth token at +Wallet --> Dapp ++-- : error {ERROR_REAUTHORIZE} + +== Reauthorize == + +Dapp -> Dapp : i = DappIdentity() +Dapp -> Wallet ++-- : reauthorize {i, at} +Wallet -> Wallet : validate Dapp identity i matches at +Wallet -> Wallet : Generate new auth token\nat2 = RegenerateAuthToken(i, at) +Wallet -> Wallet : pub = base58encode(GetPublicKey()) +alt if wallet endpoint has an endpoint-specific URI + Wallet -> Wallet : uri = EndpointBaseURI() +end +Wallet --> Dapp ++-- : response(at2, pub, uri) +Dapp -> Dapp : Store(at2, pub, uri) + +== Resubmit for signing with new auth token == + +Dapp -> Wallet ++-- : sign_transactions(at2, t) +Wallet -> Wallet : validate auth token at2 +Wallet -> Wallet : validate transaction t +Wallet -> Wallet : sign transaction\nst = SignSolanaTransactions(t) +Wallet --> Dapp ++-- : response(st) + +== Submit signed transaction to network == + +create Solana +Dapp --> Solana -- : {st} + +== Complete == + +legend +| i | the dapp identity (name, icon, website URI) | +| at | a persistent authentication token issued by the wallet to this dapp which is no longer valid | +| at2 | a persistent authentication token with the same privileges as at which is valid | +| pub | the wallet account public key for which this dapp is authorized | +| t | the transaction that the dapp wishes to have signed\nwith the private key corresponding to pub | +| st | t, signed with the private key corresponding to pub | +end legend + +@enduml diff --git a/spec/_diagrams/authorize_and_sign.plantuml b/spec/_diagrams/authorize_and_sign.plantuml index 5edeb7d87..1605f88a2 100644 --- a/spec/_diagrams/authorize_and_sign.plantuml +++ b/spec/_diagrams/authorize_and_sign.plantuml @@ -16,7 +16,8 @@ activate Dapp == Authorize for transaction signing == Dapp -> Dapp : i = DappIdentity() -Dapp -> Wallet ++-- : authorize {i, ["sign_transactions"]} +Dapp -> Dapp : c = "{namespace}:{chain_reference}" +Dapp -> Wallet ++-- : authorize {i, c} Wallet -> Wallet : validate Dapp identity i Wallet -> Wallet : Generate new auth token\nat = GenerateAuthToken(i, ["sign_transactions"]) Wallet -> Wallet : pub = base58encode(GetPublicKey()) @@ -29,8 +30,8 @@ Dapp -> Dapp : Store(at, pub, uri) == Submit transaction for signing == Dapp -> Dapp : t = CreateTransaction(pub, ...) -Dapp -> Wallet ++-- : sign_transactions {at, t} -Wallet -> Wallet : validate auth token at +Dapp -> Wallet ++-- : sign_transactions {t} +Wallet -> Wallet : verify session is in authorized state Wallet -> Wallet : validate transaction t Wallet -> Wallet : sign transaction\nst = SignSolanaTransactions(t) Wallet --> Dapp ++-- : response {st} diff --git a/spec/_diagrams/reauthorize_and_sign.plantuml b/spec/_diagrams/reauthorize_and_sign.plantuml index 127be4f4b..acc5cb331 100644 --- a/spec/_diagrams/reauthorize_and_sign.plantuml +++ b/spec/_diagrams/reauthorize_and_sign.plantuml @@ -18,14 +18,15 @@ Dapp -> Dapp : t = CreateTransaction(pub, ...) == Submit for signing with cached auth token == -Dapp -> Wallet ++-- : sign_transactions {at, t} -Wallet -X Wallet : validate auth token at -Wallet --> Dapp ++-- : error {ERROR_REAUTHORIZE} +Dapp -> Wallet ++-- : sign_transactions {t} +Wallet -X Wallet : verify session is in authorized state +Wallet --> Dapp ++-- : error {ERROR_AUTHORIZATION_FAILED} == Reauthorize == Dapp -> Dapp : i = DappIdentity() -Dapp -> Wallet ++-- : reauthorize {i, at} +Dapp -> Dapp : c = "{namespace}:{chain_reference}" +Dapp -> Wallet ++-- : authorize {i, c, at} Wallet -> Wallet : validate Dapp identity i matches at Wallet -> Wallet : Generate new auth token\nat2 = RegenerateAuthToken(i, at) Wallet -> Wallet : pub = base58encode(GetPublicKey()) @@ -37,8 +38,8 @@ Dapp -> Dapp : Store(at2, pub, uri) == Resubmit for signing with new auth token == -Dapp -> Wallet ++-- : sign_transactions(at2, t) -Wallet -> Wallet : validate auth token at2 +Dapp -> Wallet ++-- : sign_transactions(t) +Wallet -> Wallet : verify session is in authorized state Wallet -> Wallet : validate transaction t Wallet -> Wallet : sign transaction\nst = SignSolanaTransactions(t) Wallet --> Dapp ++-- : response(st) diff --git a/spec/spec.md b/spec/spec.md index c38c11664..ddd8049f2 100644 --- a/spec/spec.md +++ b/spec/spec.md @@ -16,7 +16,7 @@ Please don't introduce unnecessary line breaks in this specification - it's diff This specification uses [semantic versioning](https://en.wikipedia.org/wiki/Software_versioning#Semantic_versioning) -**Version: 1.0.0** +**Version: 2.0.0-DRAFT** ## Changelog (oldest to newest) @@ -102,6 +102,37 @@ _reflector_ - an intermediary which brokers connections between two endpoints, w _wallet endpoint_ - an app implementing wallet-like functionality (i.e. providing transaction signing services). This endpoint acts as the server in this protocol. +## Identifiers + +Namespaced identifiers are used in the format `${namespace}:${reference}` to refer to objects like blockchains and features in a canonical and human readable form. + +### Chain Identifiers + +Namespaced chain identifiers are used to refer to blockchains with which a dapp intends to interact with. This scheme is compatible with [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md) chain IDs, but are not required to be used. + +The currently supported Solana network chains are `solana:mainnet`, `solana:testnet`, and `solana:devnet`. + +Previous versions of this specification used a `cluster` parameter to specify the Solana network cluster with which the dapp endpoint intends to interact. The supported values for the `cluster` parameter were `mainnet-beta`, `testnet`, and `devnet`. These values have been replaced by the above chain references, but will continue to be supported in the interest of backwards compatibility with previous versions of the specification. + +### Feature Identifiers + +Feature identifiers are used to identify features that are supported by a wallet or account. The namespaces `solana` and `experimental` are reserved for use by the core protocol specification. The `solana` namespace includes the currently defined standard mandatory and optional features features. Features under the `experimental` namespace are optional. + +#### Mandatory Features + +These features are mandatory and must be implemented by wallet endpoints. Dapp endpoints can assume that these features are always available without an explicit call to [`get_capabilities`](#get_capabilities). + +- [`solana:signMessages`](#sign_messages) +- [`solana:signTransactions`](#sign_transactions) + +#### Optional Features + +These features are optional, wallet endpoints may choose to implement these features. Dapp endpoints can check if a wallet supports these features by calling [`get_capabilities`](#get_capabilities). + +- [`solana:signAndSendTransaction`](#sign_and_send_transactions), an optional Wallet RPC method. +- `solana:signInWithSolana`, an optional extension to the [`authorize`](#authorize) method. +- [`solana:cloneAuthorization`](#clone_authorization) + ## Transport ### WebSockets @@ -130,18 +161,25 @@ Association is the process of establishing a shared association identifier betwe The dapp endpoint should generate an ephemeral EC keypair on the P-256 curve, and encode the public keypoint Qa using the X9.62 public key format `(0x04 || x || y)`. This public keypoint is then base64url-encoded, and the resulting string is called the association token. The private keypoint for this keypair will be used during session establishment. +### Protocol Version Negotiation + +When establishing a session, dapp and wallet endpoints must agree on a version of the protocol to be used for the session. Association URIs include a protocol version in the first path segment (`solana-wallet:/v1/...`) to indicate the version of the protocol and corresponding behaviors used for the session. Any future revisions to this specification that introduce protocol breaking changes must increment the version field in the association URI. + +A version query parameter is used in the association uris below to allow dapp endpoints to specify the major version(s) of the protocol that the client supports. If this version parameter is present in the association URI, the wallet endpoint should additionally send a [session properties](#session-properties) payload during session establishment to indicate the negotiated version to the client. If no version query parameter is present, the connection is assumed to be a legacy connection (v1). + ### Local URI When running on Android or iOS, the dapp endpoint should first attempt to associate with a local wallet endpoint by opening a URI (either from within the browser for a web dapp, or directly from a native dapp) with the `solana-wallet:` scheme. The URI should be formatted as: ``` -solana-wallet:/v1/associate/local?association=&port= +solana-wallet:/v1/associate/local?association=&port=&v= ``` where: - `association_token` is as described above - `port_number` is a random number between 49152 and 65535 +- `version` is the major version of the protocol that the client supports. This value can be repeated to specify multiple major versions supported by the client. The wallet endpoint should select the highest version from this set that it supports. Once the URI is opened, the dapp endpoint should attempt to connect to the local WebSocket address, `ws://localhost:/solana-wallet`, and proceed to [Session establishment](#session-establishment). @@ -166,7 +204,7 @@ Since desktop OSes do not generally allow launching an app with a URI, a dapp en When running on a desktop OS, or when connecting to a local wallet endpoint fails, the dapp endpoint may present a URI suitable for connection via a [reflector WebSocket server](#reflector-protocol), which will reflect traffic between two parties. The URI should be formatted as: ``` -solana-wallet:/v1/associate/remote?association=&reflector=&id= +solana-wallet:/v1/associate/remote?association=&reflector=&id=&v= ``` where: @@ -174,6 +212,7 @@ where: - `association_token` is as described above - `host_authority` is the address of a publicly routable WebSocket server implementing the reflector protocol - `reflector_unique_id` is a number generated securely at random by the dapp endpoint, 0 ≤ n ≤ 2^53 - 1 +- `version` is the major version of the protocol that the client supports, as described above This URI should be provided to the wallet endpoint through an out-of-band mechanism, detailed in the subsections below. Each of the dapp and wallet endpoints should attempt to connect to the WebSocket address `wss:///reflect?id=`. On connection, each endpoint should wait for the [Reflector protocol](#reflector-protocol) to signal that the counterparty endpoint has connected. @@ -270,11 +309,33 @@ Once each endpoint has calculated the ephemeral shared secret, they should proce If either public keypoint `Qd` or `Qw` is not valid, if no `HELLO_RSP` message is received by the dapp endpoint within no less than 10 seconds, or if a second `HELLO_RSP` message is received by the dapp endpoint at any time during the connection, all ephemeral key materials should be discarded, and the connection should be closed. +### SESSION_PROPS + +#### Direction + +Wallet endpoint to dapp endpoint + +#### Specification + +``` +{ + "v":"" +} +``` + +where: + +- `version`: is the major version of the protocol in use for the session. This is expected to be the highest protocol version supported by both dapp and wallet endpoints, as specified [during association](#protocol-version-negotiation) + +#### Description + +If a `v=` query parameter is present in the association URI presented by the dapp endpoint, the wallet endpoint MUST send a `SESSION_PROPS` message to the client immediately after the `HELLO_RSP` message. If the connecting dapp endpoint runs software for a legacy version of this standard (i.e. no `v=` parameter present during association), the wallet MUST NOT send the `SESSION_PROPS` message to the client, and must assume that the connection is a legacy connection. If the wallet endpoint does not support legacy connections, it should close the connection immediately upon receipt of the `HELLO_REQ` message. + ## Wallet RPC interface ### Operation -After [session establishment](#session-establishment) completes, the wallet endpoint is ready to accept [JSON-RPC 2.0](https://www.jsonrpc.org/specification) non-privileged method calls from the dapp endpoint. To invoke privileged methods, a dapp endpoint must first put the session into an authorized state via either an [`authorize`](#authorize) or a [`reauthorize`](#reauthorize) method call. For details on how a session enters and exits an authorized state, see the [non-privileged methods](#non-privileged-methods). +After [session establishment](#session-establishment) completes, the wallet endpoint is ready to accept [JSON-RPC 2.0](https://www.jsonrpc.org/specification) non-privileged method calls from the dapp endpoint. To invoke privileged methods, a dapp endpoint must first put the session into an authorized state via an [`authorize`](#authorize) method call. For details on how a session enters and exits an authorized state, see the [non-privileged methods](#non-privileged-methods). ### Encrypted message wrapping @@ -321,7 +382,10 @@ authorize “icon”: “”, “name”: “”, }, - "cluster": "", + "chain": "", + “auth_token”: “”, + "sign_in_payload": , + "cluster": "" } ``` @@ -331,7 +395,10 @@ where: - `uri`: (optional) a URI representing the web address associated with the dapp endpoint making this authorization request. If present, it must be an absolute, hierarchical URI. - `icon`: (optional) a relative path (from `uri`) to an image asset file of an icon identifying the dapp endpoint making this authorization request - `name`: (optional) the display name for this dapp endpoint -- `cluster`: (optional) if set, the Solana network cluster with which the dapp endpoint intends to interact; supported values include `mainnet-beta`, `testnet`, `devnet`. If not set, defaults to `mainnet-beta`. +- `chain`: (optional) if set, the [chain identifier](#chain-identifiers) for the chain with which the dapp endpoint intends to interact; supported values include `solana:mainnet`, `solana:testnet`, `solana:devnet`, `mainnet-beta`, `testnet`, `devnet`. If not set, defaults to `solana:mainnet`. +- `auth_token`: (optional) an opaque string previously returned by a call to [`authorize`](#authorize), or [`clone_authorization`](#clone_authorization). When present, the wallet endpoint should attempt to reauthorize the dapp endpoint silently without prompting the user. +- `sign_in_payload`: (optional) a value object containing the payload portion of a [Sign In With Solana message](https://siws.web3auth.io/spec). If present, the wallet endpoint should present the SIWS message to the user and return the resulting signature and signed message, as described below. +- `cluster`: (optional) an alias for `chain`. This parameter is maintained for backwards compatibility with previous versions of the spec, and will be ignored if the `chain` parameter is present. ###### Result {: .no_toc } @@ -340,81 +407,74 @@ where: { “auth_token”: “”, “accounts”: [ - {“address”: “
", “label”: “