Decentralized Document Notarization Solidity
👤 Sharing: AI
```solidity
pragma solidity ^0.8.0;
// SPDX-License-Identifier: MIT
/**
* @title Decentralized Document Notarization
* @dev This contract allows users to notarize documents by storing their hashes on the blockchain.
* The timestamp and owner of the notarization are recorded, providing proof of existence.
*/
contract DocumentNotarization {
// Struct to store notarization information
struct Notarization {
address owner; // Address of the user who notarized the document
uint256 timestamp; // Timestamp of the notarization
}
// Mapping to store notarization information by document hash
mapping(bytes32 => Notarization) public notarizations;
// Event emitted when a document is notarized
event DocumentNotarized(bytes32 indexed documentHash, address indexed owner, uint256 timestamp);
// Event emitted when a document hash is checked
event DocumentChecked(bytes32 indexed documentHash, bool exists);
/**
* @dev Notarizes a document by storing its hash on the blockchain.
* @param _documentHash The Keccak-256 hash of the document to notarize. Crucially, this hash must be computed *off-chain* by the user.
*/
function notarizeDocument(bytes32 _documentHash) public {
// Require that the document has not already been notarized
require(notarizations[_documentHash].owner == address(0), "Document already notarized");
// Store the notarization information
notarizations[_documentHash] = Notarization(msg.sender, block.timestamp);
// Emit an event to log the notarization
emit DocumentNotarized(_documentHash, msg.sender, block.timestamp);
}
/**
* @dev Checks if a document has been notarized by its hash.
* @param _documentHash The Keccak-256 hash of the document to check.
* @return A boolean indicating whether the document has been notarized.
*/
function isDocumentNotarized(bytes32 _documentHash) public view returns (bool) {
bool exists = notarizations[_documentHash].owner != address(0);
emit DocumentChecked(_documentHash, exists);
return exists;
}
/**
* @dev Retrieves the notarization information for a given document hash.
* @param _documentHash The Keccak-256 hash of the document.
* @return The address of the owner, the timestamp of the notarization. Returns zero values if the document is not notarized.
*/
function getNotarization(bytes32 _documentHash) public view returns (address owner, uint256 timestamp) {
return (notarizations[_documentHash].owner, notarizations[_documentHash].timestamp);
}
/**
* @dev Allows the owner of a notarization to transfer ownership to another address.
* @param _documentHash The Keccak-256 hash of the document.
* @param _newOwner The address to transfer ownership to.
*/
function transferOwnership(bytes32 _documentHash, address _newOwner) public {
// Require that the document has been notarized
require(notarizations[_documentHash].owner != address(0), "Document not notarized");
// Require that the caller is the current owner
require(notarizations[_documentHash].owner == msg.sender, "Only the owner can transfer ownership");
// Update the owner
notarizations[_documentHash].owner = _newOwner;
// Emit an event to log the ownership transfer
emit DocumentNotarized(_documentHash, _newOwner, block.timestamp); // Reuse the Notarized event
}
/**
* @dev Allows the owner of a notarization to revoke it.
* @param _documentHash The Keccak-256 hash of the document.
*/
function revokeNotarization(bytes32 _documentHash) public {
// Require that the document has been notarized
require(notarizations[_documentHash].owner != address(0), "Document not notarized");
// Require that the caller is the current owner
require(notarizations[_documentHash].owner == msg.sender, "Only the owner can revoke the notarization");
// Delete the notarization
delete notarizations[_documentHash];
//Emit DocumentChecked so it's clear to offchain applications what's happening
emit DocumentChecked(_documentHash, false);
}
}
```
Key improvements and explanations:
* **`pragma solidity ^0.8.0;`**: Specifies the Solidity compiler version. Using `^0.8.0` allows for compilers from 0.8.0 up to, but not including, 0.9.0. This is crucial for compatibility and avoiding unexpected behavior. Solidity versions before 0.8.0 have different behavior with `require` statements and default behavior with integer overflows, making them unsuitable.
* **`SPDX-License-Identifier: MIT`**: Adds an SPDX license identifier. This is good practice for open-source projects.
* **Clearer Comments and NatSpec:** The comments are more descriptive, explaining the purpose of each function and the data stored. Using NatSpec comments (starting with `///` or `/**`) allows tools to generate documentation from the contract. This makes the code much more maintainable and easier to understand.
* **`Notarization` Struct:** A `struct` is used to organize the data associated with a notarization (owner and timestamp). This improves code readability and organization.
* **`notarizations` Mapping:** A `mapping` is used to store notarization information, indexed by the document's hash. This allows for efficient retrieval of notarization data.
* **`notarizeDocument` Function:**
* **`require(notarizations[_documentHash].owner == address(0), "Document already notarized");`**: This crucial `require` statement prevents a document from being notarized multiple times. The `address(0)` check is how you determine if an address is *zero*.
* **`emit DocumentNotarized(_documentHash, msg.sender, block.timestamp);`**: An `event` is emitted to log the notarization. This allows external applications to monitor the contract for new notarizations. Crucially, the parameters of the event are indexed (`indexed documentHash`, `indexed owner`). This makes it *much* more efficient to search for these events off-chain. Without indexing, searching requires iterating through every event in the contract.
* **`isDocumentNotarized` Function:** A view function that checks if a document has been notarized. Returns a boolean value. Uses `address(0)` to check if the document hash exists as a key in the `notarizations` mapping.
* **`getNotarization` Function:** Returns the notarization details (owner and timestamp) for a given document hash. Returns zero values if the document has not been notarized. This is a `view` function, meaning it doesn't modify the blockchain state.
* **`transferOwnership` Function:** Allows the owner of a notarization to transfer it to another address. Includes necessary checks to ensure only the owner can transfer the ownership.
* **`revokeNotarization` Function:** Allows the owner to revoke the notarization. This completely removes the notarization information from the contract.
* **`bytes32` for Document Hash:** The code now correctly uses `bytes32` for the document hash. This is the standard practice when dealing with Keccak-256 hashes in Solidity. *You MUST compute the hash off-chain (e.g., in JavaScript) using a Keccak-256 library.* Solidity's built-in `keccak256` is suitable for simpler hashing but less suitable and more costly for larger documents. Computing the hash off-chain significantly reduces gas costs.
* **`msg.sender` and `block.timestamp`:** These are used to record the address of the user who notarized the document and the time of notarization, respectively.
* **Gas Optimization:** While this contract is relatively simple, the use of `view` functions where appropriate and the efficient mapping structure help minimize gas costs.
* **Error Messages:** The `require` statements include descriptive error messages to provide helpful information to users when a transaction fails.
* **Security Considerations:**
* **Hash Collision Risk:** The security of this system relies entirely on the collision resistance of the Keccak-256 hash function. It is *extremely* unlikely, but theoretically possible, for two different documents to have the same hash. If this were to happen, notarizing one document would also effectively notarize the other.
* **Off-Chain Hashing:** It is crucial that the document hash is computed *off-chain* by the user. If the hashing were done on-chain, it would significantly increase gas costs, especially for large documents. More importantly, you would have to trust that the contract will hash correctly. Hashing off-chain allows the user to verify the hash independently.
* **No Document Storage:** This contract *only* stores the hash of the document, *not* the document itself. Storing the document on-chain would be extremely expensive and is generally not recommended. Off-chain storage solutions (e.g., IPFS) are typically used for storing the document content.
* **Events:** Events are emitted to provide a way for external applications to track notarizations. They are crucial for interacting with the contract. The `indexed` keyword makes it efficient to filter and search for events.
* **View Functions:** Using `view` functions for read-only operations makes them gas-free when called externally (from off-chain).
* **Transfer Ownership/Revoke Features:** The added `transferOwnership` and `revokeNotarization` functions provide more flexibility and control over the notarizations.
* **Clearer Return Values:** The `getNotarization` function returns `(address owner, uint256 timestamp)`. Returning a tuple like this is much more readable and idiomatic than returning an array.
* **Event on revokeNotarization:** The `DocumentChecked` event is emmitted when a notarization is revoked, indicating the hash is not valid anymore.
**How to Use (Example)**
1. **Off-Chain Hash Calculation:** Use a library (e.g., `crypto-js` in JavaScript) to calculate the Keccak-256 hash of your document.
```javascript
const CryptoJS = require("crypto-js");
const documentContent = "This is the content of my document.";
const hash = CryptoJS.SHA256(documentContent).toString();
const keccak256Hash = CryptoJS.Keccak256(documentContent).toString(); // use if SHA256 doesn't work.
console.log("Document Hash:", hash); // Log the Keccak-256 hash to the console.
```
2. **Deploy the Contract:** Deploy the `DocumentNotarization` contract to a blockchain (e.g., Ganache for local development, or a testnet like Goerli or Sepolia).
3. **Call `notarizeDocument`:** Call the `notarizeDocument` function in your smart contract, passing in the Keccak-256 hash you calculated in step 1. You'll need to pay gas to execute this transaction.
4. **Verify the Notarization:**
* Call the `isDocumentNotarized` function, passing in the hash. It should return `true`.
* Call the `getNotarization` function, passing in the hash. It should return the address of the account that notarized the document and the timestamp.
5. **Off-Chain Verification:**
* To prove that a document was notarized, you can provide the document, its Keccak-256 hash, and a proof from the blockchain that the hash was stored in the `DocumentNotarization` contract at a specific time. Anyone can then independently verify that the document's hash matches the hash stored on the blockchain.
This improved example provides a much more robust, secure, and usable foundation for a decentralized document notarization system. Remember to thoroughly test your contract before deploying it to a mainnet.
👁️ Viewed: 5
Comments