Skip to main content

Off-chain message signing

Motivation

There is ecosystem demand for a method of signing non-transaction messages with a Solana wallet. Typically this is some kind of "proof of wallet ownership" for entry into a whitelisted system.

Some inspiration can be gleaned from relevant portions of Ethereum's EIP-712

Considerations

  • Security
    • Off-chain message signatures should not be valid transaction message signatures
  • Future-proofing
    • Versioning
    • Co-exist with versioned transaction messages and extended length transactions
  • Compatibility
    • Hardware wallet signing
    • Localization

Message Preamble

FieldStart offsetLength (bytes)Description
Signing Domain0x0016[1]
Header version0x101[2]
Application domain0x1132[3]
Message format0x311[4]
Signer count0x321Number of signers committing to the message. An 8-bit, unsigned integer. MUST NOT be zero.
Signers0x33SIGNER_COUNT * 32SIGNER_COUNT ed25519 public keys of signers
Message length0x33 + SIGNER_CNT * 322Length of message in bytes. A 16-bit, unsigned, little-endian integer. MUST NOT be zero.

Signing Domain Specifier

The signing domain specifier is a prefix byte string used to give similar structure to all off-chain message signatures. We assign its value to be:

b"\xffsolana offchain"

The first byte, \xff, was chosen for the following reasons

  1. It corresponds to a value that is illegal as the first byte in a transaction MessageHeader.
  2. It avoids unintentional misuse in languages with C-like, null-terminated strings.

The remaining bytes, b"solana offchain", were chosen to be descriptive and reasonably long, but are otherwise arbitrary.

This field SHOULD NOT be displayed to users

Header version

The header version is represented as an 8-bit unsigned integer. Only the version 0 header format is specified in this document

This field SHOULD NOT be displayed to users

Application domain

A 32-byte array identifying the application requesting off-chain message signing. This may be any arbitrary bytes. For instance the on-chain address of a program, DAO instance, Candy Machine, etc.

This field SHOULD be displayed to users as a base58-encoded ASCII string rather than interpreted otherwise.

Message Format

Version 0 headers specify three message formats allowing for trade-offs between compatibility and composition of messages.

IDEncodingMaximum Length *Hardware Wallet Support
0Restricted ASCII **1232Yes
1UTF-81232Blind sign only
2UTF-865535No

* Combined length of the message preamble and message body
** Those characters for which isprint(3) returns true. That is, 0x20..=0x7e.

Both the message encoding and maximum message length MUST be enforced by signer and verifier.

Formats 0 and 1 are motivated by hardware wallet support where both RAM to store the payload and font character support are limited.

This field SHOULD NOT be displayed to users

Signing

Solana off-chain messages MUST only be signed using the ed25519 digital signature scheme. Before signing, the message MUST be strictly checked to conform to the associated preamble. The message body is then appended to the message preamble. Finally the result is ed25519 signed.

Verification

Upon successful ed25519 verification of all attached signatures, the message MUST be strictly checked to conform to the message preamble. A message that does not conform to its preamble is invalid, regardless of the validity of any signatures.

Envelope

When passing around signed off-chain messages a common format is helpful. The recommended binary representation is as follows:

FieldStart offsetLength (bytes)Description
Signature Count *0x001Number of signatures. An 8-bit, unsigned integer
Signatures0x01SIG_COUNT * 64SIG_COUNT ed25519 signatures
Message Preamble0x01 + SIG_COUNT * 64PREAMBLE_LENThe message preamble
Message Body0x01 + SIG_COUNT * 64 + PREAMBLE_LEN`MESSAGE_LENThe message content

The signature count MUST match the value of signers count from the message preamble.

Signatures MUST be ordered to match their corresponding public keys as specified in the message preamble.

Runtime Considerations

To prevent social attacks by which the signer is tricked into signing a transaction, the runtime MUST NOT accept signed off-chain messages as transactions under any circumstances. The first byte of the signing domain specifier is chosen such that it corresponds to a value (0xff) which is implicitly illegal as the first byte in a transaction MessageHeader today. The property is implicit because the top bit in the first byte of a MessageHeader being set signals a versioned transaction, but only a value of zero is supported at this time. The runtime will need to be modified to reserve 127 as an illegal version number, making this property explicit.

Implementation

The runtime changes described above have been implemented in PR #29807