_Follow along with the video lesson:_ --- ### MessageHashUtils The next library in L1BossBridge.sol, that we may not recognize is `MessageHashUtils`. This one's important, so let's spend a bit of time on it. <details> <summary>MessageHashUtils.sol</summary> ```js // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MessageHashUtils.sol) pragma solidity ^0.8.20; import {Strings} from "../Strings.sol"; /** * @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing. * * The library provides methods for generating a hash of a message that conforms to the * https://eips.ethereum.org/EIPS/eip-191[EIP 191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712] * specifications. */ library MessageHashUtils { /** * @dev Returns the keccak256 digest of an EIP-191 signed data with version * `0x45` (`personal_sign` messages). * * The digest is calculated by prefixing a bytes32 `messageHash` with * `"\x19Ethereum Signed Message:\n32"` and hashing the result. It corresponds with the * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method. * * NOTE: The `messageHash` parameter is intended to be the result of hashing a raw message with * keccak256, although any bytes32 value can be safely used because the final digest will * be re-hashed. * * See {ECDSA-recover}. */ function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) { /// @solidity memory-safe-assembly assembly { mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20) } } /** * @dev Returns the keccak256 digest of an EIP-191 signed data with version * `0x45` (`personal_sign` messages). * * The digest is calculated by prefixing an arbitrary `message` with * `"\x19Ethereum Signed Message:\n" + len(message)` and hashing the result. It corresponds with the * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method. * * See {ECDSA-recover}. */ function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) { return keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(Strings.toString(message.length)), message)); } /** * @dev Returns the keccak256 digest of an EIP-191 signed data with version * `0x00` (data with intended validator). * * The digest is calculated by prefixing an arbitrary `data` with `"\x19\x00"` and the intended * `validator` address. Then hashing the result. * * See {ECDSA-recover}. */ function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) { return keccak256(abi.encodePacked(hex"19_00", validator, data)); } /** * @dev Returns the keccak256 digest of an EIP-712 typed data (EIP-191 version `0x01`). * * The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with * `\x19\x01` and hashing the result. It corresponds to the hash signed by the * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712. * * See {ECDSA-recover}. */ function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) { /// @solidity memory-safe-assembly assembly { let ptr := mload(0x40) mstore(ptr, hex"19_01") mstore(add(ptr, 0x02), domainSeparator) mstore(add(ptr, 0x22), structHash) digest := keccak256(ptr, 0x42) } } } ``` </details> ```js /* @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing. ``` To understand what this is doing, we're going to have to go waaaay back to the [**Solidity Basis**](https://updraft.cyfrin.io/courses/solidity) resource we used, the [**Anders Brown blockchain demo**](https://andersbrownworth.com/blockchain/). We'll be using his [**public-private-key-demo GitHub Repo**](https://github.com/anders94/public-private-key-demo) for this lesson. > **Note:** You will need node.js to be able to experiment with this repo locally. Begin by following the setup instructions on the repo's README. ```bash git clone https://github.com/anders94/public-private-key-demo.git cd public-private-key-demo npm install ``` To run the required server we can then run: ```bash ./bin/www ``` When can then open `http://localhost:3000` in a browser to access the locally hosted demo. Once set up, you can click on `signatures` in the top right and it should look something like this: ::image{src='/security-section-7/12-signatures/signatures1.png' style='width: 100%; height: auto;'} Fundamentally a message signature takes a message, and hashes it with a user's private key. ::image{src='/security-section-7/12-signatures/signatures2.png' style='width: 100%; height: auto;'} A public key can then be used to verify the signature on a message. ::image{src='/security-section-7/12-signatures/signatures3.png' style='width: 100%; height: auto;'} If the message being verified has been changed, or if the public key doesn't match the signature, a message won't be verified. ::image{src='/security-section-7/12-signatures/signatures4.png' style='width: 100%; height: auto;'} With this understanding of how signatures work refreshed in our minds, we can come back to the MessageHashUtils contract. In order to make messaging and hashes more standardized a few Improvement Proposals have been made and adopted. One of which is [**ERC-191: Signed Data Standard**](https://eips.ethereum.org/EIPS/eip-191). The motivation for ERC-191 is pretty clearly outlined in the proposal itself: 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 called RLPdata), r, s and v. If there are no syntactical constraints on signed_data, this means that RLPdata can be used as a syntactically valid presigned transaction. - Multisignature wallets have also had the problem that a presigned transaction has not been tied to a particular validator, i.e a specific wallet. Example: 1. Users A, B and C have the 2/3-wallet X 2. Users A, B and D have the 2/3-wallet Y 3. User A and B submit presigned transactions to X. 4. Attacker can now reuse their presigned transactions to X, and submit to Y. So, basically, non-standardized signing practices were causing confusion and leaving room for attack vectors in ethereum transactions. The standard proposed was that all signed data would follow the format: ``` 0x19 <1 byte version> <version specific data> <data to sign>. ``` In this format the `version` denotes the type and structure of the data being signed. ::image{src='/security-section-7/12-signatures/signatures5.png' style='width: 100%; height: auto;'} The example provided in the ERC does a great job at detailing how this works in practice. ```js 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); } ``` `v, r, and s`, as we know are the constituent parts of a signature. These are hashed together via `keccak256`. The signer of this transaction is able to be recovered by using the EVM precompile `ecrecover`, this is effectively the verification step of a transaction. A message and private key are run through the `ECDSA (Elliptic Curve Digital Signature Algorithm)`, the result of this is our message signature which can be broken into our `v, r and s` elements. Smart contracts utilize the `ecrecover` precompile to verify who has signed something. You can read more on the `ecrecover` precompile [**here, on evm.codes**](https://www.evm.codes/precompiled).
Patrick introduces the ERC-191 signed data format and how signatures are used in the EVM. Discusses the ecrecover precompile.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Solidity Developer
Smart Contract SecurityDuration: 25min
Duration: 1h 18min
Duration: 35min
Duration: 2h 28min
Duration: 5h 03min
Duration: 5h 22min
Duration: 4h 33min
Duration: 2h 01min
Duration: 1h 40min
Testimonials
Read what our students have to say about this course.
Chainlink
Chainlink
Gustavo Gonzalez
Solutions Engineer at OpenZeppelin
Francesco Andreoli
Lead Devrel at Metamask
Albert Hu
DeForm Founding Engineer
Radek
Senior Developer Advocate at Ceramic
Boidushya
WalletConnect
Idris
Developer Relations Engineer at Axelar