With the rise of document forgery and the time-consuming nature of manual verification processes, there's a growing need for a more robust and efficient credential management system. This is where blockchain technology offers a promising solution.
The blockchain is an immutable, decentralized ledger - making it an ideal store of proofs.
The Problem:
Applicants submitting fake degrees is rampant
Employers lack an easy verification method
Manual checks are time-consuming
The Solution: Record student credentials on the blockchain. This enables:
Instant authenticity verification by anyone
Tamper-proof storage of records
Consider the following simplified example of a student’s examination result represented as a JSON string:
{
"id": "1234567",
"result": {
"math": "A",
"science": "B",
"english": "A"
}
}
The idea is to store the examination result onto the blockchain. However, it is not advisable to store the JSON string directly onto the blockchain for two key reasons:
Never store plain text credentials on the blockchain (expensive, privacy risk)
Instead, we store cryptographic hashes of the credentials
Never store encrypted data (hackable)
Caution: Never store encrypted data on the blockchain as they are susceptible to hacking. Storing the hash of the data is much safer.
Creating the Smart Contract
We’ll be using Remix IDE for this one. Head over to https://remix.ethereum.org/
Create a contract named EduCredentialsStore.sol. Populate it with the following statements:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract EduCredentialsStore { // Store the hash of the strings and their corresponding block number mapping (bytes32 => uint) private proofs; // Define an event event Result( address indexed from, string document, uint blockNumber ); // Store the owner of the contract address public owner; // Constructor to initialize the owner constructor() { owner = msg.sender; } // Store a proof of existence in the contract state function storeProof(bytes32 proof) private { proofs[proof] = block.number; } // Calculate and store the proof for a document function storeEduCredentials(string calldata document) external { require(msg.sender == owner, "Only the owner of the contract can store the credentials"); bytes32 proof = proofFor(document); require(proofs[proof] == 0, "Credential already stored"); storeProof(proof); } // Helper function to get a document's sha256 hash function proofFor(string calldata document) private pure returns (bytes32) { return sha256(bytes(document)); } // Check if a document has been saved previously function checkEduCredentials(string calldata document) external payable { bytes32 proof = proofFor(document); uint blockNumber = proofs[proof]; require(msg.value >= 1000 wei, "Insufficient fee: Minimum 1000 wei required"); // Emit event with block number (0 if not found) emit Result(msg.sender, document, blockNumber); } // Withdraw contract balance (only owner) function cashOut() external { require(msg.sender == owner, "Only the owner can cash out!"); payable(owner).transfer(address(this).balance); } // Fallback function to handle unexpected transactions receive() external payable {} }
Understanding the Contract Components
First, we ensure only the educational institution that deploys the contract can store credentials.
contract EduCredentialsStore {
// store the owner of the contract
address owner = msg.sender;
The owner
variable automatically stores the address of the account (msg.sender)
that deploys it.
Then, in the storeEduCredentials()
function, we add the require()
function, as follows:
function storeEduCredentials(string calldata document) external {
require(msg.sender == owner,
"Only the owner of contract can store
the credentials");
// call storeProof() with the hash of the string
storeProof(proofFor(document));
}
The require()
function first checks that whoever is calling this function (msg.sender)
must be the owner, or else it returns an error message ("Only the owner of contract can store the credentials"). If the condition is met, execution will continue; if not, the execution halts.
We also defined an event named Result
to keep front-end applications updated on what is happening to the smart contract(we’re not working with front-end in this section)
To fire the event, use the emit
keyword. You will fire this event in the checkEduCredentials()
function:
The contract includes two private functions:
storeProof()
: This function is only callable within the contract itself and is not accessible to external users. It is likely used internally to store proof data.proofFor()
: Similar tostoreProof()
, this private function is only callable within the contract and may be used to retrieve proof data for a specific key.
In addition, the contract has one external function and one public function:
checkEduCredentials()
(public): This function is visible both outside the contract and to any subclasses of the contract. Users can directly call this function.storeEduCredentials()
(external): This function is visible and callable outside the contract by users. However, it is not visible to any subclasses of the contract.
The contract uses the calldata
and memory
keywords for function parameter declarations:
calldata
indicates that the parameter holds an immutable temporary value that won't be persisted on the blockchain.memory
also holds a temporary value, but it is mutable and can be modified within the function.
The contract also uses the pure
and view
keywords for function declarations:
pure
indicates that the function does not read from or write to the blockchain.view
indicates that the function can read values from the blockchain but does not modify them.
The contract has a mapping object named proofs
, which acts like a dictionary to store key-value pairs. The values in proofs
are persisted on the blockchain as state variables.
With all the different keywords for function access modifiers and parameter declarations, which should you use? Here is a rule of thumb:
Use
calldata
for function parameters if the passed data does not need to be modified within the function.Prefer
external
overpublic
functions if the functions don't need to be called by subclasses of the contract.
With the smart contract written, head over to the Remix IDE's "Compiler" tab. Check the "Auto compile" option, then click the compile button to compile your Solidity code.
At the bottom, you will see two items: ABI and Bytecode. Clicking ABI will copy the application binary interface (ABI) to the clipboard.
Paste it onto a text editor and it will look like this:
[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": false,
"internalType": "string",
"name": "document",
"type": "string"
},
{
"indexed": false,
"internalType": "uint256",
"name": "blockNumber",
"type": "uint256"
}
],
"name": "Result",
"type": "event"
},
{
"inputs": [],
"name": "cashOut",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "document",
"type": "string"
}
],
"name": "checkEduCredentials",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "document",
"type": "string"
}
],
"name": "storeEduCredentials",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]
Deploying the Contract
Head over to the deploy tab in the Remix IDE, you’ll find an interface like this:
In the environments, click on the dropdown and select the Injected Provider- MetaMask. This will let you connect your address, click on the deploy button.
MetaMask will prompt you to pay for the transaction. Click Confirm and wait a while for the transaction to confirm.
Testing the Contract
Once the smart contract is deployed, you will see the contract under the Deployed Contracts section of the Remix IDE. You will also see the address of the contract displayed next to the contract name.
Note: To call a smart contract on the blockchain, your dapp needs the address of the contract as well as its ABI.
Let’s now try to store the hash of the base64-encoded JSON string representing the result of a student onto the blockchain.
For this contract, here’s how the base64-encoded string:
WwoJewoJCSJpbnB1dHMiOiBbXSwKCQkic3RhdGVNdXRhYmlsaXR5IjogIm5vbnBheWFibGUiLAoJCSJ0eXBlIjogImNvbnN0cnVjdG9yIgoJfSwKCXsKCQkiYW5vbnltb3VzIjogZmFsc2UsCgkJImlucHV0cyI6IFsKCQkJewoJCQkJImluZGV4ZWQiOiB0cnVlLAoJCQkJImludGVybmFsVHlwZSI6ICJhZGRyZXNzIiwKCQkJCSJuYW1lIjogImZyb20iLAoJCQkJInR5cGUiOiAiYWRkcmVzcyIKCQkJfSwKCQkJewoJCQkJImluZGV4ZWQiOiBmYWxzZSwKCQkJCSJpbnRlcm5hbFR5cGUiOiAic3RyaW5nIiwKCQkJCSJuYW1lIjogImRvY3VtZW50IiwKCQkJCSJ0eXBlIjogInN0cmluZyIKCQkJfSwKCQkJewoJCQkJImluZGV4ZWQiOiBmYWxzZSwKCQkJCSJpbnRlcm5hbFR5cGUiOiAidWludDI1NiIsCgkJCQkibmFtZSI6ICJibG9ja051bWJlciIsCgkJCQkidHlwZSI6ICJ1aW50MjU2IgoJCQl9CgkJXSwKCQkibmFtZSI6ICJSZXN1bHQiLAoJCSJ0eXBlIjogImV2ZW50IgoJfSwKCXsKCQkiaW5wdXRzIjogW10sCgkJIm5hbWUiOiAiY2FzaE91dCIsCgkJIm91dHB1dHMiOiBbXSwKCQkic3RhdGVNdXRhYmlsaXR5IjogIm5vbnBheWFibGUiLAoJCSJ0eXBlIjogImZ1bmN0aW9uIgoJfSwKCXsKCQkiaW5wdXRzIjogWwoJCQl7CgkJCQkiaW50ZXJuYWxUeXBlIjogInN0cmluZyIsCgkJCQkibmFtZSI6ICJkb2N1bWVudCIsCgkJCQkidHlwZSI6ICJzdHJpbmciCgkJCX0KCQldLAoJCSJuYW1lIjogImNoZWNrRWR1Q3JlZGVudGlhbHMiLAoJCSJvdXRwdXRzIjogW10sCgkJInN0YXRlTXV0YWJpbGl0eSI6ICJwYXlhYmxlIiwKCQkidHlwZSI6ICJmdW5jdGlvbiIKCX0sCgl7CgkJImlucHV0cyI6IFtdLAoJCSJuYW1lIjogIm93bmVyIiwKCQkib3V0cHV0cyI6IFsKCQkJewoJCQkJImludGVybmFsVHlwZSI6ICJhZGRyZXNzIiwKCQkJCSJuYW1lIjogIiIsCgkJCQkidHlwZSI6ICJhZGRyZXNzIgoJCQl9CgkJXSwKCQkic3RhdGVNdXRhYmlsaXR5IjogInZpZXciLAoJCSJ0eXBlIjogImZ1bmN0aW9uIgoJfSwKCXsKCQkiaW5wdXRzIjogWwoJCQl7CgkJCQkiaW50ZXJuYWxUeXBlIjogInN0cmluZyIsCgkJCQkibmFtZSI6ICJkb2N1bWVudCIsCgkJCQkidHlwZSI6ICJzdHJpbmciCgkJCX0KCQldLAoJCSJuYW1lIjogInN0b3JlRWR1Q3JlZGVudGlhbHMiLAoJCSJvdXRwdXRzIjogW10sCgkJInN0YXRlTXV0YWJpbGl0eSI6ICJub25wYXlhYmxlIiwKCQkidHlwZSI6ICJmdW5jdGlvbiIKCX0sCgl7CgkJInN0YXRlTXV0YWJpbGl0eSI6ICJwYXlhYmxlIiwKCQkidHlwZSI6ICJyZWNlaXZlIgoJfQpd
Paste this string into the textbox, click the storeEduCredentials button.
MetaMask will prompt you to pay for the transaction. Click Confirm and wait for a few seconds for the transaction to go through.
You can now paste the same base64-encoded string into the textbox displayed next to the checkEduCredentials
button.
You can also verify the owner of the contract by simply clicking the owner button.
When the transaction is confirmed, you can go to Etherscan and view the balance of the contract. Etherscan is a blockchain explorer that allows you to view all the detailed transaction information that happened on the Ethereum blockchain (including the various testnets).
Cashing Out
Now that our smart contract holds Ether, we’ve a problem. The Ethers are stuck forever in the contract because we didn’t make any provisions to transfer them out. To be able to get Ethers out of a contract, there are two main ways:
Immediately transfer the Ethers to another account the moment they are received in the
checkEduCredentials()
function.Add another function to transfer the Ethers to another account (such as the owner).
For this example, we used the second approach by adding a new cashOut()
function to the contract:
function cashOut() public {
require(msg.sender == owner,
"Only the owner of contract can cash out!");
payable(owner).transfer(address(this).balance);
}
In the cashOut()
function, we first need to ensure that only the owner can call this function. Once this is verified, we transfer the entire balance of the contract to the owner using the transfer()
function.
Once again, deploy the contract and then call the checkEduCredentials()
function so that you can send 1000 Wei to the contract.
Then, click the cashOut button in the Remix IDE to transfer the Ethers back to the owner. On Etherscan, you can see that there is a transfer of the balance to the owner of the contract
Yayyy!!!!! 🎉🎉🎉🎉
Your basic educational credentials smart contract is now up and running.
You can play around with the smart contract and make changes according to your mental model.
Here’s the repository if you want to have a close look at the complete dApp - https://github.com/Insharamin12/Academic-Vault