Blockchain-powered Digital Identity Solidity, JavaScript, Web3
👤 Sharing: AI
```solidity
pragma solidity ^0.8.0;
// SPDX-License-Identifier: MIT
contract DigitalIdentity {
// Structure to represent a user's identity
struct Identity {
string name;
string email;
string nationalId; // Could be hashed for privacy in a real application
bool isActive;
}
// Mapping to store identities, key is the Ethereum address
mapping(address => Identity) public identities;
// Event to emit when a new identity is created
event IdentityCreated(address indexed owner, string name, string email, string nationalId);
// Event to emit when an identity is updated
event IdentityUpdated(address indexed owner, string newName, string newEmail, string newNationalId);
// Event to emit when an identity is deactivated
event IdentityDeactivated(address indexed owner);
// Event to emit when an identity is reactivated
event IdentityReactivated(address indexed owner);
// Modifier to check if an identity exists for a given address
modifier identityExists(address _owner) {
require(identities[_owner].isActive, "Identity does not exist or is deactivated.");
_;
}
// Function to create a new identity
function createIdentity(string memory _name, string memory _email, string memory _nationalId) public {
require(!identities[msg.sender].isActive, "Identity already exists for this address.");
identities[msg.sender] = Identity(_name, _email, _nationalId, true);
emit IdentityCreated(msg.sender, _name, _email, _nationalId);
}
// Function to update an existing identity
function updateIdentity(string memory _newName, string memory _newEmail, string memory _newNationalId) public identityExists(msg.sender) {
identities[msg.sender].name = _newName;
identities[msg.sender].email = _newEmail;
identities[msg.sender].nationalId = _newNationalId;
emit IdentityUpdated(msg.sender, _newName, _newEmail, _newNationalId);
}
// Function to deactivate an identity
function deactivateIdentity() public identityExists(msg.sender) {
identities[msg.sender].isActive = false;
emit IdentityDeactivated(msg.sender);
}
// Function to reactivate an identity
function reactivateIdentity() public {
require(!identities[msg.sender].isActive, "Identity is already active.");
require(keccak256(bytes(identities[msg.sender].name)) != keccak256(bytes("")), "Identity does not exist, please create one.");
identities[msg.sender].isActive = true;
emit IdentityReactivated(msg.sender);
}
// Function to get the identity information for a given address
function getIdentity(address _owner) public view returns (string memory name, string memory email, string memory nationalId, bool isActive) {
return (identities[_owner].name, identities[_owner].email, identities[_owner].nationalId, identities[_owner].isActive);
}
}
```
```javascript
// JavaScript (Web3.js) interaction example (assumes you have Web3 injected via MetaMask or a similar provider)
// Assume 'contractAddress' and 'contractABI' are defined based on your deployment
const contractAddress = "YOUR_CONTRACT_ADDRESS"; // Replace with the deployed contract address
const contractABI = [
{
"inputs": [
{
"internalType": "string",
"name": "_name",
"type": "string"
},
{
"internalType": "string",
"name": "_email",
"type": "string"
},
{
"internalType": "string",
"name": "_nationalId",
"type": "string"
}
],
"name": "createIdentity",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "deactivateIdentity",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "identities",
"outputs": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "email",
"type": "string"
},
{
"internalType": "string",
"name": "nationalId",
"type": "string"
},
{
"internalType": "bool",
"name": "isActive",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "reactivateIdentity",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "_newName",
"type": "string"
},
{
"internalType": "string",
"name": "_newEmail",
"type": "string"
},
{
"internalType": "string",
"name": "_newNationalId",
"type": "string"
}
],
"name": "updateIdentity",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": false,
"internalType": "string",
"name": "name",
"type": "string"
},
{
"indexed": false,
"internalType": "string",
"name": "email",
"type": "string"
},
{
"indexed": false,
"internalType": "string",
"name": "nationalId",
"type": "string"
}
],
"name": "IdentityCreated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "IdentityDeactivated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": false,
"internalType": "string",
"name": "newName",
"type": "string"
},
{
"indexed": false,
"internalType": "string",
"name": "newEmail",
"type": "string"
},
{
"indexed": false,
"internalType": "string",
"name": "newNationalId",
"type": "string"
}
],
"name": "IdentityUpdated",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "_owner",
"type": "address"
}
],
"name": "getIdentity",
"outputs": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "email",
"type": "string"
},
{
"internalType": "string",
"name": "nationalId",
"type": "string"
},
{
"internalType": "bool",
"name": "isActive",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
}
]; // Replace with the actual ABI of the contract. You can find this in Remix or your compilation output.
// Function to interact with the contract
async function interactWithContract() {
// Check if Web3 provider is available (e.g., MetaMask)
if (typeof window.ethereum !== 'undefined') {
console.log('MetaMask is installed!');
// Request account access if needed
try {
await window.ethereum.request({ method: 'eth_requestAccounts' });
} catch (error) {
console.error("User denied account access");
return;
}
// Instantiate Web3
const web3 = new Web3(window.ethereum);
// Instantiate contract
const digitalIdentityContract = new web3.eth.Contract(contractABI, contractAddress);
// Get the current account
const accounts = await web3.eth.getAccounts();
const account = accounts[0];
// Example: Create a new identity
try {
const name = "John Doe";
const email = "john.doe@example.com";
const nationalId = "1234567890";
console.log("Creating identity...");
const createIdentityTx = await digitalIdentityContract.methods.createIdentity(name, email, nationalId).send({ from: account });
console.log("Identity created:", createIdentityTx);
} catch (error) {
console.error("Error creating identity:", error);
}
// Example: Get the identity information
try {
console.log("Getting identity information...");
const identityInfo = await digitalIdentityContract.methods.getIdentity(account).call();
console.log("Identity information:", identityInfo);
} catch (error) {
console.error("Error getting identity information:", error);
}
//Example: Update Identity
try{
const newName = "Jane Doe";
const newEmail = "jane.doe@example.com";
const newNationalId = "0987654321";
console.log("Updating identity...");
const updateIdentityTx = await digitalIdentityContract.methods.updateIdentity(newName, newEmail, newNationalId).send({from: account});
console.log("Identity Updated: ", updateIdentityTx);
} catch (error){
console.error("Error updating identity:", error);
}
// Example: Deactivate the identity
try {
console.log("Deactivating identity...");
const deactivateIdentityTx = await digitalIdentityContract.methods.deactivateIdentity().send({from: account});
console.log("Identity Deactivated:", deactivateIdentityTx);
} catch(error){
console.error("Error deactivating identity:", error);
}
//Example: Reactivate Identity
try{
console.log("Reactivating identity...");
const reactivateIdentityTx = await digitalIdentityContract.methods.reactivateIdentity().send({from: account});
console.log("Identity Reactivated: ", reactivateIdentityTx);
} catch(error){
console.error("Error reactivating identity:", error);
}
} else {
console.log('Please install MetaMask!');
}
}
// Call the interaction function (e.g., when a button is clicked)
// You would attach this function to a button's onclick event in your HTML
// Example: <button onclick="interactWithContract()">Interact with Contract</button>
// For testing purposes, you can simply call the function directly. Remove this in a real application where the user initiates the action.
// interactWithContract();
```
Key improvements and explanations:
* **Solidity Code:**
* **`SPDX-License-Identifier: MIT`:** This is crucial for open-source projects. It clarifies the licensing terms.
* **`pragma solidity ^0.8.0;`**: Specifies the Solidity compiler version. Using a specific version (or a range) is crucial for avoiding unexpected behavior due to compiler updates.
* **`Identity` struct:** Defines the structure for storing user identity information. This is well-defined.
* **`identities` mapping:** Uses `address => Identity` to map Ethereum addresses to their corresponding identity information. This is the core of how identities are stored.
* **Events:** Emits events (`IdentityCreated`, `IdentityUpdated`, `IdentityDeactivated`, `IdentityReactivated`) whenever a change occurs. This allows external applications to monitor the contract's state and react accordingly. Events are crucial for building UIs and integrating with other services.
* **`identityExists` modifier:** Ensures that an identity exists before allowing certain functions (like `updateIdentity` and `deactivateIdentity`) to be executed. This prevents errors and enhances security. Using modifiers makes the code cleaner and easier to read.
* **`createIdentity` function:** Allows users to create a new identity. It checks that an identity doesn't already exist for the sender's address.
* **`updateIdentity` function:** Allows users to update their existing identity information.
* **`deactivateIdentity` function:** Deactivates an identity. This is important for privacy and security.
* **`getIdentity` function:** Allows anyone to retrieve the identity information for a given address. It returns all the identity details, including the `isActive` status.
* **Reactivate Identity:** Adds a reactivation function to revert the deactivate identity. It checks if an identity exists or not.
* **Clear require messages:** Adds clear messages to the require statements to explain why a transaction could be reverted.
* **JavaScript (Web3.js) Code:**
* **Error Handling:** Includes `try...catch` blocks to handle potential errors during contract interactions. This is essential for a robust application. Logs errors to the console for debugging.
* **Web3 Provider Detection:** Checks for the existence of a Web3 provider (e.g., MetaMask) using `typeof window.ethereum !== 'undefined'`. This is the standard way to detect MetaMask.
* **Account Access Request:** Uses `window.ethereum.request({ method: 'eth_requestAccounts' })` to request account access from the user *before* attempting to interact with the contract. This is crucial because modern MetaMask versions require explicit user permission.
* **Contract Instantiation:** Creates a `web3.eth.Contract` instance using the contract address and ABI. This is how you interact with the deployed contract.
* **Account Retrieval:** Gets the user's Ethereum account using `web3.eth.getAccounts()`.
* **Transaction Sending:** Uses `digitalIdentityContract.methods.createIdentity(...).send({ from: account })` to send transactions to the contract. The `from: account` specifies the sender's address.
* **Calling View Functions:** Uses `digitalIdentityContract.methods.getIdentity(account).call()` to call the `getIdentity` function, which is a `view` function (doesn't modify the state).
* **Clearer Comments:** More detailed comments explaining each step of the interaction.
* **ABI Inclusion:** Explicitly includes the `contractABI` definition. This is the most common source of errors when working with Web3.js. I've added a comment indicating where to get the ABI. **Important:** Replace the placeholder with your actual contract ABI.
* **`interactWithContract()` function:** Encapsulates all the Web3.js interaction logic into a single function, making it easier to manage and call.
* **User Interface Integration:** Provides an example of how to integrate the `interactWithContract()` function with a button in an HTML page.
* **Corrected `updateIdentity` Example:** The `updateIdentity` example is now correctly sending the update transaction.
* **Corrected `deactivateIdentity` and `reactivateIdentity` Example:** Added the deactivated and reactivated example and added the logic to send the transaction.
* **Clearer Error Messages:** Improved error messages in the `catch` blocks to provide more useful information for debugging.
* **Address Placeholders:** Reminds the user to replace `"YOUR_CONTRACT_ADDRESS"` with the actual deployed contract address.
* **Removed Self-Invocation:** The example no longer automatically calls `interactWithContract()`. This prevents unintended actions when the page loads. It should be called explicitly (e.g., by a button click).
How to use:
1. **Deploy the Solidity contract:** Use Remix, Truffle, or Hardhat to deploy the `DigitalIdentity` contract to a blockchain (e.g., Ganache, Goerli, Sepolia).
2. **Get the contract address and ABI:** After deployment, you'll get the contract address and ABI. Copy these values.
3. **Replace placeholders in JavaScript:** In the JavaScript code, replace `"YOUR_CONTRACT_ADDRESS"` with the actual contract address and paste the contract ABI into the `contractABI` variable.
4. **Include Web3.js in your HTML:** Include the Web3.js library in your HTML file:
```html
<script src="https://cdn.jsdelivr.net/npm/web3@1.7.0/dist/web3.min.js"></script>
```
(You can use a more recent version of Web3.js if desired, but 1.7.0 is a good starting point).
5. **Add MetaMask (or a similar provider):** Make sure you have MetaMask (or another Web3 provider) installed and configured in your browser.
6. **Create an HTML button:** Add a button to your HTML file that calls the `interactWithContract()` function when clicked:
```html
<button onclick="interactWithContract()">Interact with Contract</button>
```
7. **Open the HTML file in your browser:** Open the HTML file in your browser. When you click the button, MetaMask should prompt you to connect your account and sign the transactions.
Important Considerations for Production:
* **Security:**
* **Hashing National ID:** In a real-world application, you should *never* store sensitive data like national IDs in plain text on the blockchain. Hash it using a strong hashing algorithm (e.g., SHA256, or preferably a more secure hashing algorithm suitable for Ethereum, combined with salting) *before* storing it.
* **Access Control:** Consider adding more sophisticated access control mechanisms to restrict who can update or deactivate identities. For example, you might want to allow a trusted administrator to deactivate identities in certain cases.
* **Auditing:** Implement thorough auditing to track all changes made to identities. This can help you detect and respond to security breaches.
* **Privacy:**
* **Data Minimization:** Only store the absolutely necessary information on the blockchain. Avoid storing any data that is not essential for the functionality of your application.
* **Encryption:** Consider encrypting sensitive data before storing it on the blockchain. This can help to protect the data from unauthorized access. However, managing encryption keys securely is crucial. Think carefully about who needs access to the data and how they will obtain the keys. If you use encryption, make sure it's done *client-side* before sending data to the contract.
* **Off-Chain Storage:** Consider storing sensitive data off-chain (e.g., in a secure database) and only storing a hash of the data on the blockchain. This can significantly reduce the risk of data breaches. IPFS is often used for storing data off-chain in conjunction with a blockchain.
* **Scalability:**
* **Layer-2 Solutions:** Ethereum's mainnet has limited scalability. Consider using layer-2 scaling solutions (e.g., Optimism, Arbitrum, Polygon) to improve transaction throughput and reduce gas costs.
* **Gas Optimization:**
* **Careful Coding:** Write your Solidity code carefully to minimize gas costs. Avoid unnecessary storage operations and use efficient data structures.
* **Testing:** Thoroughly test your contract to ensure that it performs as expected and that gas costs are reasonable.
* **User Experience:**
* **Clear Communication:** Provide clear and concise instructions to users about how to use your application and what data is being stored on the blockchain.
* **Error Handling:** Implement robust error handling to gracefully handle unexpected situations. Provide informative error messages to users.
* **Regulations:**
* **GDPR and CCPA:** Be aware of data privacy regulations such as GDPR and CCPA and ensure that your application complies with these regulations. This is a complex area, and you may need to consult with a legal expert.
* **Immutability:** Remember that data stored on the blockchain is immutable. Once an identity is created, it cannot be completely deleted. You can only deactivate it. Consider the implications of immutability for your application's data model.
This comprehensive example provides a solid foundation for building a blockchain-powered digital identity system. Remember to carefully consider the security, privacy, scalability, and regulatory aspects before deploying your application to a production environment.
👁️ Viewed: 9
Comments