Briefly describe the difference between EIP-155, EIP-191, and EIP-712

Signature transaction:

Simple transfer transaction: send (nonce, gasPrice, gasLimit, to, value, data: empty, v, r, s) to the node. Note that data here is empty. (EIP-155 foundation is required here)
Deploy contract transaction: send (nonce, gasPrice, gasLimit, to: empty, value, data: contract creation bytecode, v, r, s) to the node. Note that to is empty, and data is the contract creation bytecode. When the node sees that to is empty, it knows that this is a contract deployment transaction.
Call contract function transaction: send (nonce, gasPrice, gasLimit, to, value, data:selector + function parameters, v, r, s) to the node. Note that data is selector + function parameters, such as bytes4(keccak256(bytes(“foo(uint256,address,string,uint256[2])”))), x, addr, name, array.
Among them, v, r, s are signed “messages”, which can be understood as the first six parameters are the plaintext of the message, and the last three parameters are the signed message (ciphertext). After signing the transaction composed of [plaintext, ciphertext], it can be sent to the node as a signed “transaction”.
Therefore, two signatures are required in this process, one for message signature and one for transaction signature.

Let’s talk about message signature first:

1) 0x1901 Ethereum message (EIP-191 signature standard)

To put it simply, the signature of the string “\x19Ethereum Signed Message:\
” is added. Adding this string is simply to show that this is the signature of Ethereum

Signature logic:

encodepacked(parameter1,parameter2,parameter3,…)
-> Hash1=keccak256(encodepacked(parameter1,parameter2,parameter3,…)
-> keccak256(abi.encodePacked(“\x19Ethereum Signed Message:\
32”, Hash1))
-> Add the private key for calculation to get the “message signature”.

2) 0x1900

EIP-191 proposes adding a validator parameter (usually the address parameter of the contract itself) to the signature to prevent replay attacks. EIP-712 is more used in practical applications.

3) 0x1945(EIP-712)

The message of EIP-712 consists of three parts (the structural formula of 712):
encode(‘\x19\x01’, domainSeparator, message), and then hash calculation
Among them, domainSeparator and message need the keccak256 (hash) of its “structure type” and the keccak256 of its “actual value”, and then perform keccak256 again after encoding.
(Note: domainSeparator (domain name separator) is defined at the time of construct, and declares what my contract is going to do)

Expand the description domainSqparator:

domainSeparator consists of two parts,
The first part is the keccak256-encodeType of the structure,
The second part is the specific implementation of the structure – encodeData;

①encodeType:

The domainSeparator structure is as follows: (generally the salt (random number) will be omitted)

struct EIP712Domain{<!-- -->
string name, //User-readable domain name, such as the name of DAPP
// The version number of the currently signed domain uint256 chainId, // The chain ID defined in EIP-155, such as the Ethereum mainnet is 1
string version,
address verifyingContract, // the address of the contract used to verify the signature
bytes32 salt // random number
}
encodeType = keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)')

②encodeData is the encoding of the specific value of the structure:

keccak256(bytes(name)), keccak256(bytes(1’)), 1, address(this(can be changed)) (salt can not be used)
(Note: For string and bytes types, keccak256 encoding is required)

That is encodeData=keccak256(bytes(name), keccak256(bytes('1')), 1, address(this))

To sum up ↓:

domainSqparator = keccak256( // The definition of domainSqparator can be regarded as a constant, because it is given at the beginning of the contract creation
abi.encode(keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
keccak256(bytes(name)),
keccak256(bytes('1')),
chainId, address(this)
)

Or it can be understood as:

domainSqparator = keccak256( encodeType , encodeData)

//************************************************** ***********************
//******************** Separation line****************** *************************
//************************************************** ***********************

Expand the description message

MESSAGE consists of two parts,
The first part is the hash of the structure type – TYPEHASH,
The second part is the specific implementation of the structure – message;

struct message {<!-- -->
address owner,
address spender,
uint256 value,
uint256 nonce,
uint256 deadline
}

① TYPEHASH = keccak256(message(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)’)

② message = owner, spender, value, nonces[owner] + + , deadline

In summary↓

MESSAGE=keccak256(abi. encode(PERMIT_TYPEHASH, message))
(Note: domainSeparator, message are all encoded (spliced in sequence), processed by keccak256)

//************************************************** ***********************
//********************** Dividing line************************ *********************
//************************************************** ***********************

Stitch the above together and encode and hash again to become a message conforming to the EIP-712 standard:

digest = keccak256( abi. encodePacked( ‘\x19\x01’, domainSqparator , MESSAGE )

At this time, the message (digest) becomes a message to be signed that conforms to the EIP-712 standard, that is, data in the general signature method.
(Note: Message to be signed! = Transaction to be signed. Pack the signed message as data into the transaction and sign it for the sender to become a signed transaction)

//************************************************** ***********************
//********************** Dividing line************************ *********************
//************************************************** ***********************

Off-chain signature:

async function signTypedData(signer) {<!-- -->

// Describe the type and assignment of domain clearly
const domain = {<!-- --> name: "Uniswap V2", version: "1", chainId: 1, verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", }

// give the data type and name
const types = {<!-- --> Permit: [
{<!-- --> name: "owner", type: "address" },
{<!-- --> name: "spender", type: "address" },
{<!-- --> name: "value", type: "uint256" },
{<!-- --> name: "nonce", type: "uint256" },
{<!-- --> name: "deadline", type: "uint256" }, ], } // The data to sign

// Enter the determined data value
const value = {<!-- --> owner: xx, spender: xx, value: xx, nonce: xx, deadline: xx, }

// sign
const signature = await signer._signTypedData(domain, types, value) return signature }

*references:
https://eips.ethereum.org/EIPS/eip-712#rationale-for-typehash
https://mirror.xyz/xyyme.eth/cJX3zqiiUg2dxB1nmbXbDcQ1DSdajHP5iNgBc6wEZz4
https://learnblockchain.cn/article/5012#signed message ( =presigned message = pre-signed)
https://learnblockchain.cn/article/2753#Yuan transaction
https://mirror.xyz/adshao.eth/qmzSfrOB8s6_-s1AsflYNqEkTynShdpBE0EliqjGC1U