Off-chain message signing
Motivation
There is ecosystem demand for a method of signing non-transaction messages with a Openos 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
Field | Start offset | Length (bytes) | Description |
---|---|---|---|
Signing Domain | 0x00 | 16 | [1] |
Header version | 0x10 | 1 | [2] |
Application domain | 0x11 | 32 | [3] |
Message format | 0x31 | 1 | [4] |
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"\xffopenos offchain"
The first byte, \xff
, was chosen for the following reasons
- It corresponds to a value that is illegal as the first byte in a transaction
MessageHeader
. - It avoids unintentional misuse in languages with C-like, null-terminated strings.
The remaining bytes, b"openos 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 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.
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 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
Openos 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:
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 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
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