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