| tzip | 26 | |
|---|---|---|
| title | Off-Chain Message Signing | |
| status | Draft | |
| author | Klas Harrysson <klas@kukai.app>, Carlo van Driesten <carlo.van-driesten@vdl.digital> | |
| type | I | |
| created | 2024-02-21 | |
| date | 2024-03-05 | |
| requires |
|
There is no formal message signing standard for Tezos yet. Message signing has many use cases. It can for example be used for code signing, so that authenticity and integrity can be validated. Right now decentralized applications (dApps) and wallets are relying on an informal way of doing message signing by using Michelson data (magic byte 0x05), something it was never intended for. In addition the failing_noop (tag 17) was not adopted for the use of off-chain messages due to the lack of documentation. This TZIP aims to solve that problem and defines a standard that is simple, secure, extendable and compatible with hardware wallets.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
Printable ASCII: ASCII characters between 32 and 126 (inclusive).
| Name | Type |
|---|---|
| Magic string | String |
| Interface | String |
| Character encoding | Integer |
| Message | Bytes |
b"\x80tezos signed offchain message"
The first byte is the magic byte used for domain separation within the Tezos ecosystem. 0x80 was chosen and is registered in the draft-signer-requests TZIP. The remaining part is to protect against cross-chain signature replay attacks (not all wallets have this protection from bip44/slip10). It is chosen to be "long enough" and descriptive in order to make it very unlikely to have a meaning outside the context of the Tezos off-chain message signing. Any user-agents performing or verifying signatures of messages with the first byte 0x80 MUST comply with this standard.
The interface acts as a domain separator to tell a wallet or SDK developer how to distinguish different off-chain message types. The string MUST be encoded with the printable ASCII character set. This field MAY be displayed to the user. The interface MUST contain a reference to the specification defining the message. It is a URI and RECOMMENDED to be suffixed with the specifications namespace as URI fragment or OPTIONALLY with the name and number of the specification like e.g. #TZIP-10.
It is RECOMMENDED to host the specifications under a dedicated domain in order to facilitate easily human readable URIs as it is done e.g. for the Chain Agnostic Improvement Proposals (CAIPs). Tezos Improvement Proposals MAY use the following abbreviation tzip:// as prefix with the TZIP number as suffix whereas it is REQUIRED to maintain the TZIP specification at https://gitlab.com/tezos/tzip. The interface string MUST NOT be empty. The maximum length of the URI is 255 characters.
Examples:
https://gitlab.com/tezos/tzip/-/blob/proposals/tzip-XY/tzip-XY.md#OFFCHAIN-MESSAGE-SIGNING
https://chainagnostic.org/CAIPs/caip-122#CAIP-122
https://chainagnostic.org/CAIPs/caip-122#SIGN-IN-WITH-X
tzip://78
There are different kinds of encodings available for a message. If encoding Custom is used then the Interface specification MUST include the type of encoding. In certain applications e.g. a PACK encoding might be useful but this TZIP does only provide the basic options as application use cases are not foreseeable. If the encoding Custom is not supported then the dApp or wallet MUST display it as hexadecimal string.
| Id | Encoding | Hardware Wallet Support |
|---|---|---|
| 0 | Printable ASCII | Yes |
| 1 | UTF-8 | No |
| 2 | Custom | - |
This field contains the message content that the user is requested to sign. It MUST be displayed to the user. The data model and message encoding MAY be further specified through the specification in the interface.
Replay protection is out of scope for this TZIP. Implementers MUST define an interface specification protecting from replay attacks if it is relevant to the application. CAIP-122 includes suitable measures like e.g. the use of a nonce inside the message data structure.
| Name | Size | Contents |
|---|---|---|
| magic_string | 30 bytes | bytes |
| # Bytes in next field | 1 byte | unsigned 8-bit integer |
| interface | variable | bytes |
| character_encoding | 1 byte | unsigned 8-bit integer |
| # Bytes in next field | 2 bytes | unsigned 16-bit integer |
| message | Variable | bytes |
A dApp or wallet provider MUST fall back to the default interface: tzip://tbd if it does not support the provided interface specification and display the message including a warning.
- The wallet MUST enforce strict validation to ensure there is no deviation in any way from the expected data format.
- The wallet implementers MUST display the public key hash associated to the private key used for signing the message.
- Wallet implementers displaying the message as plaintext to the user SHOULD require the user to scroll to the bottom of the text area prior to signing.
message = "Hello world!"
interface = "tzip://tbd"
=>
bytes = 0x8074657a6f73207369676e6564206f6666636861696e206d6573736167650a747a69703a2f2f74626400000c48656c6c6f20776f726c6421
# Wallet
mnemonic = "all all all all all all all all all all all all"
derivation_path = m/44'/1729'/0'/0' (slip-10)
private_key = edskRgEboayXzSZHW5wK2beB4aZtfQtuc2ywwjPmSQYCg7unpVT2Sr1KUSzX9hNLJC25YcB4qZ1Wotu6EuDveWY4jkiKQr9H3k
=>
signature = edsigtvazvxVHsofbakqvqHtQGiYZBxNg8hfY45escmFpLTYeBjjBFUTt254UARm93qHpbQugGU5fmJWdf3Cm5FNMcP7oYPsa7c
- failing_noop (tag 17) - A possible solution to encode a signed message for off-chain usage.
- PACK encoding - A Michelson instruction for encoding and decoding.
This document is licensed under MIT.