A wallet can sign a transaction, a login request, or an order. All three may use the same private key. The signature alone does not explain which action the user approved. That meaning comes from the bytes being signed and the rules used to verify them.
EIP-191 gives signed messages an envelope. Its first job is to separate a message from a transaction; its versions add context for different uses.
The diagram sends the same transaction bytes through two signing paths. Switch to Raw digest: both paths produce the digest a transaction verifier expects. With personal_sign, the added envelope changes that digest. The message signature no longer verifies as the same sender's authorization for this transaction.
The transaction is the public EIP-155 example: 1 ETH, nonce 9, chain 1. The hashes are computed locally; the diagram does not use a private key or request a signature. Reusing a raw signature also requires encoding the transaction's recovery value correctly.
For the full path from transfer fields to signed bytes, see Transaction .
Before the signature
For a conventional Ethereum EOA, ECDSA signs a 32-byte digest. An application first chooses an encoding, hashes the resulting bytes with Keccak-256, and asks the key to sign that digest.
Verification follows the same encoding path. Given the digest and signature, a verifier can recover an address and compare it with the expected signer. A successful match binds that key to those bytes. Whether the bytes authorize a login or a transfer is a separate application rule.
The common serialized message signature is 65 bytes: 32 bytes for r, 32 for s, and one recovery byte v. Do not copy transaction-specific v calculations into message verification; legacy transaction chain protection and message recovery are different conventions. Contract accounts can also use verification schemes beyond EOA recovery.
The transaction boundary
Signing an arbitrary digest is dangerous when its preimage is hidden. If that digest is a valid transaction signing hash, the resulting signature may authorize that transaction. This was the problem with unrestricted raw signing exposed by some historical eth_sign implementations. RPC names alone are not a reliable description of every client's hashing behavior.
EIP-191 concatenates four segments: the 0x19 marker, a version byte, version-specific context, and application data. The diagram shows their layout alongside the boundary an RLP decoder recognizes. 0x19 is one byte, not the four text characters 0x19.
Envelope → RLP boundary
Legacy transaction
Listec
44 payload bytesnonce · fees · to · value · data · …
← one complete RLP list →
List · no trailing bytes
EIP-191 message
Marker19
Version · 1 B00 / 01 / 45
Contextversion-specific data
Payloadapplication data
1 byte ↑Trailing bytes
Scalar · not a transaction list
A legacy transaction must decode as one RLP list of transaction fields. In RLP , list headers start at 0xc0. A byte below 0x80 instead encodes only itself: 0x19 is a valid one-byte RLP value, but it is not a list header.
An EIP-191 envelope always has at least a version byte after 0x19. An RLP decoder consumes the first byte as a complete item; it cannot treat the remaining version, context, and message as children of that item. The whole envelope therefore cannot be the single RLP list required by a legacy transaction. This is the encoding boundary described in EIP-191.
Removing the prefix afterward does not convert the message signature into a transaction signature. The wallet signed the digest of the prefixed bytes. A transaction verifier computes its digest from the transaction encoding, so that signature does not authorize the transaction for the same signer. Both the byte structure and the resulting digest matter.
Modern typed transactions need a separate qualification. EIP-2718 introduces a transaction-type byte before a type-specific payload. An EIP-1559 signing preimage begins with 0x02, whereas this message begins with 0x19. The legacy RLP argument should not be stretched into a proof about every possible future transaction type.
Personal messages
personal_sign uses the EIP-191 envelope with version 0x45. This is the byte for the letter E in Ethereum Signed Message; the remaining context starts with thereum. There is no additional version byte to insert before that E.
The complete prefix is \x19Ethereum Signed Message:\n followed by the message length. That length is the ASCII decimal representation of the message's byte count, with no leading zeroes. The prefix and message are then hashed together.
For the UTF-8 text Hi, the ending is \n2Hi. For 你好, the length is 6, although JavaScript's message.length is 2. Count encoded bytes, not JavaScript string units. Editing the message in the diagram changes both the length field and the resulting digest.
The distinction matters for hashes too. A 32-byte digest represented as 0x plus 64 hexadecimal characters is 66 bytes when signed as text. Decode it first if the intended message is the underlying 32 bytes. Both operations are valid; they produce different signatures.
Transaction and message
The Transaction article builds a native transfer's complete signing data from its fields. Here the same type-2 bytes, U, pass through both signing paths: directly into Keccak-256 for the transaction, or into a personal_sign envelope as the message.
Transaction × EIP-191
Type021 B
RLP list headerf048 B payload
chainId011 B
nonce091 B
maxPriorityFeePerGas84773594005 B
maxFeePerGas8506fc23ac006 B
gasLimit8252083 B
to94353535353535353535353535353535353535353521 B
value880de0b6b3a76400001 ETH
data800x · empty
accessListc0[]
U · 50 bytes · signing preimage0x02f0010984773594008506fc23ac00825208943535353535353535353535353535353535353535880de0b6b3a764000080c0
In the default example, U is 50 bytes. The message path prepends \x19Ethereum Signed Message:\n, followed by ASCII 50 (hex 3530), then the exact U bytes. Its length counts decoded bytes, not the characters of the hexadecimal representation. Both paths end in a 32-byte digest, but the different preimages produce different digests. The message signature is therefore not the transaction authorization expected for the same account.
This diagram uses EIP-1559 type 2, whose signing data begins with 0x02. The opening diagram uses legacy EIP-155, whose signing data is an RLP list without that type byte. In both cases, personal_sign adds its envelope around the complete transaction preimage rather than signing the transaction digest directly.
Three versions
The version byte tells a verifier how to interpret the envelope.
Version
Context
Payload
0x00
Intended validator address
Application-defined bytes
0x01
EIP-712 domain separator
Structured message hash
0x45
Personal-message prefix and length
Message bytes
Version 0x00 binds signed data to a particular validator, such as a multisig wallet. It prevents a second wallet with overlapping owners from interpreting the same authorization as its own. The application must still include and enforce any needed nonce, chain binding, target, amount, and deadline.
signMessage adds the personal-message envelope internally. Do not add the prefix yourself and pass the result to signMessage; that signs a second envelope around the first.
verifyMessage returns a recovered address. It does not know which account your application expected. That comparison belongs to the verifier.
What remains unsigned
The personal-message prefix contains no website, chain, expiry, or nonce. If an application needs those boundaries, it must place them in the message and check them during verification.
A contract can accept an EIP-191 signature as an order or spending authorization. Signing costs no transaction gas, but someone else can later submit that signature to a contract. A readable message helps inspection; a prefixed hash can still hide the action being authorized.
For typed application data, continue with EIP-712 . For authentication, EIP-4361 defines the message fields and verification rules around a wallet login.