diff --git a/ICRCs/ICRC-21/ICRC-21.did b/ICRCs/ICRC-21/ICRC-21.did new file mode 100644 index 00000000..69cf9461 --- /dev/null +++ b/ICRCs/ICRC-21/ICRC-21.did @@ -0,0 +1,173 @@ +// Canister interface specification for ICRC-21. +// See https://github.com/dfinity/wg-identity-authentication/blob/main/topics/ICRC-21/icrc_21_consent_msg.md + +type icrc21_consent_message_metadata = record { + // BCP-47 language tag. See https://www.rfc-editor.org/rfc/bcp/bcp47.txt + language: text; + + // The users local timezone offset in minutes from UTC. + // Applicable when converting timestamps to human-readable format. + // + // If absent in the request, the canister should fallback to the UTC timezone when creating the consent message. + // If absent in the response, the canister is indicating that the consent message is not timezone sensitive. + utc_offset_minutes: opt int16; +}; + +type icrc21_consent_message_spec = record { + // Metadata of the consent message. + metadata: icrc21_consent_message_metadata; + + // Information about the device responsible for presenting the consent message to the user. + // If absent in the request, the canister should fallback to one of the values defined in this spec (ICRC-21). + device_spec: opt variant { + // A generic display able to handle large documents and do line wrapping and pagination / scrolling. + // Text must be Markdown formatted, no external resources (e.g. images) are allowed. + GenericDisplay; + // A simple display able to handle multiple fields with a title and content. + // It's able to do line wrapping and splits fields into multiple pages if they're too long. + // Text must be plain text without any embedded formatting elements. + FieldsDisplay; + }; +}; + +type icrc21_consent_message_request = record { + // Method name of the canister call. + method: text; + // Argument of the canister call. + arg: blob; + // User preferences with regards to the consent message presented to the end-user. + user_preferences: icrc21_consent_message_spec; +}; + +type TokenAmount = record { + decimals: nat8; + amount: nat64; + symbol: text; +}; + +type TimestampSeconds = record { + amount: nat64; +}; + +type DurationSeconds = record { + amount: nat64; +}; + +type TextValue = record { + content: text; +}; + +type Value = variant { + TokenAmount: TokenAmount; + TimestampSeconds: TimestampSeconds; + DurationSeconds: DurationSeconds; + Text: TextValue; +}; + +type icrc21_consent_message = variant { + // Message for a generic display able to handle large documents and do proper line wrapping and pagination / scrolling. + // Uses Markdown formatting, no external resources (e.g. images) are allowed. + GenericDisplayMessage: text; + // Message for a simple display able to handle multiple fields title and content. + // It's able to do line wrapping and splits fields into multiple pages if they're too long. + // Uses plain text, without any embedded formatting elements. + FieldsDisplayMessage: record { + // Context and type of canister call, accurate and concise e.g. Send ICP + intent: text; + // Canister call fields for review e.g. Amount 234.73 ICP + fields: vec record { text; Value }; + }; +}; + +type icrc21_consent_info = record { + // Consent message describing in a human-readable format what the call will do. + // + // The message should adhere as close as possible to the user_preferences specified in the consent_message_spec + // of the icrc21_consent_message_request. + // If the message is not available for the given user_preferences any fallback message should be used. Providing a + // message should be preferred over sending an icrc21_error. + // The metadata must match the consent_message provided. + // + // The message should be short and concise. + // It should only contain information that is: + // * relevant to the user + // * relevant given the canister call argument + // + // The message must fit the following context shown to + // the user on the signer UI: + // ┌─────────────────────────────────┐ + // │ Approve the following action? │ + // │ ┌───────────────────────────┐ │ + // │ │ │ │ + // │ └───────────────────────────┘ │ + // │ ┌───────────┐ ┌───────────┐ │ + // │ │ Reject │ │ Approve │ │ + // │ └───────────┘ └───────────┘ │ + // └─────────────────────────────────┘ + consent_message: icrc21_consent_message; + // Metadata of the consent_message. + metadata: icrc21_consent_message_metadata; +}; + +type icrc21_error_info = record { + // Human readable technical description of the error intended for developers, not the end-user. + description: text; +}; + +type icrc21_error = variant { + // The canister does not support this call (i.e. it will lead to a rejection or error response). + // Reasons might be (non-exhaustive list): + // * the canister call is malformed (e.g. wrong method name, argument cannot be decoded) + // * the arguments exceed certain bounds + // + // The developer should provide more information about the error using the description in icrc21_error_info. + UnsupportedCanisterCall: icrc21_error_info; + + // The canister cannot produce a consent message for this call. + // Reasons might be (non-exhaustive list): + // * it is an internal call not intended for end-users + // * the canister developer has not yet implemented a consent message for this call + // + // The developer should provide more information about the error using the description in icrc21_error_info. + ConsentMessageUnavailable: icrc21_error_info; + + // The canister did not provide a consent message for because payment was missing or insufficient. + // + // This error is used to account for payment extensions to be added in the future: + // While small consent messages are easy and cheap to provide, this might not generally be the case for all consent + // messages. To avoid future breaking changes, when introducing a payment flow, this error is already introduced + // even though there no standardized payment flow yet. + InsufficientPayment: icrc21_error_info; + + // Any error not covered by the above variants. + GenericError: record { + // Machine parsable error. Can be chosen by the target canister but should indicate the error category. + error_code: nat; + // Human readable technical description of the error intended for developers, not the end-user. + description: text; + }; +}; + +type icrc21_consent_message_response = variant { + // The call is ok, consent message is provided. + Ok: icrc21_consent_info; + // The call is not ok, error is provided. + Err: icrc21_error; +}; + +service : { + // Returns a human-readable consent message for the given canister call. + // + // This call must not require authentication (i.e. must be available for the anonymous sender). + // If the call is made with a non-anonymous identity, the response may be tailored to the identity. + // + // This is currently an update call. As soon as secure (replicated) query calls are available, this will be changed to such a replicated query call. + icrc21_canister_call_consent_message: (icrc21_consent_message_request) -> (icrc21_consent_message_response); + + // Returns a list of supported standards that this canister implements. + // The result must include an entry for ICRC-21: + // record { name = "ICRC-21"; url = "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-21/ICRC-21.md" } + // + // See ICRC-10 for more information: https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-10/ICRC-10.md + icrc10_supported_standards : () -> (vec record { name : text; url : text }) query; +} diff --git a/ICRCs/ICRC-21/ICRC-21.md b/ICRCs/ICRC-21/ICRC-21.md index eef53fe1..f6657fc7 100644 --- a/ICRCs/ICRC-21/ICRC-21.md +++ b/ICRCs/ICRC-21/ICRC-21.md @@ -1,5 +1,237 @@ -ICRC-21 is still in draft status. Once finalized, the ICRC-21 will be available here. +# ICRC-21: Canister Call Consent Messages -To read [the draft](https://github.com/dfinity/wg-identity-authentication/blob/main/topics/ICRC-21/icrc_21_consent_msg.md) and join the discussion please visit the ["Identity and Wallet Standards" working group repository](https://github.com/dfinity/wg-identity-authentication). +![APPROVED] -For historical changes and discussions please see the git history of the draft file. +**Authors:** [Frederik Rothenberger](https://github.com/frederikrothenberger), [Thomas Gladdines](https://github.com/sea-snake) + +## Summary +This specification describes a protocol for obtaining human-readable consent messages for canister calls. These messages are intended to be shown to users to help them make informed decisions about whether to approve a canister call / sign a transaction. + +The protocol is designed in such a way that it can be used interactively (e.g. in a browser-based signer) or non-interactively (e.g. in a cold signer). + +## Terminology + +* signer: a service that manages the keys of a user and can sign canister calls on their behalf. This component displays the consent message to the user. The signer may be split in a hot (with connection to the IC) and cold (offline) component. +* relying party: the service that requests a signature on a specific canister call. +* target canister: the canister that is the target of the canister call. + +## Motivation + +Interactions with canisters can have significant consequences. For example, a canister might transfer funds or provide access to sensitive information. At the same time interactions with canisters are often technical and require a deep understanding of the canister's implementation to accurately assess the consequences of a specific canister call. This makes it very difficult for users to make informed decisions about whether to sign a canister call. + +The mechanisms described in this specification ease the technical burden and empower users to make informed decisions before signing canister calls. + +## Assumptions +* The signer is trusted by the user. +* The relying party is _not_ trusted and might be malicious. +* The target canister is trusted by the user. Interactions with malicious canisters are not covered by this specification. In particular, interacting with a malicious canister can produce arbitrary outcomes regardless of how user consent was collected. + +## Canister Call Consent Message Interface + +The interface specified in [ICRC-21.did](ICRC-21.did) must be implemented to support canister call consent messages. +In addition to implementing this interface, it is recommended that the canister also provides its full candid interface in the public `candid:service` metadata section, as discussed [here](https://forum.dfinity.org/t/rfc-canister-metadata-standard). This is required to properly decode the arguments if the signer also wants to display technical information about the canister call. + +### Authentication + +The signer may send the `icrc21_canister_call_consent_message` call using the same identity as it would for the actual canister call for which the consent message was issued. + +Any canister implementing the `icrc21_canister_call_consent_message` interface must not require authentication for this call. Anonymous consent messages are required in the [cold signer use-case](#cold-signer-use-case) which would otherwise require two interactions with the cold signer component, making the flow very cumbersome for users. + +Canisters may add additional or different information if a non-anonymous `sender` is used. +For example, a canister might include private information in the consent message, if the call is made by the owner of that information. + +> **_WARNING:_** Canister developers must take care to not rely on the current state of the canister / identity attached data when issuing the consent message. There might be a significant time delay (depending on the signer used) between retrieving the consent message and submitting the canister call. The consent message must accurately describe all possible outcomes of the canister call, accounting for that time delay. + +> **_NOTE:_** The `icrc21_canister_call_consent_message` method is currently declared as an `update` call, due to the necessity +> of supporting dynamic data in a secure way. As soon as secure (replicated) query calls are available, this will be changed to such a replicated query call. + +## Use-Cases + +The canister call is designed to be used with both cold and hot signers. In addition to the signer use-cases it can also be used to enhance the documentation on generic canister interfaces. + +### Hot Signer Use-Case + +This section describes the interactions between the signer and the relying party for _hot_ signers: + +```mermaid +sequenceDiagram + participant RP as Relying Party + participant S as Signer + participant T as Target Canister + participant U as User + + Note over RP, S: Interactions follow ICRC-25 standard + RP ->> S: Request canister call + S ->> T: Consent message request + T ->> S: Consent message response + S ->> S: Validate consent message + alt Received consent message + S ->> U: Display consent message + else No consent message + S ->> U: Display warning + end + U ->> S: Approve / reject canister call + alt Approved + S ->> S: Sign canister call + S ->> T: Submit canister call + T ->> S: Canister call response + S ->> U: Display success / failure message + S ->> RP: Canister call response + else Rejected + S ->> RP: Rejection response + end +``` + +1. The relying party connects to the signer and requests a signature on a canister call. +2. The signer fetches the consent message from the target canister and validates the response: + * `icrc21_consent_message_request.method` must match the canister call method. + * `icrc21_consent_message_request.arg` must match the canister call argument. + * The signer must use the same identity for the `icrc21_canister_call_consent_message` request as is used for signing the canister call (in step 6). + * The `icrc21_canister_call_consent_message` canister call must be made to the target canister. + * The response to the `icrc21_canister_call_consent_message` canister call (fetched using `read_state`) must be delivered in a valid certificate (see [Certification](https://internetcomputer.org/docs/current/references/ic-interface-spec#certification)). + * The decoded response must not be `null` and match the `icrc21_consent_message_response::OK` variant. +3. The consent message is presented to the user. +4. User approval: + * If the user approves the canister call, continue with step 5. + * If the user rejects the canister call (or does not respond within a certain time frame), the signer returns an error to the relying party. No further steps are executed. +5. The request is signed, submitted to the IC. +6. The certified response is retrieved using read state requests +7. A message should be displayed to the user that indicates whether the canister call was successful or not. +8. The response returned to the relying party. + +### Cold Signer Use-Case + +This section describes the interactions between the signer and the relying party for signers that have a _cold_ component: +* the cold signer component is not able to interact with canisters +* the cold signer component has no knowledge of time + +```mermaid +sequenceDiagram + participant RP as Relying Party + participant S as Chain Connected Signer Component + participant CS as Cold Signer Component + participant T as Target Canister + participant U as User + + Note over RP, S: Interactions follow ICRC-25 standard + RP ->> S: Request canister call + S ->> T: Consent message request + T ->> S: Consent message response + S ->> CS: - Canister call request
- Consent message request and response + CS ->> CS: Validate consent message + alt Received consent message + CS ->> U: Display consent message + else No consent message + CS ->> U: Display warning + end + U ->> CS: Approve / reject canister call + alt Approved + CS ->> CS: Sign canister call + CS ->> S: Transfer signed canister call + S ->> T: Submit canister call + T ->> S: Canister call response + S ->> U: Display success / failure message + S ->> RP: Canister call response + else Rejected + S ->> RP: Rejection response + end +``` + +1. The relying party connects to the signer and requests a signature on a canister call. +2. The chain-connected signer component fetches the consent message from the target canister: + * `icrc21_consent_message_request.method` must match the canister call method. + * `icrc21_consent_message_request.arg` must match the canister call argument. + * The chain-connected signer component must use the anonymous identity for the `icrc21_canister_call_consent_message` request. + * The `icrc21_canister_call_consent_message` canister call must be made to the target canister. + * The response to the `icrc21_canister_call_consent_message` canister call (fetched using `read_state`) must be delivered in a valid certificate (see [Certification](https://internetcomputer.org/docs/current/references/ic-interface-spec#certification)). + * The decoded response must not be `null` and match the `icrc21_consent_message_response::OK` variant. +3. The canister call and the consent message request as well as the certified response are transferred to the cold signer component. +4. The cold signer component validates the consent message: + 1. The consent message request must match the canister call: + * `icrc21_consent_message_request.method` must match the canister call method. + * `icrc21_consent_message_request.arg` must match the canister call argument. + * The `icrc21_canister_call_consent_message` request `sender` must be anonymous or match the identity used to sign the canister call request (in step 7). + * The `icrc21_canister_call_consent_message` request `canister_id` must match the target canister id. + 2. The consent message response must be certified and valid: + * The response to the `icrc21_canister_call_consent_message` canister call must be provided in a valid certificate (see [Certification](https://internetcomputer.org/docs/current/references/ic-interface-spec#certification)). + * The decoded response must not be `null` and match the `icrc21_consent_message_response::Ok` variant. + 3. The consent message response certificate `time` must be recent with respect to the `ingress_expiry` of the canister call. + 4. The consent message user preferences must match the user preferences of the signer. In particular, the consent message must be in a language understood by the user. +5. If validation is successful, the consent message is presented to the user. + * Otherwise, the signer must abort the signing process and display an error message to the user. No further steps are executed. +6. User approval: + * If the user approves the canister call, continue with step 7. + * If the user rejects the canister call (or does not respond within a certain time frame), the signer returns an error to the relying party (via the chain connected component). No further steps are executed. +7. The request is signed and transferred to the chain connected component. +8. The request is submitted to the IC. +9. The certified response is retrieved using read state requests +10. A message should be displayed to the user that indicates whether the canister call was successful or not. +11. The response returned to the relying party. + +## Example + +The following is a non-normative example of how the canister call consent message interface could be used for the ledger transfer method. + +### Consent Message Request + +Argument for the ledger canister call to `transfer`: + +``` +( + record { + to = blob "ed2182..."; + fee = record { e8s = 10_000 : nat64 }; + memo = 123 : nat64; + from_subaccount = null; + created_at_time = null; + amount = record { e8s = 789_123_000 : nat64 }; + }, +) +``` + +Argument for the ledger canister call to `icrc21_canister_call_consent_message`: + +``` +( + record { + method = "transfer"; + arg = blob "4449..."; // candid encoded argument + consent_preferences = record { + language = "en-US" + } + } +) +``` + +### Consent Message Response + +``` +( + variant { + Ok = record { + consent_message = "Transfer 7.89 ICP to account ed2182..., include memo: 123. Fee: 0.0001 ICP."; + language = "en-US" + } + } +) +``` +### Signer UI Approval Screen + +The message will then be shown to the user in the following context: + +``` +┌─────────────────────────────────┐ +│ Approve the following action? │ +│ ┌───────────────────────────┐ │ +│ │ Transfer 7.89 ICP to │ │ +│ │ account ed2182..., │ │ +│ │ include memo: 123. │ │ +│ │ Fee: 0.0001 ICP. │ │ +│ └───────────────────────────┘ │ +│ ┌───────────┐ ┌───────────┐ │ +│ │ Reject │ │ Approve │ │ +│ └───────────┘ └───────────┘ │ +└─────────────────────────────────┘ +``` + +[APPROVED]: https://img.shields.io/badge/STATUS-APPROVED-ed1e7a.svg \ No newline at end of file