IMHO, it's not a terrific example for teaching.
Hash functions are very useful for validating information and they are used to prove things with Smart Contracts as well as on the blockchain itself. For example, block transactions are hashed which is a sort of fingerprint for authentic blocks. Previous blockhashes are part of that functions, so it forms a chain.
This will not be an academic description but it will give you a working knowledge of this tool.
Properties of a Hash function:
- Deterministic: For any given input, the output is always the same.
- Fixed Size: e.g. 32 bytes
- Uncorrelated: If even a single bit of the input is changed, the output in completely changed in an unpredictable way
- Collision-resistant: No two inputs should produce the same output, a "defect" known as a hash collision.
- High-performant: The compute resources to the compute the hash should be reasonable.
- One-way function: A hash tells one nothing about the size, format or content of the input.
Hashes are commonly used to store passwords on disks because hashes tell one nothing about the passwords, but user-inputted passwords can be quickly hashed and then compared to a table.
They are also commonly used to validate documents/files by storing only the hash of the files in a system of record such as a Smart Contract. Given that record, anyone can confirm that the file they have matches a previously-recorded hash - it must be the same file.
Here's a way to put money in a box and let it be released if someone knows a secret word.
pragma solidity 0.7.6;
contract Abracadabra {
bytes32 public publicHash;
constructor(bytes32 publicHash_) {
publicHash = publicHash_;
}
function honeyPot(bytes32 password) public {
// a little concatonation, then hash it
// it can only be claimed by the intended recipient
bytes32 hash = keccak256(abi.encodePacked(password, msg.sender));
// no one knows the password unless they learn about it off-chain
// revert the transaction if the hash is wrong
require(hash == publicHash, "That's not the magic word");
// the money only goes to msg.sender to prevent front-running
msg.sender.transfer(address(this).balance);
}
receive () external payable {}
}
Notice that the hash is on the chain from the outset, but it doesn't help anyone figure out the password. The contract naively stored a password instead of a hash, then everyone would see the password and that would be bad. There's certainly more to think about if securing such a thing but I want to keep this answer on point.
You need to be able to make the hash when you deploy it. This is not safe from a security standpoint, but it should be possible to play with it in Remix.
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.7.6;
contract Abracadabra {
bytes32 public publicHash;
constructor(bytes32 publicHash_) {
publicHash = publicHash_;
}
function honeyPot(bytes32 password) public {
require(hashHelper(password, msg.sender) == publicHash, "That's not the magic word");
msg.sender.transfer(address(this).balance);
}
function hashHelper(bytes32 password, address receiver) public pure returns(bytes32 hash) {
hash = keccak256(abi.encodePacked(password, receiver));
}
receive () external payable {}
}
Hope it helps.
hashHelperfunction ispureand runs locally it would not be safe to use for most users because they might (probably are) be using MetaMask and Infura (or similar) and that could be a starting point for a man-in-the-middle attack. They would need to know the password in order to give to the "client", so that's leak.A solution to that problem is to work out how to compute it client-side, e.g. with JavaScript.
– Rob Hitchens Apr 10 '21 at 08:33call.value()()would be more production-ready. In using that, you take responsibility for re-entrance becausetransferwas protecting you indirectly by limiting the gas available to a would-be attacker. Absent this protection you need to use the "checks, effects. interactions" pattern. – Rob Hitchens Apr 10 '21 at 18:41