Proposal for off-chain message signing (#26915)
docs: add proposal for off-chain message signing
This commit is contained in:
parent
f8a94209bd
commit
2decc5275c
|
@ -0,0 +1,138 @@
|
||||||
|
# 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](https://eips.ethereum.org/EIPS/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
|
||||||
|
|
||||||
|
| Field | Start offset | Length (bytes) | Description
|
||||||
|
| :---- | :----------: | :------------: | :----------
|
||||||
|
| Signing Domain | 0x00 | 16 | \[[1](#signing-domain-specifier)\]
|
||||||
|
| Header version | 0x10 | 1 | \[[2](#header-version)\]
|
||||||
|
| Application domain | 0x11 | 32 | \[[3](#application-domain)\]
|
||||||
|
| Message format | 0x31 | 1 | \[[4](#message-format)\]
|
||||||
|
| Signer count | 0x32 | 1 | Number of signers committing to the message. An 8-bit, unsigned integer. **MUST NOT** be zero.
|
||||||
|
| Signers | 0x33 | `SIGNER_COUNT` * 32 | `SIGNER_COUNT` ed25519 public keys of signers
|
||||||
|
| Message length | 0x33 + `SIGNER_CNT` * 32 | 2 | Length 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`.
|
||||||
|
1. 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
|
||||||
|
|
||||||
|
#### Header version
|
||||||
|
|
||||||
|
The header version is represented as an 8-bit unsigned integer. Only the version
|
||||||
|
0 header format is specfied 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 interpretted otherwise.
|
||||||
|
|
||||||
|
#### Message Format
|
||||||
|
|
||||||
|
Version `0` headers specify three message formats allowing for trade-offs between
|
||||||
|
compatibility and composition of messages.
|
||||||
|
|
||||||
|
| ID | Encoding | Maximum Length \* | Hardware Wallet Support |
|
||||||
|
| :-: | :-------------------: | :---------------: | :---------------------: |
|
||||||
|
| 0 | Restricted ASCII \*\* | 1232 | Yes |
|
||||||
|
| 1 | UTF-8 | 1232 | Blind sign only |
|
||||||
|
| 2 | UTF-8 | 65535 | No |
|
||||||
|
|
||||||
|
\* Combined length of the [message preamble](#message-preamble) and message body<br/>
|
||||||
|
\*\* Those characters for which [`isprint(3)`](https://linux.die.net/man/3/isprint)
|
||||||
|
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](#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](#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:
|
||||||
|
|
||||||
|
| Field | Start offset | Length (bytes) | Description
|
||||||
|
| :---- | :----------: | :------------: | :----------
|
||||||
|
| Signature Count \* | 0x00 | 1 | Number of signatures. An 8-bit, unsigned integer
|
||||||
|
| Signatures | 0x01 | `SIG_COUNT` * 64 | `SIG_COUNT` ed25519 signatures
|
||||||
|
| Message Preamble | 0x01 + `SIG_COUNT` * 64 | `PREAMBLE_LEN` | The [message preamble](#message-preamble)
|
||||||
|
| Message Body | 0x01 + `SIG_COUNT * 64 + `PREAMBLE_LEN` | `MESSAGE_LEN` | The 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](#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](https://github.com/solana-labs/solana/blob/b6ae6c1fe17e4b64c5051c651ca2585e4f55468c/sdk/program/src/message/versions/mod.rs#L269-L281)
|
||||||
|
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](https://github.com/solana-labs/solana/pull/29807)
|
Loading…
Reference in New Issue