4.4 KiB
eip | title | author | discussions-to | status | type | category | created |
---|---|---|---|---|---|---|---|
191 | Signed Data Standard | Martin Holst Swende (@holiman), Nick Johnson <arachnid@notdot.net> | https://github.com/ethereum/EIPs/issues/191 | Final | Standards Track | ERC | 2016-01-20 |
Abstract
This ERC proposes a specification about how to handle signed data in Ethereum contracts.
Motivation
Several multisignature wallet implementations have been created which accepts presigned
transactions. A presigned
transaction is a chunk of binary signed_data
, along with signature (r
, s
and v
). The interpretation of the signed_data
has not been specified, leading to several problems:
- Standard Ethereum transactions can be submitted as
signed_data
. An Ethereum transaction can be unpacked, into the following components:RLP<nonce, gasPrice, startGas, to, value, data>
(hereby calledRLPdata
),r
,s
andv
. If there are no syntactical constraints onsigned_data
, this means thatRLPdata
can be used as a syntactically validpresigned
transaction. - Multisignature wallets have also had the problem that a
presigned
transaction has not been tied to a particularvalidator
, i.e a specific wallet. Example:- Users
A
,B
andC
have the2/3
-walletX
- Users
A
,B
andD
have the2/3
-walletY
- User
A
andB
submitpresigned
transactions toX
. - Attacker can now reuse their presigned transactions to
X
, and submit toY
.
- Users
Specification
We propose the following format for signed_data
0x19 <1 byte version> <version specific data> <data to sign>.
The initial 0x19
byte is intended to ensure that the signed_data
is not valid RLP.
For a single byte whose value is in the [0x00, 0x7f] range, that byte is its own RLP encoding.
That means that any signed_data
cannot be one RLP-structure, but a 1-byte RLP
payload followed by something else. Thus, any EIP-191 signed_data
can never be an Ethereum transaction.
Additionally, 0x19
has been chosen because since ethereum/go-ethereum#2940 , the following is prepended before hashing in personal_sign:
"\x19Ethereum Signed Message:\n" + len(message).
Using 0x19
thus makes it possible to extend the scheme by defining a version 0x45
(E
) to handle these kinds of signatures.
Registry of version bytes
Version byte | EIP | Description |
---|---|---|
0x00 |
191 | Data with intended validator |
0x01 |
712 | Structured data |
0x45 |
191 | personal_sign messages |
Version 0x00
0x19 <0x00> <intended validator address> <data to sign>
The version 0x00
has <intended validator address>
for the version specific data. In the case of a Multisig wallet that perform an execution based on a passed signature, the validator address is the address of the Multisig itself. The data to sign could be any arbitrary data.
Version 0x01
The version 0x01
is for structured data as defined in EIP-712
Version 0x45
(E)
0x19 <0x45 (E)> <thereum Signed Message:\n" + len(message)> <data to sign>
The version 0x45
(E) has <thereum Signed Message:\n" + len(message)>
for the version-specific data. The data to sign can be any arbitrary data.
NB: The
E
inEthereum Signed Message
refers to the version byte 0x45. The characterE
is0x45
in hexadecimal which makes the remainder,thereum Signed Message:\n + len(message)
, the version-specific data.
Example
The following snippets has been written in Solidity 0.8.0.
Version 0x00
function signatureBasedExecution(address target, uint256 nonce, bytes memory payload, uint8 v, bytes32 r, bytes32 s) public payable {
// Arguments when calculating hash to validate
// 1: byte(0x19) - the initial 0x19 byte
// 2: byte(0) - the version byte
// 3: address(this) - the validator address
// 4-6 : Application specific data
bytes32 hash = keccak256(abi.encodePacked(byte(0x19), byte(0), address(this), msg.value, nonce, payload));
// recovering the signer from the hash and the signature
addressRecovered = ecrecover(hash, v, r, s);
// logic of the wallet
// if (addressRecovered == owner) executeOnTarget(target, payload);
}
Copyright
Copyright and related rights waived via CC0.