├── .gitignore ├── migrations ├── 1_initial_migration.js └── 2_deploy_contracts.js ├── contributors.txt ├── .travis.yml ├── truffle.js ├── package.json ├── contracts ├── Migrations.sol ├── SmartIdentityRegistry.sol └── SmartIdentity.sol ├── test ├── functional │ ├── SmartIdentity │ │ ├── signingKey.js │ │ ├── encryptionKey.js │ │ ├── blocklocktest.js │ │ ├── contracttest.js │ │ ├── endorsementtest.js │ │ └── attributetest.js │ └── SmartIdentityRegistry │ │ └── sicontracttest.js └── scenarios │ ├── bob.js │ └── alice.js ├── CONTRIBUTING.md ├── LICENCE.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/contracts/ 3 | scripts/ 4 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | var SmartIdentity = artifacts.require("SmartIdentity.sol"); 2 | var SmartIdentityRegistry = artifacts.require("SmartIdentityRegistry.sol"); 3 | 4 | module.exports = function(deployer) { 5 | deployer.deploy(SmartIdentity); 6 | deployer.deploy(SmartIdentityRegistry); 7 | deployer.link(SmartIdentity, SmartIdentityRegistry); 8 | }; 9 | -------------------------------------------------------------------------------- /contributors.txt: -------------------------------------------------------------------------------- 1 | Andy Loughran 2 | Maxine Glennerster 3 | Tyler Welmans 4 | Peter Hempsall 5 | Jamie Bentham 6 | Jonny Khattak 7 | Ciaran Molloy 8 | Eamon Scullion 9 | Garry Irwin 10 | Shannen Smyth 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | sudo: required 4 | 5 | node_js: 6 | - "6.9" 7 | 8 | addons: 9 | apt: 10 | sources: 11 | - ubuntu-toolchain-r-test 12 | packages: 13 | - g++-4.9 14 | - libstdc++-4.9-dev 15 | - openssl 16 | env: 17 | - CXX=g++-4.9 18 | 19 | before_install: 20 | - npm install -g npm@latest 21 | - npm install -g ethereumjs-testrpc truffle 22 | 23 | install: 24 | - npm install 25 | 26 | script: 27 | - testrpc & 28 | - truffle test 29 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | var DefaultBuilder = require("truffle-default-builder"); 2 | 3 | module.exports = { 4 | build: new DefaultBuilder({ 5 | "index.html": "index.html", 6 | "app.js": [ 7 | "javascripts/app.js" 8 | ], 9 | "app.css": [ 10 | "stylesheets/app.css" 11 | ], 12 | "images/": "images/" 13 | }), 14 | networks: { 15 | development: { 16 | host: "localhost", 17 | port: 8545, 18 | network_id: "*" // Match any network id 19 | } 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smartidentity", 3 | "version": "0.0.3", 4 | "description": "Smart Identity sample contracts & test suite", 5 | "main": "truffle", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "dependencies": { 10 | "ethereumjs-testrpc": "^4.0.1", 11 | "truffle": "^3.4.7", 12 | "truffle-default-builder": "^2.0.0" 13 | }, 14 | "devDependencies": {}, 15 | "scripts": { 16 | "test": "./node_modules/.bin/truffle test" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+ssh://git@github.com:SmartIdentity/smartId-contracts.git" 21 | }, 22 | "keywords": [ 23 | "truffle", 24 | "ethereum", 25 | "solidity", 26 | "jasmine", 27 | "mocha" 28 | ], 29 | "author": "Deloitte UK Blockchain Labs", 30 | "license": "Apache-2.0", 31 | "homepage": "https://www.deloitte.co.uk/smartid" 32 | } 33 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.2; 2 | 3 | /** 4 | * The Migrations contract is boilerplate code provided by the Truffle IDE that 5 | * helps deploy contracts to the Ethereum network. 6 | * This contract stores a number (last_completed_migration) that corresponds to the 7 | * last applied migration script found in the migrations folder. 8 | * The numbering convention is x_script_name.ks with x starting at 1. 9 | * As this contract stores the number of the last deployment script applied, 10 | * Truffle will not run those scripts again. 11 | */ 12 | 13 | contract Migrations { 14 | 15 | address public owner; 16 | uint public last_completed_migration; 17 | 18 | /** 19 | * Modifier to check to see if the value of msg.sender is the same as owner. 20 | * The underscore _ denotes the inclusion of the remainder of the function body 21 | * to which the modifier is applied 22 | */ 23 | modifier restricted() { 24 | if (msg.sender == owner) _; 25 | } 26 | 27 | /** 28 | * Constructor of the Migrations contract which assigns owner to the value of msg.sender 29 | */ 30 | function Migrations() { 31 | owner = msg.sender; 32 | } 33 | 34 | /** 35 | * A function with the signature 'setCompleted(uint)' is required. 36 | * Uses the restricted modifier to update the last_completed_migration variable 37 | */ 38 | function setCompleted(uint completed) restricted { 39 | last_completed_migration = completed; 40 | } 41 | 42 | /** 43 | * Function which provides the contract upgrade mechanism. 44 | * This function takes the address of the new contraxt, creates a handle and 45 | * calls the setCompleted function on the new contract 46 | */ 47 | function upgrade(address new_address) restricted { 48 | Migrations upgraded = Migrations(new_address); 49 | upgraded.setCompleted(last_completed_migration); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/functional/SmartIdentity/signingKey.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The purpose of this test contract is to test the functions in SmartIdentity.sol 3 | * that alter the signing public key. 4 | */ 5 | 6 | var SmartIdentity = artifacts.require("SmartIdentity"); 7 | 8 | contract('SmartIdentity', function(accounts) { 9 | 10 | var smartIdentity, 11 | owner, 12 | thirdparty, 13 | publicSigningKey, 14 | newPublicSigningKey; 15 | 16 | before("Setup the smart identity and hydrate the required variables", function(done) { 17 | owner = accounts[0]; 18 | thirdparty = accounts[1]; 19 | 20 | SmartIdentity.new({from: owner}) 21 | .then(function(data) { 22 | smartIdentity = data; 23 | done(); 24 | }); 25 | 26 | return smartIdentity, 27 | owner, 28 | thirdparty; 29 | }); 30 | 31 | publicSigningKey = "Signing Public Key 1"; 32 | newPublicSigningKey = "Signing Public Key 2"; 33 | 34 | describe("Signing Public Key tests", function() { 35 | 36 | it("will set signing public key as the owner", function() { 37 | return smartIdentity.setSigningPublicKey(publicSigningKey, {from: owner}) 38 | .then(function(response) { 39 | var newSigningKeyStatus = response.logs[0].args.status; 40 | assert.equal(3, newSigningKeyStatus, "Transaction returned unexpected status"); 41 | assert.isOk(response, "Attribute addition failed"); 42 | }); 43 | }); 44 | 45 | it("will confirm that the owner's signing public key was set successfully", function() { 46 | return smartIdentity.signingPublicKey.call({from: owner}) 47 | .then(function(response) { 48 | assert.equal(response, publicSigningKey, "signingPublicKey doesn't exist"); 49 | }); 50 | }); 51 | 52 | it("will update the signing public key as the owner", function() { 53 | return smartIdentity.setSigningPublicKey(newPublicSigningKey, {from: owner}) 54 | .then(function(response) { 55 | var newSigningKeyStatus = response.logs[0].args.status; 56 | assert.equal(3, newSigningKeyStatus, "Transaction returned unexpected status"); 57 | assert.isOk(response, "Attribute addition failed"); 58 | }); 59 | }); 60 | 61 | it("will confirm that the owner's signing public key has been changed", function() { 62 | return smartIdentity.signingPublicKey.call({from: owner}) 63 | .then(function(response) { 64 | assert.equal(response, newPublicSigningKey, "signingPublicKey has not been changed"); 65 | }); 66 | }); 67 | 68 | it("will not allow a third party to modify the owner's signing public key", function() { 69 | return smartIdentity.setSigningPublicKey(publicSigningKey, {from: thirdparty}) 70 | .catch(function(error) { 71 | assert.isOk(error, 'Expected error has not been caught'); 72 | }); 73 | }); 74 | 75 | it("will confirm that the owner's signing public key has not been changed by the third party", function() { 76 | return smartIdentity.signingPublicKey.call({from: owner}) 77 | .then(function(response) { 78 | assert.equal(response, newPublicSigningKey, "signingPublicKey has been changed"); 79 | }); 80 | }); 81 | 82 | it("will allow a third party to read the owner's signing public key", function() { 83 | return smartIdentity.signingPublicKey.call({from: thirdparty}) 84 | .then(function(response) { 85 | assert.equal(response, newPublicSigningKey, "signingPublicKey has not been changed"); 86 | }); 87 | }); 88 | 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /test/functional/SmartIdentity/encryptionKey.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The purpose of this test contract is to test the functions in SmartIdentity.sol 3 | * that alter the encryption public key. 4 | */ 5 | 6 | var SmartIdentity = artifacts.require("SmartIdentity"); 7 | 8 | contract('SmartIdentity', function(accounts) { 9 | 10 | var smartIdentity, 11 | owner, 12 | thirdparty, 13 | publicEncryptionKey, 14 | newPublicEncryptionKey; 15 | 16 | before("Setup the Smart Identity contract and hydrate the required variables", function(done) { 17 | owner = accounts[0]; 18 | thirdparty = accounts[1]; 19 | 20 | SmartIdentity.new({from: owner}) 21 | .then(function(response) { 22 | smartIdentity = response; 23 | done(); 24 | }); 25 | 26 | return smartIdentity, 27 | owner, 28 | thirdparty; 29 | }); 30 | 31 | publicEncryptionKey = "Example Public Key 1"; 32 | newPublicEncryptionKey = "Example Public Key 2"; 33 | 34 | describe("Encryption Public Key tests", function() { 35 | 36 | it("will set encryption public key as the owner", function() { 37 | return smartIdentity.setEncryptionPublicKey(publicEncryptionKey, {from: owner}) 38 | .then(function(response) { 39 | var newEncryptKeyStatus = response.logs[0].args.status; 40 | assert.equal(3, newEncryptKeyStatus, "Transaction returned unexpected status"); 41 | assert.isOk(response, "Attribute addition failed"); 42 | }); 43 | }); 44 | 45 | it("will confirm that the owner's encryption public key was set successfully", function() { 46 | return smartIdentity.encryptionPublicKey.call({from: owner}) 47 | .then(function(response) { 48 | assert.equal(response, publicEncryptionKey, "encryptionPublicKey doesn't exist"); 49 | }); 50 | }); 51 | 52 | it("will update the encryption public key as the owner", function() { 53 | return smartIdentity.setEncryptionPublicKey(newPublicEncryptionKey, {from: owner}) 54 | .then(function(response) { 55 | var newEncryptKeyStatus = response.logs[0].args.status; 56 | assert.equal(3, newEncryptKeyStatus, "Transaction returned unexpected status"); 57 | assert.isOk(response, "Attribute addition failed"); 58 | }); 59 | }); 60 | 61 | it("will confirm that the owner's encryption public key has been changed", function() { 62 | return smartIdentity.encryptionPublicKey.call({from: owner}) 63 | .then(function(response) { 64 | assert.equal(response, newPublicEncryptionKey, "encryptionPublicKey has not been changed"); 65 | }); 66 | }); 67 | 68 | it("will not allow a third party to modify the owner's encryption public key", function() { 69 | return smartIdentity.setEncryptionPublicKey(publicEncryptionKey, {from: thirdparty}) 70 | .catch(function(error) { 71 | assert.isOk(error, 'Expected error has not been caught'); 72 | }); 73 | }); 74 | 75 | it("will confirm that the owner's encryption public key has not been changed by the third party", function() { 76 | return smartIdentity.encryptionPublicKey.call({from: owner}) 77 | .then(function(response) { 78 | assert.equal(response, newPublicEncryptionKey, "encryptionPublicKey has been changed"); 79 | }); 80 | }); 81 | 82 | it("will allow a third party to read the owner's encryption public key", function() { 83 | return smartIdentity.encryptionPublicKey.call({from: thirdparty}) 84 | .then(function(response) { 85 | assert.equal(response, newPublicEncryptionKey, "encryptionPublicKey has not been changed"); 86 | }); 87 | }); 88 | 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /test/functional/SmartIdentity/blocklocktest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The purpose of this test contract is to test the blocklock function in 3 | * SmartIdentity.sol. 4 | */ 5 | 6 | var SmartIdentity = artifacts.require("SmartIdentity"); 7 | 8 | contract('SmartIdentity', function(accounts) { 9 | 10 | var smartIdentity, 11 | owner, 12 | override, 13 | thirdparty; 14 | 15 | before("Setup the Smart Identity contract and hydrate the required variables", function() { 16 | owner = accounts[0]; 17 | override = owner; 18 | thirdparty = accounts[1]; 19 | 20 | SmartIdentity.new({from: owner}) 21 | .then(function(data) { 22 | smartIdentity = data; 23 | }); 24 | 25 | return smartIdentity, 26 | owner, 27 | thirdparty; 28 | }); 29 | 30 | describe("Blocklock tests", function() { 31 | 32 | it("will create a new user and immediately set a new owner successfully", function() { 33 | return SmartIdentity.new({from: owner}).then(function(identity) { 34 | return identity.setOwner(thirdparty, {from: override}) 35 | }).then(function(response) { 36 | var newOwnerStatus = response.logs[0].args.status; 37 | assert.equal(3, newOwnerStatus, "Transaction returned unexpected status"); 38 | assert.isOk(response, "Setting new owner failed"); 39 | }); 40 | }); 41 | 42 | it("will set thirdparty as the new owner by an override user", function() { 43 | return smartIdentity.setOwner(thirdparty, {from: override}).then(function(response) { 44 | var newOwnerStatus = response.logs[0].args.status; 45 | assert.equal(3, newOwnerStatus, "Transaction returned unexpected status"); 46 | assert.isOk(response, "Setting new owner failed"); 47 | }); 48 | }); 49 | 50 | it("will invoke the blocklock function when repeatedly setting a new owner as an override user", function() { 51 | return smartIdentity.setOwner(thirdparty, {from: override}).then(function(){ 52 | assert.isOk(error, "Expected error has not been thrown"); 53 | }).catch(function(error) { 54 | assert.isOk(error, "Expected error has not been caught"); 55 | }); 56 | }); 57 | 58 | it("will mine through 20 blocks to simulate the blocklock time passing"); 59 | for (var i = 1; i < 21; i++) { 60 | it("BLOCK MINING " + i, function() { 61 | return SmartIdentity.new(); 62 | }); 63 | } 64 | 65 | it("will set the override to a thirdparty owner", function() { 66 | return smartIdentity.setOverride(accounts[3], {from: thirdparty}).then(function(response) { 67 | var newOverrideStatus = response.logs[0].args.status; 68 | assert.equal(3, newOverrideStatus, "Transaction returned unexpected status"); 69 | assert.isOk(response, "Setting override failed"); 70 | }); 71 | }); 72 | 73 | it("will invoke the blocklock function when repeatedly setting a new owner as an override user", function() { 74 | smartIdentity.setOwner(thirdparty, {from: override}) 75 | .catch(function(error) { 76 | assert.isOk(error, "Expected error has not been caught"); 77 | }); 78 | }); 79 | 80 | it("will once again mine through 20 blocks to simulate the blocklock time passing"); 81 | for (var j = 1; j < 21; j++) { 82 | it("BLOCK MINING " + j, function() { 83 | return SmartIdentity.new(); 84 | }); 85 | } 86 | 87 | it("will set the new owner back to 'owner' as the new override user", function(done) { 88 | smartIdentity.setOwner(owner, {from: accounts[3]}) 89 | .then(function(response) { 90 | var newOwnerStatus = response.logs[0].args.status; 91 | assert.equal(3, newOwnerStatus, "Transaction returned unexpected status"); 92 | assert.isOk(response, "Setting new owner failed"); 93 | done(); 94 | }); 95 | }); 96 | 97 | it("will invoke the blocklock function when repeatedly setting a new owner as an override user", function(done) { 98 | smartIdentity.setOwner(thirdparty, {from: override}) 99 | .catch(function(error) { 100 | assert.isOk(error, "Expected error has not been caught"); 101 | done(); 102 | }); 103 | }); 104 | }); 105 | }, 5000); 106 | -------------------------------------------------------------------------------- /test/scenarios/bob.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Bob has a current account with his bank, but wants to apply for a mortgage. 3 | * 4 | * The mortgage department need additional information from Bob to prove he’s employed. 5 | * Bob’s employer has endorsed his Smart ID. 6 | * This has been shared with the bank, who can verify his employment status - now and on an ongoing basis. 7 | * 8 | */ 9 | 10 | var SmartIdentity = artifacts.require("SmartIdentity"); 11 | 12 | contract('SmartIdentity', function(accounts) { 13 | 14 | var bob = {}, 15 | employer = {}, 16 | bank = {}, 17 | employmentAttributeHash, 18 | endorsementHash; 19 | 20 | before("Setup the scenario", function() { 21 | bob.address = accounts[0]; 22 | employer.address = accounts[1]; 23 | bank.address = accounts[2]; 24 | 25 | employmentAttributeHash = '0xca02b2202ffaacbd499438ef6d594a48f7a7631b60405ec8f30a0d7c096dc3ff'; 26 | endorsementHash = '0xca02b2202ffaacbd499438ef6d594a48f7a7631b60405ec8f30a0d7c096d54d5'; 27 | 28 | return bob, 29 | employer, 30 | bank, 31 | employmentAttributeHash, 32 | endorsementHash; 33 | 34 | }); 35 | 36 | describe("Scenario: Bob has a current account with his bank, but needs to supply additional information to apply for a mortgage.", function() { 37 | 38 | it("Should create an identity for Bob so that he can create employement status attributes", function() { 39 | var encryptionKey = "test encryption key"; 40 | 41 | return SmartIdentity.new({from: bob.address}) 42 | .then(function(data) { 43 | bob.identity = data.address; 44 | assert.isOk(data, "Bob's Identity failed to be created"); 45 | return data.setEncryptionPublicKey(encryptionKey, {from: bob.address}) 46 | }).then(function() { 47 | SmartIdentity.at(bob.identity).then(function(identity){ 48 | return identity.encryptionPublicKey.call(); 49 | }).then(function(returnedEncryptionKey) { 50 | assert.equal(encryptionKey, returnedEncryptionKey, "failed to add key"); 51 | }) 52 | }) 53 | }); 54 | 55 | it("Should create an identity for Bob's Employer so they can endorse his employment status", function() { 56 | return SmartIdentity.new({from: employer.address}) 57 | .then(function(data) { 58 | employer.identity = data.address; 59 | assert.isOk(data, "Bob's Employer Identity failed to be created"); 60 | }); 61 | }); 62 | 63 | it("Should create an identity for Bob's Bank so they can verify the employment status", function() { 64 | return SmartIdentity.new({from: bank.address}) 65 | .then(function(data) { 66 | bank.identity = data.address; 67 | assert.isOk(data, "Bob's Bank Identity failed to be created"); 68 | }); 69 | }); 70 | 71 | it("Should allow Bob to create a new employment status attribute", function() { 72 | return SmartIdentity.at(bob.identity).addAttribute(employmentAttributeHash, {from: bob.address}) 73 | .then(function(data) { 74 | assert.isOk(data, 'Employment status attribute failed to be created'); 75 | }); 76 | }); 77 | 78 | it("Should allow Bob's Employer to endorse Bob's employment status", function() { 79 | return SmartIdentity.at(bob.identity).addEndorsement(employmentAttributeHash, endorsementHash, {from: employer.address}) 80 | .then(function(data) { 81 | assert.isOk(data, "Bob's employment status failed to be endorsed"); 82 | }); 83 | }); 84 | 85 | it("Should allow Bob to accept his Employer's endorsement for his employment status", function() { 86 | return SmartIdentity.at(bob.identity).acceptEndorsement(employmentAttributeHash, endorsementHash, {from: bob.address}) 87 | .then(function(data) { 88 | assert.isOk(data, "Bob failed to accept the employment endorsement"); 89 | }); 90 | }); 91 | 92 | it("Should allow Bob's Bank to check that an endorsement exists for his attribute; verifying his employement status", function() { 93 | return SmartIdentity.at(bob.identity).checkEndorsementExists.call(employmentAttributeHash, endorsementHash, {from: bank.address}) 94 | .then(function(data) { 95 | assert.equal(data.valueOf(), true, "Bank failed to find endorsements for Bob's employment status"); 96 | }); 97 | }); 98 | 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /test/scenarios/alice.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Alice has a current account with her bank, but wants to open an account for her business. 3 | * 4 | * Instead of the retail bank having to transfer confidential documentation to the commercial bank in order to validate 5 | * Alice’s identity, both organisations trust Alice’s Smart ID. In order to pass the commercial bank's KYC checks, 6 | * Alice simply sends them her digitally-signed documents from the retail bank (through her Smart ID app). 7 | * 8 | */ 9 | 10 | var SmartIdentity = artifacts.require("SmartIdentity"); 11 | 12 | contract('SmartIdentity', function(accounts) { 13 | 14 | var alice = {}, 15 | retailBank = {}, 16 | commercialBank = {}, 17 | kycAttributeHash, 18 | endorsementHash; 19 | 20 | before("Setup the scenario", function() { 21 | alice.address = accounts[0]; 22 | retailBank.address = accounts[1]; 23 | commercialBank.address = accounts[2]; 24 | 25 | kycAttributeHash = '0xca02b2202ffaacbd499438ef6d594a48f7a7631b60405ec8f30a0d7c096dc3ff'; 26 | endorsementHash = '0xca02b2202ffaacbd499438ef6d594a48f7a7631b60405ec8f30a0d7c096d54d5'; 27 | 28 | return alice, 29 | retailBank, 30 | commercialBank, 31 | kycAttributeHash, 32 | endorsementHash; 33 | 34 | }); 35 | 36 | describe("Scenario: Alice has a current account with her bank, but wants to open an account for her business.", function() { 37 | 38 | it("Should create an identity for Alice so that she can create KYC attributes", function() { 39 | var encryptionKey = "test encryption key"; 40 | return SmartIdentity.new({from: alice.address}) 41 | .then(function(data) { 42 | alice.identity = data.address; 43 | assert.isOk(data, "Alice's Identity failed to be created"); 44 | 45 | return data.setEncryptionPublicKey(encryptionKey, {from: alice.address}) 46 | }).then(function() { 47 | SmartIdentity.at(alice.identity).then(function(identity){ 48 | return identity.encryptionPublicKey.call(); 49 | }).then(function(returnedEncryptionKey) { 50 | assert.equal(encryptionKey, returnedEncryptionKey, "failed to add key"); 51 | }); 52 | }); 53 | }); 54 | 55 | it("Should create an identity for Alice's Retail Bank so they can endorse her KYC checks", function() { 56 | return SmartIdentity.new({from: retailBank.address}) 57 | .then(function(data) { 58 | retailBank.identity = data.address; 59 | assert.isOk(data, "Alice's Retail Bank Identity failed to be created"); 60 | }); 61 | }); 62 | 63 | it("Should create an identity for Alice's Commercial Bank so they can verify the KYC checks", function() { 64 | return SmartIdentity.new({from: commercialBank.address}) 65 | .then(function(data) { 66 | commercialBank.identity = data.address; 67 | assert.isOk(data, "Alice's Commercial Bank Identity failed to be created"); 68 | }); 69 | }); 70 | 71 | it("Should allow Alice to create a new KYC attribute", function() { 72 | return SmartIdentity.at(alice.identity).addAttribute(kycAttributeHash, {from: alice.address}) 73 | .then(function(data) { 74 | assert.isOk(data, 'KYC attribute failed to be created'); 75 | }); 76 | }); 77 | 78 | it("Should allow Alice's Retail Bank to endorse Alice's KYC attribute", function() { 79 | return SmartIdentity.at(alice.identity).addEndorsement(kycAttributeHash, endorsementHash, {from: retailBank.address}) 80 | .then(function(data) { 81 | assert.isOk(data, "Alice's KYC checks failed to be endorsed"); 82 | }); 83 | }); 84 | 85 | it("Should allow Alice to accept her Retail Bank's endorsement for her attribute", function() { 86 | return SmartIdentity.at(alice.identity).acceptEndorsement(kycAttributeHash, endorsementHash, {from: alice.address}) 87 | .then(function(data) { 88 | assert.isOk(data, "Alice failed to accept the KYC endorsement"); 89 | }); 90 | }); 91 | 92 | it("Should allow Alice's Bank to check that an endorsement exists for her KYC attribute; verifying her KYC checks", function() { 93 | return SmartIdentity.at(alice.identity).checkEndorsementExists.call(kycAttributeHash, endorsementHash, {from: commercialBank.address}) 94 | .then(function(data) { 95 | assert.equal(data.valueOf(), true, "Bank failed to find endorsements for Alice's KYC checks"); 96 | }); 97 | }); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /test/functional/SmartIdentityRegistry/sicontracttest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The purpose of this test contract is to test the functions in SmartIdentityRegistry.sol. 3 | */ 4 | 5 | var SmartIdentityRegistry = artifacts.require("SmartIdentityRegistry"); 6 | 7 | contract('SmartIdentityRegistry', function(accounts) { 8 | 9 | var registry, 10 | contractRegistry1, 11 | contractRegistry2, 12 | contracthash1, 13 | contracthash2; 14 | 15 | contracthash1 = '0xca02b2202ffaacbd499438ef6d594a48f7a7631b60405ec8f30a0d7c096d54d5'; 16 | contracthash2 = '0xca02b2202ffaacbd499438ef6d594a48f7a7631b60405ec8f30a0d7c096dc3ff'; 17 | 18 | before("Setup the Smart Identity registry and hydrate the required variables", function(done) { 19 | contractRegistry1 = accounts[0]; 20 | contractRegistry2 = accounts[1]; 21 | 22 | SmartIdentityRegistry.new({from: contractRegistry1}) 23 | .then(function(response) { 24 | registry = response; 25 | done(); 26 | }); 27 | 28 | return registry, 29 | contractRegistry1, 30 | contractRegistry2; 31 | }); 32 | 33 | describe("SmartIdentityRegistry tests", function() { 34 | 35 | it("will submit a contract into the registry", function() { 36 | return registry.submitContract(contracthash1, {from: contractRegistry1}) 37 | .then(function(response) { 38 | assert.isOk(response, 'Contract submitting failed'); 39 | }); 40 | }); 41 | 42 | it("will prove that the registry owner can approve a contract", function() { 43 | return registry.approveContract(contracthash1, {from: contractRegistry1}) 44 | .then(function(response) { 45 | assert.isOk(response, 'Contract approval failed'); 46 | }); 47 | }); 48 | 49 | it("will prove that a non-owner cannot approve a contract", function() { 50 | return registry.approveContract(contracthash1, {from: contractRegistry2}) 51 | .catch(function(error) { 52 | assert.isOk(error, "Expected error has not been caught"); 53 | }); 54 | }); 55 | 56 | it("will prove that the registry owner can reject a contract", function() { 57 | return registry.rejectContract(contracthash1, {from: contractRegistry1}) 58 | .then(function(response) { 59 | assert.isOk(response, 'Contract rejection failed'); 60 | }); 61 | }); 62 | 63 | it("will prove that a non-owner cannot reject a contract", function() { 64 | return registry.rejectContract(contracthash1, {from: contractRegistry2}) 65 | .catch(function(error) { 66 | assert.isOk(error, "Expected error has not been caught"); 67 | }); 68 | }); 69 | 70 | it("will delete a contract from the registry", function() { 71 | registry.submitContract(contracthash2, {from: contractRegistry1}) 72 | .then(function(response) { 73 | assert.isOk(response, 'Contract submitting failed'); 74 | }); 75 | return registry.deleteContract(contracthash2).then(function(response) { 76 | assert.isOk(response, 'Contract failed to be deleted'); 77 | }); 78 | }); 79 | 80 | it("will verify a contract is not valid", function() { 81 | registry.submitContract(contracthash2, {from: contractRegistry1}) 82 | .then(function(response) { 83 | assert.isOk(response, 'Contract submitting failed'); 84 | }); 85 | return registry.isValidContract(contracthash2).then(function(response) { 86 | assert.isOk(response, 'Failed to verify contract'); 87 | }); 88 | }); 89 | 90 | it("will verify a contract is not valid and will throw an error", function() { 91 | registry.submitContract(contracthash2, {from: contractRegistry1}) 92 | .then(function(response) { 93 | assert.isOk(response, 'Contract submitting failed'); 94 | }); 95 | registry.rejectContract(contracthash2, {from: contractRegistry1}); 96 | return registry.isValidContract(contracthash2) 97 | .catch(function(error) { 98 | assert.isOk(error, "Expected error has not been caught"); 99 | }); 100 | }); 101 | 102 | it("will verify a contract is valid", function() { 103 | registry.submitContract(contracthash2, {from: contractRegistry1}) 104 | .then(function(response) { 105 | assert.isOk(response, 'Contract submitting failed'); 106 | }); 107 | registry.approveContract(contracthash2, {from: contractRegistry1}); 108 | return registry.isValidContract(contracthash2) 109 | .then(function(response) { 110 | assert.isOk(response, 'Failed to verify contract'); 111 | }); 112 | }); 113 | }); 114 | 115 | }); 116 | -------------------------------------------------------------------------------- /contracts/SmartIdentityRegistry.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.0; 2 | 3 | /** 4 | * The purpose of this contract is to provide a mechanism to verify that a contract is valid 5 | */ 6 | 7 | contract SmartIdentityRegistry { 8 | 9 | address private owner; 10 | uint constant PENDING = 0; 11 | uint constant ACTIVE = 1; 12 | uint constant REJECTED = 2; 13 | 14 | /** 15 | * Constructor of the registry. 16 | */ 17 | function SmartIdentityRegistry() { 18 | owner = msg.sender; 19 | } 20 | 21 | /** 22 | * The SIContract structure: every SIContract is composed of: 23 | * - Hash of contract bytecode 24 | * - Account that submitted the address 25 | * - Status - 0 = pending, 1 = active, 2 = rejected. 26 | */ 27 | struct SIContract { 28 | bytes32 hash; 29 | address submitter; 30 | uint status; 31 | } 32 | 33 | /** 34 | * Mapping for contract registry. 35 | */ 36 | mapping(bytes32 => SIContract) public sicontracts; 37 | 38 | /** 39 | * The only permission worth setting; doing the reverse is pointless as a contract 40 | * owner can interact with the contract as an anonymous third party simply by using 41 | * another public key address. 42 | */ 43 | modifier onlyBy(address _account) { 44 | if (msg.sender != _account) { 45 | revert(); 46 | } 47 | _; 48 | } 49 | 50 | /** 51 | * Anyone can submit a contract for acceptance into a registry. 52 | */ 53 | function submitContract(bytes32 _contractHash) returns(bool) { 54 | var sicontract = sicontracts[_contractHash]; 55 | sicontract.hash = _contractHash; 56 | sicontract.submitter = msg.sender; 57 | sicontract.status = PENDING; 58 | return true; 59 | } 60 | 61 | /** 62 | * Only the registry owner (ideally multi-sig) can approve a contract. 63 | */ 64 | function approveContract(bytes32 _contractHash) onlyBy(owner) returns(bool) { 65 | var sicontract = sicontracts[_contractHash]; 66 | sicontract.status = ACTIVE; 67 | return true; 68 | } 69 | 70 | /** 71 | * Only the registry owner (ideally multi-sig) can reject a contract. 72 | */ 73 | function rejectContract(bytes32 _contractHash) onlyBy(owner) returns(bool) { 74 | var sicontract = sicontracts[_contractHash]; 75 | sicontract.status = REJECTED; 76 | return true; 77 | } 78 | 79 | /** 80 | * Only the registry owner and original submitter can delete a contract. 81 | * A contract in the rejected list cannot be removed. 82 | */ 83 | function deleteContract(bytes32 _contractHash) returns(bool) { 84 | var sicontract = sicontracts[_contractHash]; 85 | if (sicontract.status != REJECTED) { 86 | if (sicontract.submitter == msg.sender) { 87 | if (msg.sender == owner) { 88 | delete sicontracts[_contractHash]; 89 | return true; 90 | } 91 | } 92 | } else { 93 | revert(); 94 | } 95 | } 96 | 97 | /** 98 | * This is the public registry function that contracts should use to check 99 | * whether a contract is valid. It's defined as a function, rather than .call 100 | * so that the registry owner can choose to charge based on their reputation 101 | * of managing good contracts in a registry. 102 | * 103 | * Using a function rather than a call also allows for better management of 104 | * dependencies when a chain forks, as the registry owner can choose to kill 105 | * the registry on the wrong fork to stop this function executing. 106 | */ 107 | function isValidContract(bytes32 _contractHash) returns(bool) { 108 | if (sicontracts[_contractHash].status == ACTIVE) { 109 | return true; 110 | } 111 | if (sicontracts[_contractHash].status == REJECTED) { 112 | revert(); 113 | } else { 114 | return false; 115 | } 116 | } 117 | 118 | /** 119 | * Kill function to end a registry (useful for hard forks). 120 | */ 121 | function kill() onlyBy(owner) returns(uint) { 122 | suicide(owner); 123 | } 124 | 125 | /***********************************************************************************************/ 126 | // The purpose of this contract is to enable the following logic to be used in other contracts: 127 | 128 | /** 129 | * Check Identity is valid using modifier. 130 | * 131 | * This will also serve as protection against forks, because at the time the chain forks, 132 | * we can kill the registry, which will then 'invalidate' the identities that are stored 133 | * on the old chain. 134 | */ 135 | 136 | /** 137 | * modifier checkIdentity(address identity, address registry) return (bool) { 138 | * if ( registry.isValidContract(identity) != 1 ) { 139 | * revert(); 140 | * _; 141 | * } 142 | * } 143 | */ 144 | 145 | // We recommend using checkIdentity(0x23424234242242) on your contracts. 146 | 147 | /***********************************************************************************************/ 148 | } 149 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributor License Agreement (CLA): 2 | 3 | In order to clarify the intellectual property license granted with Contributions from any person or entity, Deloitte MCS Ltd. ("Deloitte") must have a Contributor License Agreement (CLA) on file that has been signed by each Contributor, indicating agreement to the license terms below. This license is for your protection as a Contributor as well as the protection of Deloitte; it does not change your rights to use your own Contributions for any other purpose. 4 | 5 | This Agreement allows an entity (the "Individual" or "Corporation") to submit Contributions to Deloitte, to authorise Contributions submitted by any designated employees to Deloitte, and to grant copyright and patent licenses thereto. 6 | 7 | You accept and agree to the following terms and conditions for Your present and future Contributions submitted to Deloitte. Except for the license granted herein to Deloitte and recipients of software distributed by Deloitte, You reserve all right, title, and interest in and to Your Contributions. 8 | 9 | 1. Definitions. 10 | 11 | "You" (or "Your") shall mean the copyright owner or legal entity authorised by the copyright owner that is making this Agreement with Deloitte. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 12 | 13 | "Contribution" shall mean the code, documentation or any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to Deloitte for inclusion in, or documentation of, any of the products owned or managed by Deloitte (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to Deloitte or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, Deloitte for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." 14 | 15 | 2. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to Deloitte and to recipients of software distributed by Deloitte a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. 16 | 17 | 3. Grant of Patent License. Subject to the terms and conditions of this Agreement, You hereby grant to Deloitte and to recipients of software distributed by Deloitte a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. 18 | 19 | 4. You represent that You are legally entitled to grant the above license. You represent further that each employee of the Corporation designated by You is authorised to submit Contributions on behalf of the Corporation. 20 | 21 | 5. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). 22 | 23 | 6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. 24 | 25 | 7. Should You wish to submit work that is not Your original creation, You may submit it to Deloitte separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]". 26 | 27 | 8. It is your responsibility to notify Deloitte when any change is required to the list of designated employees authorised to submit Contributions on behalf of the Corporation, or to the Corporation's Point of Contact with Deloitte. -------------------------------------------------------------------------------- /test/functional/SmartIdentity/contracttest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The purpose of this test contract is to test the functions in SmartIdentity.sol 3 | * that involve altering the ownership of the contract. 4 | */ 5 | 6 | var SmartIdentity = artifacts.require("SmartIdentity"); 7 | 8 | contract('SmartIdentity', function(accounts) { 9 | 10 | var smartIdentity, 11 | owner, 12 | override, 13 | thirdparty; 14 | 15 | before("Setup the Smart Identity contract and hydrate the required variables", function() { 16 | owner = accounts[0]; 17 | override = owner; 18 | thirdparty = accounts[1]; 19 | 20 | SmartIdentity.new({from: owner}) 21 | .then(function(data) { 22 | smartIdentity = data; 23 | }); 24 | 25 | return smartIdentity, 26 | owner, 27 | thirdparty; 28 | }); 29 | 30 | describe("Contract tests", function() { 31 | 32 | /** 33 | * Since the getOwner function can only be executed by the override account, this 34 | * test implies that if the owner is successfully returned, the override account 35 | * has been correctly set. 36 | */ 37 | it("will have an owner with an override account that matches the creator of the contract", function() { 38 | return SmartIdentity.new({from: override}).then(function(identity) { 39 | return identity.getOwner.call() 40 | }).then(function(response) { 41 | assert.equal(response.valueOf(), owner, "owner does not match override"); 42 | }); 43 | }); 44 | 45 | it("will not allow a non-owner to execute the getOwner function", function() { 46 | return SmartIdentity.new({from: thirdparty}).then(function(identity) { 47 | return identity.getOwner.call(); 48 | }).then(function(error){ 49 | assert.isOk(error, "Expected error has not been thrown"); 50 | }).catch(function(error) { 51 | assert.isOk(error, "Expected error has been caught"); 52 | }); 53 | }); 54 | 55 | it("will attempt to setOwner as a thirdparty user and fail", function() { 56 | return smartIdentity.setOwner(thirdparty, {from: thirdparty}).then(function(error){ 57 | assert.isOk(error, "Expected error has not been thrown"); 58 | }).catch(function(error) { 59 | assert.isOk(error, "Expected error has not been caught"); 60 | }); 61 | }); 62 | 63 | it("will attempt to setOverride as a thirdparty user and fail", function() { 64 | return smartIdentity.setOverride(thirdparty, {from: thirdparty}).then(function(error){ 65 | assert.isOk(error, "Expected error has not been thrown"); 66 | }).catch(function(error) { 67 | assert.isOk(error, "Expected error has not been caught"); 68 | }); 69 | }); 70 | 71 | it("will set thirdparty as the new owner by an override user (override is the owner)", function() { 72 | return smartIdentity.setOwner(thirdparty, {from: override}).then(function(response) { 73 | var newOwnerStatus = response.logs[0].args.status; 74 | assert.equal(3, newOwnerStatus, "Transaction returned unexpected status"); 75 | assert.isOk(response, 'Error produced by setOwner function'); 76 | }); 77 | }); 78 | 79 | it("will verify that thirdparty is now the contract owner", function() { 80 | return smartIdentity.getOwner.call().then(function(response) { 81 | assert.equal(response.valueOf(), thirdparty, "thirdparty is not the owner"); 82 | }); 83 | }); 84 | 85 | it("will attempt to setOwner back to owner as the thirdparty user and fail as thirdparty is not override user", function() { 86 | return smartIdentity.setOwner(owner, {from: thirdparty}).then(function(error){ 87 | assert.isOk(error, "Expected error has not been thrown"); 88 | }).catch(function(error) { 89 | assert.isOk(error, "Expected error has not been caught"); 90 | }); 91 | }); 92 | 93 | it("will invoke the blocklock function when repeatedly setting a new owner as an override user", function() { 94 | return smartIdentity.setOwner(thirdparty, {from: override}).then(function(){ 95 | assert.isOk(error, "Expected error has not been thrown"); 96 | }).catch(function(error) { 97 | assert.isOk(error, "Expected error has not been caught"); 98 | }); 99 | }); 100 | 101 | it("will create a new contract then set override to a thirdparty user", function() { 102 | return SmartIdentity.new({from: owner}).then(function(identity) { 103 | return identity.setOverride(thirdparty, {from: override}) 104 | }).then(function(response) { 105 | var newOverrideStatus = response.logs[0].args.status; 106 | assert.equal(3, newOverrideStatus, "Transaction returned unexpected status"); 107 | assert.isOk(response, "Expected error has not been caught"); 108 | }); 109 | }); 110 | 111 | /** 112 | * Since 0.4.4, it may be preferable to change the contract to have a disabled 113 | * state, that also allows for the 'identity' to be migrated to a new address. 114 | */ 115 | it("will kill the contract as the owner", function() { 116 | return smartIdentity.kill({from: thirdparty}).then(function() { 117 | assert.equal(0, web3.eth.getBalance(smartIdentity.address).valueOf(), "Hmm, money still abounds."); 118 | }); 119 | }); 120 | }); 121 | }, 5000); 122 | -------------------------------------------------------------------------------- /test/functional/SmartIdentity/endorsementtest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The purpose of this test contract is to test the functions in SmartIdentity.sol 3 | * that use endorsements. 4 | */ 5 | 6 | var SmartIdentity = artifacts.require("SmartIdentity"); 7 | 8 | contract('SmartIdentity', function(accounts) { 9 | 10 | var smartIdentity, 11 | owner, 12 | endorser, 13 | thirdparty, 14 | attributeHash1, 15 | attributeHash2, 16 | attributeHash3, 17 | endorsementHash, 18 | endorsementHash2; 19 | 20 | before("Setup the Smart Identity contract and hydrate the required variables", function(done) { 21 | owner = accounts[0]; 22 | endorser = accounts[1]; 23 | thirdparty = accounts[2]; 24 | 25 | SmartIdentity.new({from: owner}) 26 | .then(function(response) { 27 | smartIdentity = response; 28 | done(); 29 | }); 30 | 31 | return smartIdentity, 32 | owner, 33 | endorser, 34 | thirdparty; 35 | }); 36 | 37 | attributeHash1 = '0xca02b2202ffaacbd499438ef6d594a48f7a7631b60405ec8f30a0d7c096d54d5'; 38 | attributeHash2 = '0xca02b2202ffaacbd499438ef6d594a48f7a7631b60405ec8f30a0d7c096dc3ff'; 39 | attributeHash3 = '0xca02b2202ffaacbd499438ef6d594a48f7a7631b60405ec8f30a0d7c096cn8sh'; 40 | endorsementHash = '0xca02b2202ffaacbd499438ef6d594a48f7a7631b60405ec8f30a0d7c096d54d5'; 41 | endorsementHash2 = '0xca02b2202ffaacbd499438ef6d594a48f7a7631b60405ec8f30a0d7c096d59ab'; 42 | 43 | describe("Endorsement tests", function() { 44 | 45 | it("will add an attribute for testing purposes", function() { 46 | return smartIdentity.addAttribute(attributeHash1, {from: owner}) 47 | .then(function(response) { 48 | assert.isOk(response, "Attribute addition failed"); 49 | var addAttributeStatus = response.logs[0].args.status; 50 | assert.equal(4, addAttributeStatus, "Transaction returned unexpected status"); 51 | }); 52 | }); 53 | 54 | it("will confirm that the owner's attribute was added successfully", function() { 55 | return smartIdentity.attributes.call(attributeHash1) 56 | .then(function(response) { 57 | assert.equal(response.valueOf(), attributeHash1, "Attribute hash doesn't exist"); 58 | }); 59 | }); 60 | 61 | it("will add an endorsement from an endorser", function() { 62 | return smartIdentity.addEndorsement(attributeHash1, endorsementHash, {from: endorser}) 63 | .then(function(response) { 64 | var addEndorsementStatus = response.logs[0].args.status; 65 | assert.equal(4, addEndorsementStatus, "Transaction returned unexpected status"); 66 | assert.isOk(response, "Endorsement addition failed"); 67 | }); 68 | }); 69 | 70 | it("will accept the endorsement as the owner", function() { 71 | return smartIdentity.acceptEndorsement(attributeHash1, endorsementHash, {from: owner}) 72 | .then(function(response) { 73 | var acceptEndorsementStatus = response.logs[0].args.status; 74 | assert.equal(3, acceptEndorsementStatus, "Transaction returned unexpected status"); 75 | assert.isOk(response, "Endorsement was not accepted"); 76 | }); 77 | }); 78 | 79 | it("will confirm that the endorsement has been added successfully", function() { 80 | return smartIdentity.checkEndorsementExists.call(attributeHash1, endorsementHash, {from: endorser}) 81 | .then(function(response) { 82 | assert.equal(response.valueOf(), true, "Endorsement doesn't exist"); 83 | }); 84 | }); 85 | 86 | it("will attempt to remove an endorsement as the owner (fail)", function() { 87 | return smartIdentity.removeEndorsement.call(attributeHash1, endorsementHash, {from: owner}) 88 | .catch(function(error) { 89 | assert.isOk(error, "Expected error has not been caught"); 90 | }); 91 | }); 92 | 93 | it("will attempt to remove an endorsement as a third party (fail)", function() { 94 | return smartIdentity.removeEndorsement.call(attributeHash1, endorsementHash, {from: thirdparty}) 95 | .catch(function(error) { 96 | assert.isOk(error, "Expected error has not been caught"); 97 | }); 98 | }); 99 | 100 | it("will confirm that the endorsement has not been removed by the third party", function() { 101 | return smartIdentity.checkEndorsementExists.call(attributeHash1, endorsementHash, {from: endorser}) 102 | .then(function(response) { 103 | assert.equal(response.valueOf(), true, "Endorsement doesn't exist"); 104 | }); 105 | }); 106 | 107 | it("will remove the endorsement as the endorser", function() { 108 | return smartIdentity.removeEndorsement(attributeHash1, endorsementHash, {from: endorser}) 109 | .then(function(response) { 110 | var removeEndorsementStatus = response.logs[0].args.status; 111 | assert.equal(3, removeEndorsementStatus, "Transaction returned unexpected status"); 112 | assert.isOk(response, "Endorsement was not accepted"); 113 | }); 114 | }); 115 | 116 | it("will confirm that the endorsement has been removed successfully", function() { 117 | return smartIdentity.checkEndorsementExists.call(attributeHash1, endorsementHash, {from: endorser}) 118 | .then(function(response) { 119 | assert.equal(response.valueOf(), false, "Endorsement still present despite removal"); 120 | }); 121 | }); 122 | 123 | it("will confirm that an endorsement is invalid if an attribute is removed", function() { 124 | return smartIdentity.addAttribute(attributeHash2, {from: owner}) 125 | .then(function(response) { 126 | assert.isOk(response, "Attribute was not added successfully"); 127 | return smartIdentity.addEndorsement(attributeHash2, endorsementHash, {from: endorser}) 128 | }).then(function(response) { 129 | assert.isOk(response, "Endorsement was not added successfully"); 130 | return smartIdentity.removeAttribute(attributeHash2, {from: owner}) 131 | }).then(function(response) { 132 | assert.isOk(response, "Attribute was not removed successfully"); 133 | return smartIdentity.checkEndorsementExists.call(attributeHash2, endorsementHash, {from: endorser}) 134 | }).then(function(response) { 135 | assert.equal(response, false, "Endorsement still exists"); 136 | }); 137 | }); 138 | 139 | it("will make sure that an endorser can delete an endorsement after an attribute has been removed", function() { 140 | return smartIdentity.addAttribute(web3.sha3("testhash"), {from: owner}) 141 | .then(function(response) { 142 | assert.isOk(response, "Attribute was not added successfully"); 143 | return smartIdentity.addEndorsement(web3.sha3("testhash"), web3.sha3("testendorsement"), {from: endorser}) 144 | }).then(function(response) { 145 | assert.isOk(response, "Endorsement was not added successfully"); 146 | return smartIdentity.removeAttribute(web3.sha3("testhash"), {from: owner}) 147 | }).then(function(response) { 148 | assert.isOk(response, "Attribute was not removed successfully"); 149 | return smartIdentity.removeEndorsement(web3.sha3("testhash"), web3.sha3("testendorsement"), {from: endorser}) 150 | }).then(function(response) { 151 | assert.isOk(response, "Endorsement not removed successfully"); 152 | }); 153 | }); 154 | 155 | it("will make sure that an endorser cannot add an endorsement to a non-existent attribute", function() { 156 | return smartIdentity.addEndorsement(web3.sha3("non-existent attribute"), web3.sha3("new endorsement"), {from: endorser}) 157 | .catch(function(error) { 158 | assert.isOk(error, "Endorsement was added for a non-existent attribute"); 159 | }); 160 | }); 161 | 162 | it("will add an endorsement from the endorser for testing purposes", function() { 163 | return smartIdentity.addEndorsement(attributeHash1, endorsementHash2, {from: endorser}) 164 | .then(function(response) { 165 | var addEndorsementStatus = response.logs[0].args.status; 166 | assert.equal(4, addEndorsementStatus, "Transaction returned unexpected status"); 167 | assert.isOk(response, "Endorsement was not added"); 168 | }); 169 | }); 170 | 171 | it("will remove the endorsement as the owner", function() { 172 | return smartIdentity.removeEndorsement(attributeHash1, endorsementHash2, {from: owner}) 173 | .then(function(response) { 174 | var removeEndorsementStatus = response.logs[0].args.status; 175 | assert.equal(3, removeEndorsementStatus, "Transaction returned unexpected status"); 176 | assert.isOk(response, 'Endorsement not removed'); 177 | }); 178 | }); 179 | 180 | }); 181 | }); 182 | -------------------------------------------------------------------------------- /test/functional/SmartIdentity/attributetest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The purpose of this test contract is to test the functions in SmartIdentity.sol 3 | * that use attributes. 4 | */ 5 | 6 | var SmartIdentity = artifacts.require("SmartIdentity"); 7 | 8 | contract('SmartIdentity', function(accounts) { 9 | 10 | var smartIdentity, 11 | owner, 12 | endorser, 13 | thirdparty, 14 | attributeHash1, 15 | attributeHash2, 16 | attributeHash3, 17 | attributeHash4; 18 | 19 | attributeHash1 = '0xca02b2202ffaacbd499438ef6d594a48f7a7631b60405ec8f30a0d7c096d54d5'; 20 | attributeHash2 = '0xca02b2202ffaacbd499438ef6d594a48f7a7631b60405ec8f30a0d7c096dc3ff'; 21 | attributeHash3 = '0xca02b2202ffaacbd499438ef6d594a48f7a7631b60405ec8f30a0d7c096d1A2B'; 22 | attributeHash4 = '0xbc614e0000000000000000000000000000000000000000000000000000000000'; 23 | 24 | before("Setup the Smart Identity contract and hydrate the required variables", function(done) { 25 | owner = accounts[0]; 26 | endorser = accounts[1]; 27 | thirdparty = accounts[2]; 28 | 29 | SmartIdentity.new({from: owner}) 30 | .then(function(data) { 31 | smartIdentity = data; 32 | done(); 33 | }); 34 | 35 | return smartIdentity, 36 | owner, 37 | endorser, 38 | thirdparty; 39 | }); 40 | 41 | describe("Attribute tests", function() { 42 | 43 | it("will add an attribute as the owner, and succeed", function() { 44 | return smartIdentity.addAttribute(attributeHash1, {from: owner}) 45 | .then(function(response) { 46 | assert.isOk(response, "Attribute addition failed"); 47 | var addAttributeStatus = response.logs[0].args.status; 48 | assert.equal(4, addAttributeStatus, "Transaction returned unexpected status"); 49 | }); 50 | }); 51 | 52 | it("will confirm that the owner's attribute has been added successfully", function() { 53 | return smartIdentity.attributes.call(attributeHash1) 54 | .then(function(response) { 55 | assert.equal(response.valueOf(), attributeHash1, "Attribute hash does not exist"); 56 | }); 57 | }); 58 | 59 | it("will attempt to add an attribute that already exists, and fail", function() { 60 | return smartIdentity.addAttribute(attributeHash1, {from: owner}) 61 | .catch(function(error) { 62 | assert.isOk(error, "Expected error has not been caught"); 63 | }); 64 | }); 65 | 66 | it("will attempt to add an attribute as a third party, and fail", function() { 67 | return smartIdentity.addAttribute(attributeHash2, {from: thirdparty}) 68 | .catch(function(error) { 69 | assert.isOk(error, "Expected error has not been caught"); 70 | }); 71 | }); 72 | 73 | it("will confirm that the third party's attribute has not been added", function() { 74 | return smartIdentity.attributes.call(attributeHash2) 75 | .then(function(response) { 76 | assert.equal(response.valueOf(), false, "Attribute has been added"); 77 | }); 78 | }); 79 | 80 | it("will attempt to add an attribute as an endorser, and fail", function() { 81 | return smartIdentity.addAttribute(attributeHash3, {from: endorser}) 82 | .catch(function(error) { 83 | assert.isOk(error, "Expected error has not been caught"); 84 | }); 85 | }); 86 | 87 | it("will confirm that the endorser's attribute has not been added", function() { 88 | return smartIdentity.attributes.call(attributeHash3) 89 | .then(function(response) { 90 | assert.equal(response.valueOf(), false, "Attribute has been added"); 91 | }); 92 | }); 93 | 94 | it("will attempt to remove an attribute that doesn't exist, and fail", function() { 95 | return smartIdentity.removeAttribute(attributeHash2, {from: owner}) 96 | .catch(function(error) { 97 | assert.isOk(error, "Expected error has not been caught"); 98 | }); 99 | }); 100 | 101 | it("will attempt to update an existing attribute as an endorser, and fail", function() { 102 | var oldAttributeHash = attributeHash1; 103 | var newAttributeHash = attributeHash3; 104 | return smartIdentity.updateAttribute(oldAttributeHash, newAttributeHash, {from: endorser}) 105 | .catch(function(error) { 106 | assert.isOk(error, "Expected error has not been caught"); 107 | }); 108 | }); 109 | 110 | it("will confirm that the owner's attribute has not been changed by the endorser", function() { 111 | return smartIdentity.attributes.call(attributeHash1) 112 | .then(function(response) { 113 | assert.equal(response.valueOf(), attributeHash1, "Attribute has been changed"); 114 | }); 115 | }); 116 | 117 | it("will attempt to update an existing attribute as a third party, and fail", function() { 118 | var oldAttributeHash = attributeHash1; 119 | var newAttributeHash = attributeHash3; 120 | return smartIdentity.updateAttribute(oldAttributeHash, newAttributeHash, {from: thirdparty}) 121 | .catch(function(error) { 122 | assert.isOk(error, "Expected error has not been caught"); 123 | }); 124 | }); 125 | 126 | it("will confirm that the owner's attribute has not been changed by the third party", function() { 127 | return smartIdentity.attributes.call(attributeHash1) 128 | .then(function(response) { 129 | assert.equal(response.valueOf(), attributeHash1, "Attribute has been changed"); 130 | }); 131 | }); 132 | 133 | it("will attempt to remove an attribute as a third party, and fail", function() { 134 | return smartIdentity.removeAttribute(attributeHash1, {from: thirdparty}) 135 | .catch(function(error) { 136 | assert.isOk(error, "Expected error has not been caught"); 137 | }); 138 | }); 139 | 140 | it("will confirm that the owner's attribute was not removed by the third party", function() { 141 | return smartIdentity.attributes.call(attributeHash1) 142 | .then(function(response) { 143 | assert.equal(response.valueOf(), attributeHash1, "Attribute hash does not exist"); 144 | }); 145 | }); 146 | 147 | it("will attempt to remove an attribute as an endorser, and fail", function() { 148 | return smartIdentity.removeAttribute(attributeHash1, {from: endorser}) 149 | .catch(function(error) { 150 | assert.isOk(error, "Expected error has not been caught"); 151 | }); 152 | }); 153 | 154 | it("will confirm that the owner's attribute was not removed by the endorser", function() { 155 | return smartIdentity.attributes.call(attributeHash1) 156 | .then(function(response) { 157 | assert.equal(response.valueOf(), attributeHash1, "Attribute hash does not exist"); 158 | }); 159 | }); 160 | 161 | it("will remove an attribute as an owner, and succeed", function() { 162 | return smartIdentity.removeAttribute(attributeHash1, {from: owner}) 163 | .then(function(response) { 164 | var removeAttributeStatus = response.logs[0].args.status; 165 | assert.equal(3, removeAttributeStatus, "Transaction returned unexpected status"); 166 | assert.isOk(response, "Attribute removal failed"); 167 | }); 168 | }); 169 | 170 | it("will confirm that the attribute has been removed by the owner", function() { 171 | return smartIdentity.attributes.call(attributeHash1) 172 | .then(function(response) { 173 | assert.equal(response.valueOf(), false, "Attribute was not removed"); 174 | }); 175 | }); 176 | 177 | it("will add a new attribute as the owner, and succeed", function() { 178 | return smartIdentity.addAttribute(attributeHash4, {from: owner}) 179 | .then(function(response) { 180 | var addAttributeStatus = response.logs[0].args.status; 181 | assert.equal(4, addAttributeStatus, "Transaction returned unexpected status"); 182 | assert.isOk(response, "Attribute addition failed"); 183 | }); 184 | }); 185 | 186 | it("will confirm that the owner's attribute has been added successfully", function() { 187 | return smartIdentity.attributes.call(attributeHash4) 188 | .then(function(response) { 189 | assert.equal(response.valueOf(), attributeHash4, "Attribute hash does not exist"); 190 | }); 191 | }); 192 | 193 | it("will update an existing attribute as the owner, and succeed", function() { 194 | var oldAttributeHash = attributeHash4; 195 | var newAttributeHash = "0x154f7ce000000000000000000000000000000000000000000000000000000000"; 196 | return smartIdentity.updateAttribute(oldAttributeHash, newAttributeHash, {from: owner}) 197 | .then(function(response) { 198 | var debugStatus = response.logs[0].args.status; 199 | var removeAttributeStatus = response.logs[1].args.status; 200 | var addAttributeStatus = response.logs[2].args.status; 201 | var updateAttributeStatus = response.logs[3].args.status; 202 | assert.equal(5, debugStatus, "Transaction returned unexpected status"); 203 | assert.equal(3, removeAttributeStatus, "Transaction returned unexpected status"); 204 | assert.equal(4, addAttributeStatus, "Transaction returned unexpected status"); 205 | assert.equal(3, updateAttributeStatus, "Transaction returned unexpected status"); 206 | assert.isOk(response, "Attribute update failed"); 207 | }); 208 | }); 209 | 210 | it("will confirm that the old attribute has been removed by the owner", function() { 211 | var oldAttributeHash = attributeHash4; 212 | return smartIdentity.attributes.call(oldAttributeHash) 213 | .then(function(response) { 214 | assert.equal(response.valueOf(), false, "Attribute was not removed"); 215 | }); 216 | }); 217 | 218 | it("will confirm that the owner's new attribute has been added successfully", function() { 219 | var newAttributeHash = "0x154f7ce000000000000000000000000000000000000000000000000000000000"; 220 | return smartIdentity.attributes.call(newAttributeHash) 221 | .then(function(response) { 222 | assert.equal(response.valueOf(), newAttributeHash, "Attribute hash does not exist"); 223 | }); 224 | }); 225 | }); 226 | }, 6000); 227 | -------------------------------------------------------------------------------- /contracts/SmartIdentity.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.0; 2 | 3 | /** 4 | * The contract is a Smart ID, and all derived 'Smart IDs' should match the bytecode 5 | * of a known 'good SmartIdentity' version. See the 'SmartIdentityRegistry' contract 6 | * for a mechanism for verifying valid SmartIdentities. 7 | */ 8 | 9 | contract SmartIdentity { 10 | 11 | address private owner; 12 | address private override; 13 | uint private blocklock; 14 | string public encryptionPublicKey; 15 | string public signingPublicKey; 16 | 17 | uint constant BLOCK_HEIGHT = 20; 18 | uint constant ERROR_EVENT = 1; 19 | uint constant WARNING_EVENT = 2; 20 | uint constant SIG_CHANGE_EVENT = 3; 21 | uint constant INFO_EVENT = 4; 22 | uint constant DEBUG_EVENT = 5; 23 | 24 | mapping(bytes32 => Attribute) public attributes; 25 | 26 | /** 27 | * Constructor of the Smart Identity 28 | */ 29 | function SmartIdentity() { 30 | owner = msg.sender; 31 | override = owner; 32 | blocklock = block.number - BLOCK_HEIGHT; 33 | } 34 | 35 | /** 36 | * Modifier to place a constraint on the user calling a function 37 | */ 38 | modifier onlyBy(address _account) { 39 | if (msg.sender != _account) { 40 | revert(); 41 | } 42 | _; 43 | } 44 | 45 | /** 46 | * Modifier to prevent change if the block height is too low. 47 | * This has been set at 20 for testing purposes, and should 48 | * be made longer to better protect the contract from significant 49 | * ownership changes. 50 | */ 51 | modifier checkBlockLock() { 52 | if (blocklock + BLOCK_HEIGHT > block.number) { 53 | revert(); 54 | } 55 | _; 56 | } 57 | 58 | /** 59 | * Modifier to set the blocklock. 60 | */ 61 | modifier blockLock() { 62 | blocklock = block.number; 63 | _; 64 | } 65 | 66 | /** 67 | * The attribute structure: every attribute is composed of: 68 | * - Attribute hash 69 | * - Endorsements 70 | */ 71 | struct Attribute { 72 | bytes32 hash; 73 | mapping(bytes32 => Endorsement) endorsements; 74 | } 75 | 76 | /** 77 | * The endorsement structure: every endorsement is composed of: 78 | * - Endorser address 79 | * - Endorsement hash 80 | * - Accepted Status - true if the user has accepted the endorsement 81 | */ 82 | struct Endorsement { 83 | address endorser; 84 | bytes32 hash; 85 | bool accepted; 86 | } 87 | 88 | /** 89 | * This event is used for standard change notification messages and outputs the following: 90 | * - owner of the contract 91 | * - event status level 92 | * - event message body 93 | */ 94 | event ChangeNotification(address indexed sender, uint status, bytes32 notificationMsg); 95 | 96 | /** 97 | * This function is used to send events. 98 | * Status Level Scale: 99 | * 1 Error: error conditions 100 | * 2 Warning: warning conditions 101 | * 3 Significant Change: Significant change to condition 102 | * 4 Informational: informational messages 103 | * 5 Verbose: debug-level messages 104 | */ 105 | function sendEvent(uint _status, bytes32 _notification) internal returns(bool) { 106 | ChangeNotification(owner, _status, _notification); 107 | return true; 108 | } 109 | 110 | /** 111 | * This function gives the override address the ability to change owner. 112 | * This could allow the identity to be moved to a multi-sig contract. 113 | * See https://github.com/ethereum/dapp-bin/blob/master/wallet/wallet.sol 114 | * for a multi-sig wallet example. 115 | */ 116 | function setOwner(address _newowner) onlyBy(override) checkBlockLock() blockLock() returns(bool) { 117 | owner = _newowner; 118 | sendEvent(SIG_CHANGE_EVENT, "Owner has been changed"); 119 | return true; 120 | } 121 | 122 | /** 123 | * Cosmetic function for the override account holder to check that their 124 | * permissions on the contract have been set correctly. 125 | */ 126 | function getOwner() onlyBy(override) returns(address) { 127 | return owner; 128 | } 129 | 130 | /** 131 | * The override address is another ethereum address that can reset the owner. 132 | * In practice this could either be another multi-sig account, or another 133 | * smart contract that this control could be delegated to. 134 | */ 135 | function setOverride(address _override) onlyBy(owner) checkBlockLock() blockLock() returns(bool) { 136 | override = _override; 137 | sendEvent(SIG_CHANGE_EVENT, "Override has been changed"); 138 | return true; 139 | } 140 | 141 | /** 142 | * This function removes the override by the owner - if trust between the identity 143 | * holder and the new account ends. 144 | */ 145 | function removeOverride() onlyBy(owner) checkBlockLock() blockLock() returns(bool) { 146 | override = owner; 147 | sendEvent(SIG_CHANGE_EVENT, "Override has been removed"); 148 | return true; 149 | } 150 | 151 | /** 152 | * Adds an attribute, with an empty list of endorsements. 153 | */ 154 | function addAttribute(bytes32 _hash) onlyBy(owner) checkBlockLock() returns(bool) { 155 | var attribute = attributes[_hash]; 156 | if (attribute.hash == _hash) { 157 | sendEvent(SIG_CHANGE_EVENT, "A hash exists for the attribute"); 158 | revert(); 159 | } 160 | attribute.hash = _hash; 161 | sendEvent(INFO_EVENT, "Attribute has been added"); 162 | return true; 163 | } 164 | 165 | /** 166 | * This updates an attribute by removing the old one first, and then 167 | * adding the new one. The event log should hold the record of the 168 | * transaction so at a future date it should be possible to traverse 169 | * the history of an attribute against the blockchain. 170 | */ 171 | function updateAttribute(bytes32 _oldhash, bytes32 _newhash) onlyBy(owner) checkBlockLock() returns(bool) { 172 | sendEvent(DEBUG_EVENT, "Attempting to update attribute"); 173 | removeAttribute(_oldhash); 174 | addAttribute(_newhash); 175 | sendEvent(SIG_CHANGE_EVENT, "Attribute has been updated"); 176 | return true; 177 | } 178 | 179 | /** 180 | * Removes an attribute from a contract. 181 | */ 182 | function removeAttribute(bytes32 _hash) onlyBy(owner) checkBlockLock() returns(bool) { 183 | var attribute = attributes[_hash]; 184 | if (attribute.hash != _hash) { 185 | sendEvent(WARNING_EVENT, "Hash not found for attribute"); 186 | revert(); 187 | } 188 | delete attributes[_hash]; 189 | sendEvent(SIG_CHANGE_EVENT, "Attribute has been removed"); 190 | return true; 191 | } 192 | 193 | /** 194 | * Adds an endorsement to an attribute; must provide a valid attributeHash. 195 | * See the docs for off-chain transfer of the encrypted endorsement information. 196 | */ 197 | function addEndorsement(bytes32 _attributeHash, bytes32 _endorsementHash) returns(bool) { 198 | var attribute = attributes[_attributeHash]; 199 | if (attribute.hash != _attributeHash) { 200 | sendEvent(ERROR_EVENT, "Attribute doesn't exist"); 201 | revert(); 202 | } 203 | var endorsement = attribute.endorsements[_endorsementHash]; 204 | if (endorsement.hash == _endorsementHash) { 205 | sendEvent(ERROR_EVENT, "Endorsement already exists"); 206 | revert(); 207 | } 208 | endorsement.hash = _endorsementHash; 209 | endorsement.endorser = msg.sender; 210 | endorsement.accepted = false; 211 | sendEvent(INFO_EVENT, "Endorsement has been added"); 212 | return true; 213 | } 214 | 215 | /** 216 | * Owner can mark an endorsement as accepted. 217 | */ 218 | function acceptEndorsement(bytes32 _attributeHash, bytes32 _endorsementHash) onlyBy(owner) returns(bool) { 219 | var attribute = attributes[_attributeHash]; 220 | var endorsement = attribute.endorsements[_endorsementHash]; 221 | endorsement.accepted = true; 222 | sendEvent(SIG_CHANGE_EVENT, "Endorsement has been accepted"); 223 | } 224 | 225 | /** 226 | * Checks that an endorsement _endorsementHash exists for the attribute _attributeHash. 227 | */ 228 | function checkEndorsementExists(bytes32 _attributeHash, bytes32 _endorsementHash) returns(bool) { 229 | var attribute = attributes[_attributeHash]; 230 | if (attribute.hash != _attributeHash) { 231 | sendEvent(ERROR_EVENT, "Attribute doesn't exist"); 232 | return false; 233 | } 234 | var endorsement = attribute.endorsements[_endorsementHash]; 235 | if (endorsement.hash != _endorsementHash) { 236 | sendEvent(ERROR_EVENT, "Endorsement doesn't exist"); 237 | return false; 238 | } 239 | if (endorsement.accepted == true) { 240 | sendEvent(INFO_EVENT, "Endorsement exists for attribute"); 241 | return true; 242 | } else { 243 | sendEvent(ERROR_EVENT, "Endorsement hasn't been accepted"); 244 | return false; 245 | } 246 | } 247 | 248 | /** 249 | * Allows only the person who gave the endorsement the ability to remove it. 250 | */ 251 | function removeEndorsement(bytes32 _attributeHash, bytes32 _endorsementHash) returns(bool) { 252 | var attribute = attributes[_attributeHash]; 253 | var endorsement = attribute.endorsements[_endorsementHash]; 254 | if (msg.sender == endorsement.endorser) { 255 | delete attribute.endorsements[_endorsementHash]; 256 | sendEvent(SIG_CHANGE_EVENT, "Endorsement removed"); 257 | return true; 258 | } 259 | if (msg.sender == owner && endorsement.accepted == false) { 260 | delete attribute.endorsements[_endorsementHash]; 261 | sendEvent(SIG_CHANGE_EVENT, "Endorsement denied"); 262 | return true; 263 | } 264 | sendEvent(SIG_CHANGE_EVENT, "Endorsement removal failed"); 265 | revert(); 266 | } 267 | 268 | /** 269 | * Allows only the account owner to create or update encryptionPublicKey. 270 | * Only 1 encryptionPublicKey is allowed per account, therefore use same set 271 | * method for both create and update. 272 | */ 273 | function setEncryptionPublicKey(string _myEncryptionPublicKey) onlyBy(owner) checkBlockLock() returns(bool) { 274 | encryptionPublicKey = _myEncryptionPublicKey; 275 | sendEvent(SIG_CHANGE_EVENT, "Encryption key added"); 276 | return true; 277 | } 278 | 279 | /** 280 | * Allows only the account owner to create or update signingPublicKey. 281 | * Only 1 signingPublicKey allowed per account, therefore use same set method 282 | * for both create and update. 283 | */ 284 | function setSigningPublicKey(string _mySigningPublicKey) onlyBy(owner) checkBlockLock() returns(bool) { 285 | signingPublicKey = _mySigningPublicKey; 286 | sendEvent(SIG_CHANGE_EVENT, "Signing key added"); 287 | return true; 288 | } 289 | 290 | /** 291 | * Kills the contract and prevents further actions on it. 292 | */ 293 | function kill() onlyBy(owner) returns(uint) { 294 | suicide(owner); 295 | sendEvent(WARNING_EVENT, "Contract killed"); 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /LICENCE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2016 Deloitte MCS Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | 16 | 17 | Apache License 18 | Version 2.0, January 2004 19 | http://www.apache.org/licenses/ 20 | 21 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 22 | 23 | 1. Definitions. 24 | 25 | "License" shall mean the terms and conditions for use, reproduction, 26 | and distribution as defined by Sections 1 through 9 of this document. 27 | 28 | "Licensor" shall mean the copyright owner or entity authorized by 29 | the copyright owner that is granting the License. 30 | 31 | "Legal Entity" shall mean the union of the acting entity and all 32 | other entities that control, are controlled by, or are under common 33 | control with that entity. For the purposes of this definition, 34 | "control" means (i) the power, direct or indirect, to cause the 35 | direction or management of such entity, whether by contract or 36 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 37 | outstanding shares, or (iii) beneficial ownership of such entity. 38 | 39 | "You" (or "Your") shall mean an individual or Legal Entity 40 | exercising permissions granted by this License. 41 | 42 | "Source" form shall mean the preferred form for making modifications, 43 | including but not limited to software source code, documentation 44 | source, and configuration files. 45 | 46 | "Object" form shall mean any form resulting from mechanical 47 | transformation or translation of a Source form, including but 48 | not limited to compiled object code, generated documentation, 49 | and conversions to other media types. 50 | 51 | "Work" shall mean the work of authorship, whether in Source or 52 | Object form, made available under the License, as indicated by a 53 | copyright notice that is included in or attached to the work 54 | (an example is provided in the Appendix below). 55 | 56 | "Derivative Works" shall mean any work, whether in Source or Object 57 | form, that is based on (or derived from) the Work and for which the 58 | editorial revisions, annotations, elaborations, or other modifications 59 | represent, as a whole, an original work of authorship. For the purposes 60 | of this License, Derivative Works shall not include works that remain 61 | separable from, or merely link (or bind by name) to the interfaces of, 62 | the Work and Derivative Works thereof. 63 | 64 | "Contribution" shall mean any work of authorship, including 65 | the original version of the Work and any modifications or additions 66 | to that Work or Derivative Works thereof, that is intentionally 67 | submitted to Licensor for inclusion in the Work by the copyright owner 68 | or by an individual or Legal Entity authorized to submit on behalf of 69 | the copyright owner. For the purposes of this definition, "submitted" 70 | means any form of electronic, verbal, or written communication sent 71 | to the Licensor or its representatives, including but not limited to 72 | communication on electronic mailing lists, source code control systems, 73 | and issue tracking systems that are managed by, or on behalf of, the 74 | Licensor for the purpose of discussing and improving the Work, but 75 | excluding communication that is conspicuously marked or otherwise 76 | designated in writing by the copyright owner as "Not a Contribution." 77 | 78 | "Contributor" shall mean Licensor and any individual or Legal Entity 79 | on behalf of whom a Contribution has been received by Licensor and 80 | subsequently incorporated within the Work. 81 | 82 | 2. Grant of Copyright License. Subject to the terms and conditions of 83 | this License, each Contributor hereby grants to You a perpetual, 84 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 85 | copyright license to reproduce, prepare Derivative Works of, 86 | publicly display, publicly perform, sublicense, and distribute the 87 | Work and such Derivative Works in Source or Object form. 88 | 89 | 3. Grant of Patent License. Subject to the terms and conditions of 90 | this License, each Contributor hereby grants to You a perpetual, 91 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 92 | (except as stated in this section) patent license to make, have made, 93 | use, offer to sell, sell, import, and otherwise transfer the Work, 94 | where such license applies only to those patent claims licensable 95 | by such Contributor that are necessarily infringed by their 96 | Contribution(s) alone or by combination of their Contribution(s) 97 | with the Work to which such Contribution(s) was submitted. If You 98 | institute patent litigation against any entity (including a 99 | cross-claim or counterclaim in a lawsuit) alleging that the Work 100 | or a Contribution incorporated within the Work constitutes direct 101 | or contributory patent infringement, then any patent licenses 102 | granted to You under this License for that Work shall terminate 103 | as of the date such litigation is filed. 104 | 105 | 4. Redistribution. You may reproduce and distribute copies of the 106 | Work or Derivative Works thereof in any medium, with or without 107 | modifications, and in Source or Object form, provided that You 108 | meet the following conditions: 109 | 110 | (a) You must give any other recipients of the Work or 111 | Derivative Works a copy of this License; and 112 | 113 | (b) You must cause any modified files to carry prominent notices 114 | stating that You changed the files; and 115 | 116 | (c) You must retain, in the Source form of any Derivative Works 117 | that You distribute, all copyright, patent, trademark, and 118 | attribution notices from the Source form of the Work, 119 | excluding those notices that do not pertain to any part of 120 | the Derivative Works; and 121 | 122 | (d) If the Work includes a "NOTICE" text file as part of its 123 | distribution, then any Derivative Works that You distribute must 124 | include a readable copy of the attribution notices contained 125 | within such NOTICE file, excluding those notices that do not 126 | pertain to any part of the Derivative Works, in at least one 127 | of the following places: within a NOTICE text file distributed 128 | as part of the Derivative Works; within the Source form or 129 | documentation, if provided along with the Derivative Works; or, 130 | within a display generated by the Derivative Works, if and 131 | wherever such third-party notices normally appear. The contents 132 | of the NOTICE file are for informational purposes only and 133 | do not modify the License. You may add Your own attribution 134 | notices within Derivative Works that You distribute, alongside 135 | or as an addendum to the NOTICE text from the Work, provided 136 | that such additional attribution notices cannot be construed 137 | as modifying the License. 138 | 139 | You may add Your own copyright statement to Your modifications and 140 | may provide additional or different license terms and conditions 141 | for use, reproduction, or distribution of Your modifications, or 142 | for any such Derivative Works as a whole, provided Your use, 143 | reproduction, and distribution of the Work otherwise complies with 144 | the conditions stated in this License. 145 | 146 | 5. Submission of Contributions. Unless You explicitly state otherwise, 147 | any Contribution intentionally submitted for inclusion in the Work 148 | by You to the Licensor shall be under the terms and conditions of 149 | this License, without any additional terms or conditions. 150 | Notwithstanding the above, nothing herein shall supersede or modify 151 | the terms of any separate license agreement you may have executed 152 | with Licensor regarding such Contributions. 153 | 154 | 6. Trademarks. This License does not grant permission to use the trade 155 | names, trademarks, service marks, or product names of the Licensor, 156 | except as required for reasonable and customary use in describing the 157 | origin of the Work and reproducing the content of the NOTICE file. 158 | 159 | 7. Disclaimer of Warranty. Unless required by applicable law or 160 | agreed to in writing, Licensor provides the Work (and each 161 | Contributor provides its Contributions) on an "AS IS" BASIS, 162 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 163 | implied, including, without limitation, any warranties or conditions 164 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 165 | PARTICULAR PURPOSE. You are solely responsible for determining the 166 | appropriateness of using or redistributing the Work and assume any 167 | risks associated with Your exercise of permissions under this License. 168 | 169 | 8. Limitation of Liability. In no event and under no legal theory, 170 | whether in tort (including negligence), contract, or otherwise, 171 | unless required by applicable law (such as deliberate and grossly 172 | negligent acts) or agreed to in writing, shall any Contributor be 173 | liable to You for damages, including any direct, indirect, special, 174 | incidental, or consequential damages of any character arising as a 175 | result of this License or out of the use or inability to use the 176 | Work (including but not limited to damages for loss of goodwill, 177 | work stoppage, computer failure or malfunction, or any and all 178 | other commercial damages or losses), even if such Contributor 179 | has been advised of the possibility of such damages. 180 | 181 | 9. Accepting Warranty or Additional Liability. While redistributing 182 | the Work or Derivative Works thereof, You may choose to offer, 183 | and charge a fee for, acceptance of support, warranty, indemnity, 184 | or other liability obligations and/or rights consistent with this 185 | License. However, in accepting such obligations, You may act only 186 | on Your own behalf and on Your sole responsibility, not on behalf 187 | of any other Contributor, and only if You agree to indemnify, 188 | defend, and hold each Contributor harmless for any liability 189 | incurred by, or claims asserted against, such Contributor by reason 190 | of your accepting any such warranty or additional liability. 191 | 192 | END OF TERMS AND CONDITIONS 193 | 194 | APPENDIX: How to apply the Apache License to your work. 195 | 196 | To apply the Apache License to your work, attach the following 197 | boilerplate notice, with the fields enclosed by brackets "{}" 198 | replaced with your own identifying information. (Don't include 199 | the brackets!) The text should be enclosed in the appropriate 200 | comment syntax for the file format. We also recommend that a 201 | file or class name and description of purpose be included on the 202 | same "printed page" as the copyright notice for easier 203 | identification within third-party archives. 204 | 205 | Copyright {yyyy} {name of copyright owner} 206 | 207 | Licensed under the Apache License, Version 2.0 (the "License"); 208 | you may not use this file except in compliance with the License. 209 | You may obtain a copy of the License at 210 | 211 | http://www.apache.org/licenses/LICENSE-2.0 212 | 213 | Unless required by applicable law or agreed to in writing, software 214 | distributed under the License is distributed on an "AS IS" BASIS, 215 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 216 | See the License for the specific language governing permissions and 217 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Smart Identity 2 | 3 | [![Build Status](https://travis-ci.org/SmartIdentity/smartId-contracts.svg?branch=develop)](https://travis-ci.org/SmartIdentity/smartId-contracts) 4 | 5 | ## Table of Contents 6 | 7 | - [What is Smart Identity?](#what-is-smart-identity) 8 | - [Getting Started](#getting-started) 9 | - [Community / Forum / Contact](#community--forum--contact) 10 | - [Our Vision](#the-vision) 11 | - [Overview](#overview) 12 | - [Licensing](#licensing) 13 | - [Contributions](#contributions) 14 | - [How to contribute](#how-to-contribute) 15 | - [Use Cases](#use-cases) 16 | - [Disclaimer](#disclaimer) 17 | 18 | ## What is Smart Identity? 19 | 20 | Today, Smart Identity uses the Ethereum blockchain to represent an identity using a smart contract, attributes can be added by the identity owner and are stored in hash form. Attributes can be endorsed by any user, this is done by storing a corresponding endorsement hash against the attribute hash. Endorsements are revocable and are considered current if not revoked. 21 | 22 | To verify underlying attribute and endorsement data, corresponding hashes must be computed, and their presence verified within the corresponding identity contract. 23 | 24 | Attributes and endorsements are formed of field sets, merkle-root hashes are used to allow sharing and verification of partial data (such as date of birth within a driving license). 25 | 26 | Smart ID is a platform that uses Ethereum and solidity smart contracts as a framework for its core protocol. A spring boot java application has been implemented to showcase how the protocol works and acts as an interface to the blockchain. Initial smart contracts written have been written in Solidity to the Solidity 0.4 specification. [http://solidity.readthedocs.io/en/develop/](http://solidity.readthedocs.io/en/develop/). 27 | 28 | ## Prerequisites 29 | * [Node.js 6.9](https://nodejs.org) 30 | * [Python 2.7](https://www.python.org/download/releases/2.7/) 31 | * Command Line Tools 32 | * **Mac OS X**: [Xcode](https://itunes.apple.com/us/app/xcode/id497799835?mt=12) (or **OS X 10.9+**: `xcode-select --install`) and `brew install libgcrypt` 33 | * **Ubuntu / Linux**: `sudo apt-get install build-essential python-software-properties libssl-dev` 34 | 35 | ## Getting Started 36 | 37 | ### Setting up the dev environment 38 | 39 | ```bash 40 | # Get the latest snapshot 41 | #https 42 | git clone https://github.com/SmartIdentity/smartId-contracts.git 43 | #ssh 44 | git clone git@github.com:SmartIdentity/smartId-contracts.git 45 | 46 | # Change directory 47 | cd smartId-contracts/ 48 | 49 | # Install NPM dependencies 50 | npm install 51 | 52 | ``` 53 | 54 | In a separate terminal run: 55 | 56 | ``` 57 | ./node_modules/.bin/testrpc 58 | ``` 59 | to spin up a test blockchain (it doesn't mine, just runs and parses the commands sent to it). 60 | 61 | It listens on the default RPC interface of localhost:8085, so you could also run against geth, or any other web3 enabled RPC. 62 | 63 | Testrpc is designed to provide fast feedback, without rinsing your processor. 64 | 65 | You can then execute the following functions: 66 | 67 | * `truffle compile` - checks that the contracts compile and there are no errors. Run this to setup the contract before `truffle console`. 68 | * `truffle console` - spins up an interactive shell to interrogate the blockchain 69 | * `truffle test` - runs the unit tests in against testrpc 70 | 71 | 72 | ## The Vision 73 | 74 | The vision for Smart Identity is to enable a universal platform for identity representation and verification, enabling people, organisations, IoT devices, Dapps, or anything else, to obtain, use and verify identity information with minimal reliance on centrally provided systems or services. 75 | 76 | Such an outcome offers considerable social and economic advantage; in developing economies the ability to obtain credible identity offers value outright, whilst in developed economies the vision addresses higher-order efficiency and user experience imperatives by standardising the technical framework within which any identity artefact can be represented, shared and verified. 77 | 78 | Beyond traditional identity data, a Smart ID is intended to serve as a container (wallet) for digital assets owned by an identity, for contracts an identity is party to, and as a controller to identity linked Dapps. 79 | 80 | To offer global utility, Smart Identity should: 81 | 82 | * Operate over resilient, widely accessible decentralised infrastructure, reducing risks of failure, compromise or imposed cost associated with centrally provided platforms 83 | * Provide a high degree of default user privacy and security 84 | * Operate at very low cost 85 | 86 | ## Community / Forum / Contact 87 | 88 | * [Solidity Gitter Channel](https://gitter.im/ethereum/solidity) for tech issues/discussion around solidity. 89 | * [LinkedIn Group](https://www.linkedin.com/groups/8585249) for professional chat on use cases and/or anything else. 90 | * [Deloitte Smart ID website](http://www.deloitte.co.uk/smartid) for Deloitte announcements relating to Smart ID 91 | * Or contact a member of the Deloitte Smart ID team via email: blockchain@deloitte.co.uk 92 | 93 | ## Overview 94 | 95 | ### The Smart Identity structure: Smart Contracts, Attributes and Endorsements 96 | 97 | The Smart Identity construct uses an attribute-endorsement model enabled by the use of smart contracts which provide rudimentary role based permissions. An identity-owner can attest that an 'attribute' is a correct representation of a part of their identity (by storing a corresponding hash value within their identity), following which third parties are able to attest to the validity of each attribute (by storing a corresponding hash value against the attribute within the identity) 98 | 99 | #### Smart Contracts 100 | 101 | ##### Smart IDentity.sol 102 | 103 | This is the Smart Identity contract as used by the Smart Identity instance. It describes the core functionality required as part of a Smart Identity contracts with encryption keys, attributes & endorsements. 104 | 105 | A Smart ID is an [Ethereum](https://www.ethereum.org/) Smart Contract address. The smart contract must be constructed using valid Smart ID bytecode. It provides access to identity management commands and stores hash representations of identity data. 106 | 107 | The Smart Contract has a constructor that defines the owner and core elements of the identity: 108 | 109 | * Contract address - a 32byte hash of the address at which the contract is deployed. 110 | * Encryption key - a changeable encryption (public) key that allows other actors to send data for encrypted receipt and decryption by this identity. This can be changed at any point. 111 | * Signing key - a changeable encryption (public) key that allows other actors to verify Endorsements signed by this identity. 112 | * Attribute mapping - A mapping that stores the Attributes (and associated Endorsements) related to the contract/identity. 113 | * It also implements a kill function so that an identity can be retired (though the record of it's 'active' period is of course retained in the blockchain). 114 | 115 | #### Smart IDentityRegistry.sol 116 | 117 | This contract holds a curated list of valid contracts that Deloitte (or any other registrar, there are no barriers to entry) have approved as valid implementations of Smart Identity. These are curated by hashing the bytecode of a known good contract. This should be maintained as a list on the Blockchain so that other contracts can perform (optional) real-time verification that a contract is present on this list, and therefore a valid smart identity. There may be multiple statuses on this registry (initially Pending / Accepted / Rejected) so that the contracts can be better maintained. 118 | 119 | ### Attributes 120 | 121 | An Attribute is a specific instance of an attribute template which has been populated and (the corresponding hash) stored within a Smart ID. 122 | 123 | If verification of attribute field subsets is required, for instance to use a digital driving license in order to prove age but not disclose address, the attribute hash should be the merkle root of the attribute field set (with appropriate salt or RND values applied at the leaf node level to prevent reverse engineering of leaf node hash values). 124 | 125 | **The attribute hash corresponds to an attribute record stored off-chain, which consists of at least:** 126 | 127 | * AttributeHash 128 | * AttributeId (attribute template identifier) 129 | * Attribute field set 130 | 131 | Attribute creation/update/removal transactions can only be submitted by the identity owner. 132 | 133 | ### Endorsements 134 | 135 | An endorsement is a notarised record of attestation by a third party in relation to a specified attribute, stored with the attribute within the identity contract. Our initial implementation uses a single endorsement template. The definition of what 'endorsement' means for a given attribute can also be varied within the underlying attribute definition to provide some flexibility. 136 | 137 | Receivers/consumers of identity data may (should) privately manage the Endorser identities they are prepared to trust. 138 | 139 | **The endorsement hash corresponds to an endorsement record stored off-chain, which consists of at least:** 140 | 141 | * Endorsement Hash 142 | * Endorsee Address (Smart ID) 143 | * Endorsed Attribute Hash 144 | * Endorsement Expiry Date 145 | * Endorser Address (Smart ID) 146 | * Endorser signature of endorsement 147 | 148 | Whilst attributes can only be added by an identity owner, endorsements must be added anonymously from previously unused ethereum public keys. This is to preserve privacy and prevent unwanted identification of an endorsing party. This will also allow endorsements to be created 'off chain', and added in by the owner themselves, providing the signature of the endorsement can be verified against an on-chain Identity. The unrestricted ability to add endorsements presents a risk of spam or unwanted endorsements, for which there are a number of potential solutions, and for which future protocol updates may be introduced. 149 | 150 | ### Value of the identity / attribute / endorsement model 151 | 152 | Given that an attribute has been endorsed by a trusted third party, the weight of that endorsement is what adds value to the attribute. An attribute without endorsement requires complete trust in the identity. For the benefit of the ecosystem, each time a user chooses to trust such an attribute, they should endorse the fact that they trust it. 153 | 154 | The challenge lies in providing the transparency of endorsement types, if not endorsement people. For example, if I simply endorse a driving licence for the purpose of identifying a person qualfies for entry into a nightclub, then that same endorsement should hold far less weight on whether a person is entititled to drive. To that end I should only endorse the attributes of the driving licence that I have relied upon for my judgement, and rarely the document as a whole. 155 | 156 | ### Attribute Templates 157 | 158 | An Attribute Template can describe either a single field, or collection of fields representing a logical set of identity data. 159 | 160 | Attribute templates are created and stored within the Smart Identity application instance (see Admin Microservice below). 161 | 162 | The model for templates should follow that of jsonschemaform - whereby the definition of the form is kept in the attribute. 163 | 164 | Whilst bespoke attribute templates can be configured for any purpose (consider an attribute template as a data collection form which facilitates personal data notarisation), it is expected that common attributes (e.g. driving license) will be standardised and endorsable, and in time repositories of common templates will be curated and shared for common reference. 165 | 166 | ## Wider architecture 167 | 168 | The distributed ledger and identity smart contract form just one layer in the Smart Identity ecosystem, outside of this layer, numerous applications support the various off-chain storage and processing tasks needed during interaction with the blockchain. Operators are free to use alternative components, however Deloitte have developed a reference application formed of various capability microservices, and have begun a process of porting these into the opensource repository, components include: 169 | 170 | * Core application and blockchain interface 171 | * Application and User (data vault) databases 172 | * Administration functions such as attribute template creation 173 | * Core user actions (create identity, add/update/remove attributes, add/update/remove endorsements) 174 | * Attribute/Endorsement sharing and verification services 175 | * Web user interface providing user access to core application features 176 | 177 | ## Licensing 178 | 179 | This code is released under the Apache v2 open source licence. 180 | 181 | ## Contributions 182 | 183 | We've launched the core Smart Contracts of SmartIDentity to allow third parties to start coming up with their own implementations of Smart Identity. 184 | 185 | Please help by coming up with ways in which you could implement Smart Identity, share your ideas with us on the issues tab (first check the [Use Cases](#use-cases) list below in case we have already added it), and help us make sure that the solidity code is secure. 186 | 187 | We've created two use cases in the test/scenario/ folder, to give an example of how Smart Identity could be used. 188 | Please add in some example tests of your own that either help support Smart Identity, or that come up with use cases that we need to think about further. 189 | 190 | Direct your questions to the GitHub issue tracker, and we will endeavour to respond. 191 | 192 | ## How to contribute 193 | 194 | **Understand the vision. Review and experiment with the code. Contribute to the design and build process.** 195 | 196 | We encourage all contributors and welcome all constructive input. By opening the project we reduce the barriers to access, and expose it to an exponentially expanding pool of rich and diverse brainpower; we do this to attract an open and collaborative community intended to validate, inform and accelerate the further development, testing and hardening of core components, and ultimately to build momentum for widespread adoption. 197 | 198 | To contribute, please follow the process outlined below. 199 | 200 | 1. Create an issue in Github. 201 | 2. Anyone can review and comment on the issue, and a member of the Smart ID team will advise / recommend next steps. If the team is already working on a similar issue we will let you know and close the issue to prevent the issue board from clogging up. 202 | 3. If you wish to complete the development work on an issue please start development. Please note, to contribute to the Smart Identity code base, you will need to agree to the contributors license agreement (below - for more info contact blockchain@deloitte.co.uk). 203 | 4. If you do not wish to do the development work, please leave the ticket open for one of the Smart ID team or another contributor from the Open Source community to pick up and complete. 204 | 5. Create a PR once you have finished development - Please include tests and any relevant documentation. 205 | 6. The PR will be reviewed by the Smart ID team. 206 | 7. If approved the PR will be merged into our repo. 207 | 208 | [Contributor License Agreement](CONTRIBUTING.md) 209 | 210 | ## Use Cases 211 | 212 | This is a non-exhaustive list of example use cases for Smart Identity. Distributed identity is a powerful enabler for many digital services and so we expect that more use cases will come up as the Smart Identity project progresses. 213 | 214 | * **Identity Management:** 215 | 216 | Our core use case is the provision of an efficient, standardised identity platform to enable automation of complex identity verification processes. Today, customer identity information is duplicated in every organisation a customer touches. Records are poorly integrated, and information is often out of date. This duplication of data leads to friction within processes and duplication of activity. Common activities such as KYC/KYB compliance checks are often repeated both across and within organisations. 217 | 218 | Smart ID enables an industry-wide transition from disconnected and duplicated customer information records, to widespread integration with a distributed master identity record. Users are able to create an identity profile, and allow trusted users, including institutions, to verify its authenticity. Users can then interact between one another directly, safe in the knowledge that their counterparty is who they say they are. 219 | 220 | * **Travel and International Settlement:** 221 | 222 | Users could transport their digital identity across jurisdictions and use it to easily travel through border checks and gain access to financial and other services in their new place of residence. 223 | 224 | * **Tailored Risk and Insurance Policies:** 225 | 226 | Insurance providers are able to use customer digital identities to more effectively build risk profiles and develop tailored risk-based products. 227 | 228 | * **Asset Ownership:** 229 | 230 | Assets with their own digital identity can be linked to and securely transferred between individual identities with an immutable record of asset ownership. Smart Identity could be used to send, receive and store digital asset tokens representing currency or property. 231 | 232 | * **Licensing, IP and Digital Rights Management:** 233 | 234 | Digital rights (eg. digital media) and intellectual property could be linked to individual or organisational identities. Transfer of ownership, usage terms and royalty payments could all be managed using Smart Identity. 235 | 236 | * **Digital Driving Licences (and other documents):** 237 | 238 | Digital licences could be used to represent a right-to-drive, manage penalties, offences and revocations. 239 | 240 | * **Digital Public Services (and Gov.verify):** 241 | 242 | Individuals could use Smart Identity to validate their right to access digital public services (eg. NHS treatment, welfare, council tax, etc.). Smart Identity could also be integrated as a Gov.verify service to assert your digital identity credentials. 243 | 244 | * **Secure Digital Voting:** 245 | 246 | The right-to-vote could be verified using individual digital identity with the ability to digitally allocate a vote, while reducing the risk of fraudulent voting. 247 | 248 | ## Disclaimer 249 | 250 | Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 251 | 252 | It has not been exhaustively tested and should be treated as a prototype, not a production-ready application. 253 | 254 | Recommended for private or controlled testnet use only at this stage. Use with a public testnet should be controlled to avoid use of real identity data. 255 | 256 | --------------------------------------------------------------------------------