Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
This is a research project for blockchain-based Self Sovereign Identity, intended for eHealth Systems in Europe. This tech report is a project deliverable, written in Greek. ENG version shall follow.
git clone https://github.com/d-sfounis/bluechain_SSI.gitgit clone git@github.com:d-sfounis/bluechain_SSI.git/* SPDX-License-Identifier: GPL-3.0-or-later
* Author: Dimitrios Sfounis, for Bluechain Social Cooperative Enterprise, Athens (GR), February 2021
* Website: https://bluechain.tech
* Project: European Self-Sovereign Identity on blockchain, intended for electronic Health records,
* Funded under the European Cohesion Fund's "Vouchers for Innovation" programme.
*/
pragma solidity >=0.8.0 <0.9;
contract PassportManager {
/* Basic implementation of a user's identity: a Passport! */
struct Passport {
string flair_name; //User nickname. It's optional.
//The controller (owner) of this passport instance.
//Initially, it's the first user/deployer. This can change later on.
address controller;
//the main workhorse of this struct. This is a hashmap of
//key-value pairs of keccak256 hashes of the (string)sha256 checksum of any identity file (say, a PDF certificate)
//and a trust-score stored as a simple integer. The more people have voted to trust this particular document of your passport,
//the higher the score is. Simple, right? Probably not...
//bytes32 because keccak256, through the web3.eth lib, always produces 32 bytes (256 bits) of output.
mapping(bytes32 => uint) identity_files;
//Lookup table for the previous mapping, just so we don't ever have to deal with orphaned values.
//#NOTE: If you self-destruct this contract, be sure to iterate over `identity_files` and delete all keys using this Lookup Table.
mapping(bytes32 => bytes32) identity_files_LUT;
//Also this helps for easy, O(1) search of whether an address is here or not: //#NOTE: DEPRECATED in v0.2.0
//mapping(bytes32 => bool) identity_files_LUT_Bool;
//Delegates of this passport and its contained identity files, able to control it too.
//Disabling this for now, we'll see if we can circumvent delegation functionality as defined in the CALYSPSO paper by Kokkoris-Kogias.
//address[] delegates;
}
//The main database of our user IDs. This hashtable stores key-value pairs of
//address to (struct Passport) objects belonging to this smart contract's users.
//bytes32 because keccak256, through the web3 library, always produces 32 bytes (256 bits) of output.
//the public attribute just gives it an automatic getter. However, the getter only returns a tuple containing all atomic fields,
//and not the actual data structure.
mapping(address => Passport) public user_passports;
//And the associated lookup table, for quick O(1) lookup:
mapping(address => address) public user_passports_LUT;
/* Debugging Events! Respect these, they'll save you headache(s) */
event PassportInitialized(address passport_id, address by);
event AddedIDFileToPassport(address passport_id, bytes32 hashed_file);
event Voted(address passport_id, address voter);
/* Helper function to check if a user has initialized/created a Passport before */
function hasInitializedPassport(address addr) private view returns (bool) {
//All these checks cumulatively mean that a passport has been properly initialized
if(user_passports_LUT[addr] != address(0)){
Passport storage p = user_passports[addr];
if(p.controller != address(0)){
return true;
}
}
return false;
}
function initPassport(string memory nickname) public returns (string memory, address) {
require(!(hasInitializedPassport(msg.sender)), "Your User Passport is already initialized!");
//As taken by the Solidity docs:
//We cannot use "Passport[campaignID] = Passport(beneficiary, goal, 0, 0)"
//because the RHS creates a memory-struct "Passport" that contains a mapping.
Passport storage p = user_passports[msg.sender];
p.flair_name = nickname;
p.controller = msg.sender;
//Mark passport as initialized, and emit the event.
user_passports_LUT[msg.sender] = msg.sender;
emit PassportInitialized(user_passports_LUT[msg.sender], msg.sender);
return (p.flair_name, p.controller);
}
function addIDFileToPassport(address passport_id, bytes32 id_file) public returns (address, bytes32, uint) {
Passport storage p = user_passports[passport_id];
require(hasInitializedPassport(passport_id));
require(p.controller == msg.sender, "Sender/controller mismatch when accessing passport");
require(p.identity_files_LUT[id_file] == "", "ID File is already contained in this Passport!");
p.identity_files_LUT[id_file] = id_file;
//trust-score == 1 means we've self-added this file and only us do trust it. In other words, this is a new, self-trusted-only doc.
//#TODO: There's probably a better way to do this other than using magic numbers...
p.identity_files[id_file] = 1;
//Return all these for debugging and testing purposes. It's a tuple of (controller_address, identity_file_byte32hash, trust_score).
emit AddedIDFileToPassport(passport_id, id_file);
return (p.controller, p.identity_files_LUT[id_file], p.identity_files[id_file]);
}
//This function raises the trust score of a Passport, when a user vouches for it.
//Only users with already initiated Passports, and therefore active members of the community, can vouch for other people's Passports.
function voteForDocInPassport(address passport_id, bytes32 doc_id) public returns (address, bytes32, uint) {
require(PassportExists(passport_id), "Passport_id does not exist!");
require(passport_id != msg.sender, "You can't vote on the trust score of your own Passport's documents!");
Passport storage p = user_passports[passport_id];
require(DocExistsInPassport(p, doc_id), "Document doesn't exist in specified Passport!");
require(hasInitializedPassport(msg.sender), "You have to have an active Passport yourself to vote on others!");
//Everything ok, we found it. Raise its score by 1.
p.identity_files[doc_id] = p.identity_files[doc_id] + 1;
return (passport_id, doc_id, p.identity_files[doc_id]);
}
function PassportExists(address passport_id) private view returns (bool) {
if(user_passports_LUT[passport_id] == address(0)){
return false;
}
return true;
}
function DocExistsInPassport(Passport storage P, bytes32 doc_id) private view returns (bool) {
if(P.identity_files_LUT[doc_id] == bytes32(0)){
return false;
}
return true;
}
/* TODO 1: We need a voting function, to play with trust_scores of stored Passport documents */
/* DONE! */
/* TODO 2: We need a function to export DID documents and Verifiable Claims as per specification format. Possibly a view function, so it doesn't burn up gas */
/* TODO 3: We need some failsafe against Orphaned Passports... */
/* TODO 4: Currently, trust_scores of documents are handled like magic numbers. This is generally an anti-pattern, let's look into a better way of doing this. */
/* TODO 5: The contract should maintain a list of Arbitrator addresses, for example Gnomon's address. Votes to documents from those addresses should give +100 score instead of just +1 */
/* This is as per the CALYPSO paper where the Trust Committee is maintained as a separate group, and votes from them are stronger */
}// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.4.22 <0.9.0;
contract Migrations {
address public owner = msg.sender;
uint public last_completed_migration;
modifier restricted() {
require(
msg.sender == owner,
"This function is restricted to the contract's owner"
);
_;
}
function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}
}$ chmod +x ganache-2.5.4-linux-x86_64.AppImage$ ./ganache-2.5.4-linux-x86_64.AppImagemodule.exports = {
/*
* Εφιστούμε την προσοχή μας εδώ!
*/
networks: {
development: {
host: "127.0.0.1", // Localhost (default: none)
port: 7545, // Standard Ethereum port (default: none)
network_id: "5777", // Any network (default: none)
},
},
mocha: {
},
compilers: {
solc: {
version: "0.8.2", // Fetch exact version from solc-bin (default: truffle's version)
}
}
}$ truffle compile --all
$ truffle migrate --reset$ truffle compile --all
Compiling your contracts...
===========================
> Compiling ./contracts/Migrations.sol
> Compiling ./contracts/PassportManager.sol
> Artifacts written to /home/dimitris/Documents/Code/Bluechain_SSI_Truffle/bluechain_ssi/build/contracts
> Compiled successfully using:
- solc: 0.8.2+commit.661d1103.Emscripten.clang
$ truffle migrate --reset
Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.
Starting migrations...
======================
> Network name: 'development'
> Network id: 5777
> Block gas limit: 6721975 (0x6691b7)
1_initial_migration.js
======================
Replacing 'Migrations'
----------------------
> transaction hash: 0x4c95c2ef66121f10b67aed6429f8124527f6cba7fa99a321c7f00a6f5da0f605
> Blocks: 0 Seconds: 0
> contract address: 0x4dEcd29fc37dd3124b30f7412e9a16B910cfd79f
> block number: 995
> block timestamp: 1620854844
> account: 0x4c8C46E0269C7882A2427eF282a426eBb9716767
> balance: 95.11712696
> gas used: 246904 (0x3c478)
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00493808 ETH
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.00493808 ETH
2_deploy_contracts.js
=====================
Replacing 'PassportManager'
---------------------------
> transaction hash: 0x48f73ad6d3ea9537dcdaaaeeaa6aec4d17b068056340bf75c5009ba5d4838a92
> Blocks: 0 Seconds: 0
> contract address: 0xCcd04bB0b4beb0A544311c6ce1cE324Ff6c049EE
> block number: 997
> block timestamp: 1620854845
> account: 0x4c8C46E0269C7882A2427eF282a426eBb9716767
> balance: 95.09212666
> gas used: 1207502 (0x126cce)
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.02415004 ETH
PassportManager contract deployed at address: 0xCcd04bB0b4beb0A544311c6ce1cE324Ff6c049EE
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.02415004 ETH
Summary
=======
> Total deployments: 2
> Final cost: 0.02908812 ETH
const PassportManager = artifacts.require("PassportManager");
contract("Bluechain SSI PassportManager test-suite Alpha", accounts => {
it("should initialize a new passport linked with the current user's address", () => {
let this_address = accounts[0];
let this_nickname = "John Doe";
let meta;
return PassportManager.deployed()
.then(instance => {
meta = instance;
console.log("Test1 on PassportManager address: " + meta.address);
return meta.initPassport.call(this_nickname);
})
.then(returned_tuple => {
//console.log(returned_tuple[0]);
//console.log(returned_tuple[1]);
assert.equal(returned_tuple[0], this_nickname, "Nickname, passed and returned by PassportManager.initPassport(), should match!");
assert.equal(returned_tuple[1], this_address, "Controller address, passed and returned by PassportManager.initPassport(), should match!");
//If we're here, it means the previous call() has succeeded. Now,
//let's take things up a notch and let's actually send a transaction that changes the state of the blockchain.
//Remember: We can't check for return values with a transaction, we have to debug the tx id manually.
//#NOTE: We passed an extra parameter here. For more info on this special parameter object, check out:
//https://www.trufflesuite.com/docs/truffle/getting-started/interacting-with-your-contracts#making-a-transaction
const result = meta.initPassport.sendTransaction(this_nickname, {from: accounts[0]});
result.on('transactionHash', (hash) => {
console.log('TxHash', hash);
});
return result;
})
.then(result => {
//#NOTE: This is a hacky solution at ensuring Ganache has had time to process the tx.
console.log("Test 1 should've succeeded!");
});
});
it("should successfully initialize a 2nd passport linked with accounts[1].", () => {
let this_address = accounts[1];
let this_nickname = "Theocharis Iordanidis";
let meta;
return PassportManager.deployed()
.then(instance => {
meta = instance;
console.log("Test2 on PassportManager address: " + meta.address);
return meta.initPassport.call(this_nickname, {from: accounts[1]});
})
.then(returned_tuple => {
//console.log(returned_tuple[0]);
//console.log(returned_tuple[1]);
assert.equal(returned_tuple[0], this_nickname, "Nickname, passed and returned by PassportManager.initPassport(), should match!");
assert.equal(returned_tuple[1], this_address, "Controller address, passed and returned by PassportManager.initPassport(), should match!");
//If we're here, it means the previous call() has succeeded. Now,
//let's take things up a notch and let's actually send a transaction that changes the state of the blockchain.
//Remember: We can't check for return values with a transaction, we have to debug the tx id manually.
//#NOTE: We passed an extra parameter here. For more info on this special parameter object, check out:
//https://www.trufflesuite.com/docs/truffle/getting-started/interacting-with-your-contracts#making-a-transaction
const result = meta.initPassport.sendTransaction(this_nickname, {from: accounts[1]});
result.on('transactionHash', (hash) => {
console.log('TxHash', hash);
});
return result;
})
.then(result => {
//#NOTE: This is a hacky solution at ensuring Ganache has had time to process the tx.
console.log("Test 2 should've succeeded!");
});
});
it("should add an identity file sha256 hash to a controlled passport", () => {
let this_address = accounts[0];
let doc_hash = "0x21f3a9de43f07d855f49b946a10c30df432e8af95311435f77daf894216dcd41";
let meta;
return PassportManager.deployed()
.then(instance => {
meta = instance;
console.log("Test3 on PassportManager address: " + meta.address);
return meta.addIDFileToPassport.call(this_address, doc_hash);
})
.then(returned_tuple => {
assert.equal(returned_tuple[0], this_address, "Passport controller, passed and returned by PassportManager.addIDFileToPassport(), should match!");
assert.equal(returned_tuple[1], doc_hash, "Document hash (bytes32), passed and returned by PassportManager.addIDFileToPassport(), should match!");
assert.equal(returned_tuple[2], 1, "Trust score of newly added doc_hash should be 1!");
console.log(returned_tuple);
//Now let's actually pass a concrete, actuallly persistent transaction instead of a call.
const result = meta.addIDFileToPassport.sendTransaction(this_address, doc_hash, {from: accounts[0]});
result.on('transactionHash', (hash) => {
console.log('TxHash', hash);
});
return result;
})
.then(result => {
//#NOTE: This is a hacky solution at ensuring Ganache has had time to process the tx.
console.log("Test 3 should've succeeded!");
});
});
it("should add +1 to the trust score of a doc in a passport", () => {
let this_address = accounts[1];
let passport_address = accounts[0];
let doc_hash = "0x21f3a9de43f07d855f49b946a10c30df432e8af95311435f77daf894216dcd41";
let meta;
return PassportManager.deployed()
.then(instance => {
meta = instance;
console.log("Test4 on PassportManager address: " + meta.address);
return meta.voteForDocInPassport.call(passport_address, doc_hash, {from: this_address});
})
.then(returned_tuple => {
assert.equal(returned_tuple[0], passport_address, "Passport ID address, passed and returned by PassportManager.voteForDocInPassport(), should match!");
assert.equal(returned_tuple[1], doc_hash, "Document hash (bytes32), passed and returned by PassportManager.voteForDocInPassport(), should match!");
assert.equal(returned_tuple[2], 2, "Trust score of doc_id should have been raised to 2!");
console.log(returned_tuple);
//Now let's actually pass a concrete, actuallly persistent transaction instead of a call.
const result = meta.voteForDocInPassport.sendTransaction(passport_address, doc_hash, {from: this_address});
result.on('transactionHash', (hash) => {
console.log('TxHash', hash);
});
return result;
})
.then(result => {
//#NOTE: This is a hacky solution at ensuring Ganache has had time to process the tx.
console.log("Test 4 should've succeeded!");
});
});
/*it("return false", () => {
assert(0==1);
});
*/
});$ truffle testUsing network 'development'.
Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.
PassportManager contract deployed at address: 0xC6DAa380707E13829700EF40Ad7fE6f2A7297FcF
Contract: PassportManager testsuite Alpha
Test1 on PassportManager address: 0xC6DAa380707E13829700EF40Ad7fE6f2A7297FcF
TxHash 0x550509d1fea429e23c56745d14f6790bcc5b2389dd3a55aaf85303d1ded33d05
Test 1 should've succeeded!
✓ should initialize a new passport linked with the current user's address (404ms)
Test2 on PassportManager address: 0xC6DAa380707E13829700EF40Ad7fE6f2A7297FcF
TxHash 0x14cfae455ddb0d6161374f43f84e72df5ba3e10e42068e0a0d150f66390df2b8
Test 2 should've succeeded!
✓ should successfully initialize a 2nd passport linked with accounts[1]. (223ms)
Test3 on PassportManager address: 0xC6DAa380707E13829700EF40Ad7fE6f2A7297FcF
Result {
'0': '0x4c8C46E0269C7882A2427eF282a426eBb9716767',
'1': '0x21f3a9de43f07d855f49b946a10c30df432e8af95311435f77daf894216dcd41',
'2': BN {
negative: 0,
words: [ 1, <1 empty item> ],
length: 1,
red: null
}
}
TxHash 0x013ae2f108056fee7d2bc5882ea63e7a6b5f923925060029b495aad0894f9d74
Test 3 should've succeeded!
✓ should add an identity file sha256 hash to a controlled passport (210ms)
Test4 on PassportManager address: 0xC6DAa380707E13829700EF40Ad7fE6f2A7297FcF
Result {
'0': '0x4c8C46E0269C7882A2427eF282a426eBb9716767',
'1': '0x21f3a9de43f07d855f49b946a10c30df432e8af95311435f77daf894216dcd41',
'2': BN {
negative: 0,
words: [ 2, <1 empty item> ],
length: 1,
red: null
}
}
TxHash 0x6b5031a08041859f969da7babbae59747d5bf997b00c1724920f3fdc0e3fb9e9
Test 4 should've succeeded!
✓ should add +1 to the trust score of a doc in a passport (158ms)
4 passing (1s)
Υπεύθυνοι για την ανάπτυξη του λογισμικού και του κώδικα στο παρόν έργο.