Introduction
In this tutorial, you will learn how to build a decentralized document verification and notarization system on the celo blockchain using Solidity.
Document verification and notarization are crucial processes in various domains such as legal, financial, and real estate, where ensuring the authenticity and integrity of documents is of utmost importance. By developing a decentralized system, we can leverage the transparency, immutability, and tamper-proof nature of the celo blockchain to provide a secure and reliable solution for document verification.
You can find the repository for this tutorial Here
Prerequisites
To follow this tutorial, you will need the following:
- Basic understanding of Solidity and smart contracts.
- A Development Environment Like Remix.
- The Celo Extension Wallet.
Contract Developement
The complete code contract look like this
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract DocumentNotary {
struct Document {
string documentHash;
bool isNotarized;
address owner;
mapping(address => bool) signers;
uint256 signerCount;
}
mapping(string => Document) public documents;
mapping(address => bool) public notaries;
modifier onlyOwner(string memory _documentHash) {
require(msg.sender == documents[_documentHash].owner, "Not the document owner");
_;
}
modifier onlyNotary() {
require(notaries[msg.sender], "Not a registered notary");
_;
}
event DocumentAdded(string _documentHash, address indexed _owner);
event DocumentNotarized(string _documentHash, address indexed _notary);
event DocumentSigned(string _documentHash, address indexed _signer);
constructor() {
// The contract deployer is added as a notary by default
notaries[msg.sender] = true;
}
function registerNotary(address _notary) public onlyNotary {
notaries[_notary] = true;
}
function addDocument(string memory _documentHash) public {
require(bytes(documents[_documentHash].documentHash).length == 0, "Document already exists");
Document storage doc = documents[_documentHash];
doc.documentHash = _documentHash;
doc.isNotarized = false;
doc.owner = msg.sender;
emit DocumentAdded(_documentHash, msg.sender);
}
function notarizeDocument(string memory _documentHash) public onlyNotary {
require(!documents[_documentHash].isNotarized, 'Document already notarized');
documents[_documentHash].isNotarized = true;
emit DocumentNotarized(_documentHash, msg.sender);
}
function signDocument(string memory _documentHash) public onlyOwner(_documentHash) {
require(!documents[_documentHash].signers[msg.sender], 'Already signed the document');
documents[_documentHash].signers[msg.sender] = true;
documents[_documentHash].signerCount++;
emit DocumentSigned(_documentHash, msg.sender);
}
function verifyDocument(string memory _documentHash, address _signer) public view returns(bool, uint256) {
return (documents[_documentHash].signers[_signer], documents[_documentHash].signerCount);
}
}
Code Breakdown
Let’s look at our smart contract in detail.
Structures and Variables
The contract begins by defining a structure Document
which holds information about a document including its hash, notarization status, the owner of the document, a mapping of signers, and the total number of signers.
struct Document {
string documentHash;
bool isNotarized;
address owner;
mapping(address => bool) signers;
uint256 signerCount;
}
There are two key mappings that are used to store all the documents and notaries in the contract.
mapping(string => Document) public documents;
mapping(address => bool) public notaries;
Modifiers
There are two modifier functions onlyOwner
and onlyNotary
. Modifiers are used in Solidity to change the behavior of functions in a declarative way. The onlyOwner
modifier ensures that a function can only be called by the owner of a document, and the onlyNotary
modifier ensures that a function can only be called by a registered notary.
modifier onlyOwner(string memory _documentHash) {
require(msg.sender == documents[_documentHash].owner, "Not the document owner");
_;
}
modifier onlyNotary() {
require(notaries[msg.sender], "Not a registered notary");
_;
}
Events
Three events are defined: DocumentAdded
, DocumentNotarized
, and DocumentSigned
. These events provide a way for external consumers to track activities occurring inside the contract.
event DocumentAdded(string _documentHash, address indexed _owner);
event DocumentNotarized(string _documentHash, address indexed _notary);
event DocumentSigned(string _documentHash, address indexed _signer);
Constructor
The constructor is a special function that is executed when the contract is deployed. In this contract, the constructor assigns the contract deployer as the initial notary.
constructor() {
notaries[msg.sender] = true;
}
Register Notary Function
The registerNotary
function allows an existing notary to add a new notary.
function registerNotary(address _notary) public onlyNotary {
notaries[_notary] = true;
}
Add Document Function
The addDocument
function allows a user to add a new document by its hash. It checks if the document already exists before creating a new one.
function addDocument(string memory _documentHash) public {
require(bytes(documents[_documentHash].documentHash).length == 0, "Document already exists");
Document storage doc = documents[_documentHash];
doc.documentHash = _documentHash;
doc.isNotarized = false;
doc.owner = msg.sender;
emit DocumentAdded(_documentHash, msg.sender);
}
Notarize Document Function
The notarizeDocument
function updates the notarization status of a document and emits the DocumentNotarized
event. This function can only be called by a registered notary and only on documents that haven’t already been notarized.
function notarizeDocument(string memory _documentHash) public onlyNotary {
require(!documents[_documentHash].isNotarized, 'Document already notarized');
documents[_documentHash].isNotarized = true;
emit DocumentNotarized(_documentHash, msg.sender);
}
Sign Document Function
The signDocument
function allows the owner of a document to sign it. It checks whether the document has already been signed by the owner, and if not, it records the signature and increments the count of signers. Then it emits the DocumentSigned
event.
function signDocument(string memory _documentHash) public onlyOwner(_documentHash) {
require(!documents[_documentHash].signers[msg.sender], 'Already signed the document');
documents[_documentHash].signers[msg.sender] = true;
documents[_documentHash].signerCount++;
emit DocumentSigned(_documentHash, msg.sender);
}
Verify Document Function
The verifyDocument
function allows anyone to verify whether a specific address has signed a specific document. This is achieved by checking the signers
mapping of the document. It returns two values: whether the queried address has signed the document, and the total number of signers of the document.
function verifyDocument(string memory _documentHash, address _signer) public view returns(bool, uint256) {
return (documents[_documentHash].signers[_signer], documents[_documentHash].signerCount);
}
Deployment
To deploy our smart contract successfully, we need the celo extention wallet which can be downloaded from here
Next, we need to fund our newly created wallet which can done using the celo alfojares faucet Here
Now, click on the plugin logo at the bottom left corner and search for celo plugin.
Install the plugin and click on the celo logo which will show in the side tab after the plugin is installed.
Next connect your Celo wallet, select the contract you want to deploy and finally click on deploy to deploy your contract.
Conclusion
In this tutorial, we explained how to create a decentralized document notarization and verification system on the Celo blockchain using Solidity. The system allows users to upload document hashes, notaries to notarize them, and anyone to verify whether a user has signed a specific document. The contract, DocumentNotary
, uses various Solidity concepts including structs, mappings, modifiers, and events. The system provides a transparent, immutable mechanism for notarizing and verifying documents.
Next Steps
I hope you learned a lot from this tutorial. Here are some relevant links that would aid your learning further.
About the author
I’m Jonathan Iheme, A full stack block-chain Developer from Nigeria.