From 2c1de4671babe27724379925454c013f9ec77b4f Mon Sep 17 00:00:00 2001 From: S3bb1 Date: Wed, 29 Jan 2020 15:28:40 +0100 Subject: [PATCH 001/104] parallize sharing and unsharing - [CORE-901] --- src/contracts/digital-twin/container.ts | 105 ++++++++++++++---------- 1 file changed, 63 insertions(+), 42 deletions(-) diff --git a/src/contracts/digital-twin/container.ts b/src/contracts/digital-twin/container.ts index b7591e90..1c388030 100644 --- a/src/contracts/digital-twin/container.ts +++ b/src/contracts/digital-twin/container.ts @@ -1010,18 +1010,25 @@ export class Container extends Logger { `tried to share properties, but missing one or more in schema: ${missingProperties}`, ); } + // for all share configs - for (const { - accountId, read = [], readWrite = [], removeListEntries = [], - } of localShareConfig) { + const userPromises = []; + + userPromises.concat(localShareConfig.map(async (shareConfig) => { + const { + accountId, read = [], readWrite = [], removeListEntries = [], + } = shareConfig; + + const accessPromises = []; + // //////////////////////////////////////////////// ensure that account is member in contract if (!await this.options.executor.executeContractCall( this.contract, 'isConsumer', accountId, ) ) { - await this.options.dataContract.inviteToContract( + accessPromises.push(this.options.dataContract.inviteToContract( null, this.contract.options.address, this.config.accountId, accountId, - ); + )); } // ///////////////////////////////////////////////////// ensure property roles and membership @@ -1030,7 +1037,7 @@ export class Container extends Logger { read.push('type'); } // ensure that roles for fields exist and that accounts have permissions - for (const property of readWrite) { + accessPromises.concat(readWrite.map(async (property) => { // get permissions from contract const hash = this.options.rightsAndRoles.getOperationCapabilityHash( property, @@ -1073,10 +1080,9 @@ export class Container extends Logger { this.contract, this.config.accountId, accountId, permittedRole, ); } - } - + })); - for (const property of removeListEntries) { + accessPromises.concat(removeListEntries.map(async (property) => { const propertyType = getPropertyType(schemaProperties[property].type); // throw error if remove should be given on no list @@ -1126,7 +1132,9 @@ export class Container extends Logger { this.contract, this.config.accountId, accountId, permittedRole, ); } - } + })); + + await Throttle.all(accessPromises, { maxInProgress: 10 }); // ensure that content keys were created for all shared properties await Promise.all([...read, ...readWrite].map( @@ -1183,7 +1191,9 @@ export class Container extends Logger { ); } }); - } + })); + + await Throttle.all(userPromises); } /** @@ -1374,13 +1384,20 @@ export class Container extends Logger { } } + const shareConfigPromises = []; // for all share configs - for (const { - accountId, readWrite = [], removeListEntries = [], write = [], - } of localUnshareConfigs) { + + shareConfigPromises.concat(localUnshareConfigs.map(async (unshareConfig) => { + const { + accountId, readWrite = [], removeListEntries = [], write = [], + } = unshareConfig; + this.log('checking unshare configs', 'debug'); // remove write permissions for all in readWrite and write - for (const property of [...readWrite, ...write]) { + + const accessPromises = []; + + accessPromises.concat([...readWrite, ...write].map(async (property) => { this.log(`removing write permissions for ${property}`, 'debug'); const propertyType = getPropertyType(schemaProperties[property].type); // search for role with permissions @@ -1418,34 +1435,10 @@ export class Container extends Logger { ); } } - } - - // /////////////////// check if only remaining property is 'type', cleanup if that's the case - const shareConfig = await this.getContainerShareConfigForAccount(accountId); - const remainingFields = Array.from(new Set([ - ...(shareConfig.read ? shareConfig.read : []), - ...(shareConfig.readWrite ? shareConfig.readWrite : []), - ])); - if (remainingFields.length === 1 && remainingFields[0] === 'type') { - // remove property - const permittedRole = await this.getPermittedRole( - authority, 'type', PropertyType.Entry, ModificationType.Set, - ); - await this.options.rightsAndRoles.removeAccountFromRole( - this.contract, this.config.accountId, accountId, permittedRole, - ); - - // remove read if applicable - readWrite.push('type'); - - // uninvite - this.options.dataContract.removeFromContract( - null, await this.getContractAddress(), this.config.accountId, accountId, - ); - } + })); // ///////////////////////////////////////////////////////////// remove list entries handling - for (const property of removeListEntries) { + accessPromises.concat(removeListEntries.map(async (property) => { const propertyType = getPropertyType(schemaProperties[property].type); const permittedRole = await this.getPermittedRole( authority, property, propertyType, ModificationType.Remove, @@ -1476,6 +1469,32 @@ export class Container extends Logger { false, ); } + })); + + await Throttle.all(accessPromises); + + // /////////////////// check if only remaining property is 'type', cleanup if that's the case + const shareConfig = await this.getContainerShareConfigForAccount(accountId); + const remainingFields = Array.from(new Set([ + ...(shareConfig.read ? shareConfig.read : []), + ...(shareConfig.readWrite ? shareConfig.readWrite : []), + ])); + if (remainingFields.length === 1 && remainingFields[0] === 'type') { + // remove property + const permittedRole = await this.getPermittedRole( + authority, 'type', PropertyType.Entry, ModificationType.Set, + ); + await this.options.rightsAndRoles.removeAccountFromRole( + this.contract, this.config.accountId, accountId, permittedRole, + ); + + // remove read if applicable + readWrite.push('type'); + + // uninvite + this.options.dataContract.removeFromContract( + null, await this.getContractAddress(), this.config.accountId, accountId, + ); } // //////////////////////////////////////////////////// ensure encryption keys for properties @@ -1547,7 +1566,9 @@ export class Container extends Logger { } }); }); - } + })); + + await Throttle.all(shareConfigPromises); } /** From 42d3209b776b4e5ddbc21fa3b3be65f7c8fe3e8b Mon Sep 17 00:00:00 2001 From: S3bb1 Date: Wed, 29 Jan 2020 17:00:09 +0100 Subject: [PATCH 002/104] fix throttle all - [CORE-901] --- src/contracts/digital-twin/container.ts | 31 ++++++++----------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/src/contracts/digital-twin/container.ts b/src/contracts/digital-twin/container.ts index 1c388030..cf03efab 100644 --- a/src/contracts/digital-twin/container.ts +++ b/src/contracts/digital-twin/container.ts @@ -1012,23 +1012,20 @@ export class Container extends Logger { } // for all share configs - const userPromises = []; - - userPromises.concat(localShareConfig.map(async (shareConfig) => { + await Throttle.all(localShareConfig.map((shareConfig) => async () => { const { accountId, read = [], readWrite = [], removeListEntries = [], } = shareConfig; - - const accessPromises = []; + let accessPromises = []; // //////////////////////////////////////////////// ensure that account is member in contract if (!await this.options.executor.executeContractCall( this.contract, 'isConsumer', accountId, ) ) { - accessPromises.push(this.options.dataContract.inviteToContract( + await this.options.dataContract.inviteToContract( null, this.contract.options.address, this.config.accountId, accountId, - )); + ); } // ///////////////////////////////////////////////////// ensure property roles and membership @@ -1037,7 +1034,7 @@ export class Container extends Logger { read.push('type'); } // ensure that roles for fields exist and that accounts have permissions - accessPromises.concat(readWrite.map(async (property) => { + accessPromises = accessPromises.concat(readWrite.map((property) => async () => { // get permissions from contract const hash = this.options.rightsAndRoles.getOperationCapabilityHash( property, @@ -1082,7 +1079,7 @@ export class Container extends Logger { } })); - accessPromises.concat(removeListEntries.map(async (property) => { + accessPromises = accessPromises.concat(removeListEntries.map((property) => async () => { const propertyType = getPropertyType(schemaProperties[property].type); // throw error if remove should be given on no list @@ -1133,9 +1130,7 @@ export class Container extends Logger { ); } })); - await Throttle.all(accessPromises, { maxInProgress: 10 }); - // ensure that content keys were created for all shared properties await Promise.all([...read, ...readWrite].map( (property) => this.ensureKeyInSharing(property), @@ -1192,8 +1187,6 @@ export class Container extends Logger { } }); })); - - await Throttle.all(userPromises); } /** @@ -1384,10 +1377,8 @@ export class Container extends Logger { } } - const shareConfigPromises = []; // for all share configs - - shareConfigPromises.concat(localUnshareConfigs.map(async (unshareConfig) => { + await Throttle.all(localUnshareConfigs.map(async (unshareConfig) => { const { accountId, readWrite = [], removeListEntries = [], write = [], } = unshareConfig; @@ -1395,9 +1386,9 @@ export class Container extends Logger { this.log('checking unshare configs', 'debug'); // remove write permissions for all in readWrite and write - const accessPromises = []; + let accessPromises = []; - accessPromises.concat([...readWrite, ...write].map(async (property) => { + accessPromises = accessPromises.concat([...readWrite, ...write].map(async (property) => { this.log(`removing write permissions for ${property}`, 'debug'); const propertyType = getPropertyType(schemaProperties[property].type); // search for role with permissions @@ -1438,7 +1429,7 @@ export class Container extends Logger { })); // ///////////////////////////////////////////////////////////// remove list entries handling - accessPromises.concat(removeListEntries.map(async (property) => { + accessPromises = accessPromises.concat(removeListEntries.map(async (property) => { const propertyType = getPropertyType(schemaProperties[property].type); const permittedRole = await this.getPermittedRole( authority, property, propertyType, ModificationType.Remove, @@ -1567,8 +1558,6 @@ export class Container extends Logger { }); }); })); - - await Throttle.all(shareConfigPromises); } /** From a946b52d4f34f6022fcb3ad162405b5d02f84cd8 Mon Sep 17 00:00:00 2001 From: S3bb1 Date: Thu, 30 Jan 2020 08:05:58 +0100 Subject: [PATCH 003/104] fix Throttle async returns - [CORE-901] --- src/contracts/digital-twin/container.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/contracts/digital-twin/container.ts b/src/contracts/digital-twin/container.ts index cf03efab..2b9def57 100644 --- a/src/contracts/digital-twin/container.ts +++ b/src/contracts/digital-twin/container.ts @@ -1378,7 +1378,7 @@ export class Container extends Logger { } // for all share configs - await Throttle.all(localUnshareConfigs.map(async (unshareConfig) => { + await Throttle.all(localUnshareConfigs.map((unshareConfig) => async () => { const { accountId, readWrite = [], removeListEntries = [], write = [], } = unshareConfig; @@ -1429,7 +1429,7 @@ export class Container extends Logger { })); // ///////////////////////////////////////////////////////////// remove list entries handling - accessPromises = accessPromises.concat(removeListEntries.map(async (property) => { + accessPromises = accessPromises.concat(removeListEntries.map((property) => async () => { const propertyType = getPropertyType(schemaProperties[property].type); const permittedRole = await this.getPermittedRole( authority, property, propertyType, ModificationType.Remove, From eb4daa7358422c6e098ea269b236491b65be039c Mon Sep 17 00:00:00 2001 From: S3bb1 Date: Thu, 30 Jan 2020 08:31:24 +0100 Subject: [PATCH 004/104] fix async call - [CORE-901] --- src/contracts/digital-twin/container.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/contracts/digital-twin/container.ts b/src/contracts/digital-twin/container.ts index 2b9def57..d6a3356e 100644 --- a/src/contracts/digital-twin/container.ts +++ b/src/contracts/digital-twin/container.ts @@ -1386,9 +1386,9 @@ export class Container extends Logger { this.log('checking unshare configs', 'debug'); // remove write permissions for all in readWrite and write - let accessPromises = []; + let accessP = []; - accessPromises = accessPromises.concat([...readWrite, ...write].map(async (property) => { + accessP = accessP.concat([...readWrite, ...write].map((property) => async () => { this.log(`removing write permissions for ${property}`, 'debug'); const propertyType = getPropertyType(schemaProperties[property].type); // search for role with permissions @@ -1429,7 +1429,7 @@ export class Container extends Logger { })); // ///////////////////////////////////////////////////////////// remove list entries handling - accessPromises = accessPromises.concat(removeListEntries.map((property) => async () => { + accessP = accessP.concat(removeListEntries.map((property) => async () => { const propertyType = getPropertyType(schemaProperties[property].type); const permittedRole = await this.getPermittedRole( authority, property, propertyType, ModificationType.Remove, @@ -1462,7 +1462,7 @@ export class Container extends Logger { } })); - await Throttle.all(accessPromises); + await Throttle.all(accessP); // /////////////////// check if only remaining property is 'type', cleanup if that's the case const shareConfig = await this.getContainerShareConfigForAccount(accountId); From 6bde7e40b7bfa4997bd2a5865f1f80bc1604bffd Mon Sep 17 00:00:00 2001 From: S3bb1 Date: Fri, 31 Jan 2020 14:55:18 +0100 Subject: [PATCH 005/104] fix calculation of roles - [CORE-901] --- src/contracts/digital-twin/container.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/contracts/digital-twin/container.ts b/src/contracts/digital-twin/container.ts index d6a3356e..e29dd8fc 100644 --- a/src/contracts/digital-twin/container.ts +++ b/src/contracts/digital-twin/container.ts @@ -1034,7 +1034,7 @@ export class Container extends Logger { read.push('type'); } // ensure that roles for fields exist and that accounts have permissions - accessPromises = accessPromises.concat(readWrite.map((property) => async () => { + accessPromises = accessPromises.concat(readWrite.map((property, index) => async () => { // get permissions from contract const hash = this.options.rightsAndRoles.getOperationCapabilityHash( property, @@ -1050,13 +1050,14 @@ export class Container extends Logger { const binary = (new BigNumber(rolesMap)).toString(2); // search for role with permissions let permittedRole = [...binary].reverse().join('').indexOf('1'); - if (permittedRole < this.reservedRoles) { + if (permittedRole + index < this.reservedRoles) { // if not found or included in reserved roles, add new role const roleCount = await this.options.executor.executeContractCall(authority, 'roleCount'); if (roleCount >= 256) { throw new Error(`could not share property "${property}", maximum role count reached`); } permittedRole = Math.max(this.reservedRoles, roleCount); + permittedRole += index; await this.options.rightsAndRoles.setOperationPermission( authority, this.config.accountId, @@ -1072,6 +1073,7 @@ export class Container extends Logger { const hasRole = await this.options.executor.executeContractCall( authority, 'hasUserRole', accountId, permittedRole, ); + if (!hasRole) { await this.options.rightsAndRoles.addAccountToRole( this.contract, this.config.accountId, accountId, permittedRole, From c7c3b3b23f71c27c3f503a045f3eff7096607a8a Mon Sep 17 00:00:00 2001 From: S3bb1 Date: Tue, 4 Feb 2020 10:20:58 +0100 Subject: [PATCH 006/104] disable parallel execution for users - [CORE-901] --- src/contracts/digital-twin/container.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/contracts/digital-twin/container.ts b/src/contracts/digital-twin/container.ts index e29dd8fc..f9be57b8 100644 --- a/src/contracts/digital-twin/container.ts +++ b/src/contracts/digital-twin/container.ts @@ -1012,10 +1012,9 @@ export class Container extends Logger { } // for all share configs - await Throttle.all(localShareConfig.map((shareConfig) => async () => { - const { - accountId, read = [], readWrite = [], removeListEntries = [], - } = shareConfig; + for (const { + accountId, read = [], readWrite = [], removeListEntries = [], + } of localShareConfig) { let accessPromises = []; // //////////////////////////////////////////////// ensure that account is member in contract @@ -1188,7 +1187,7 @@ export class Container extends Logger { ); } }); - })); + } } /** From ff390de6ada72e269ae01ee9ba47b4fdb1e37524 Mon Sep 17 00:00:00 2001 From: OmairLiaquatAli Date: Tue, 4 Feb 2020 14:40:03 +0100 Subject: [PATCH 007/104] update docs for getSharings and getSharingsFromContracts -[CORE-948] --- docs/contracts/sharing.rst | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/contracts/sharing.rst b/docs/contracts/sharing.rst index 18c5c936..663828ca 100644 --- a/docs/contracts/sharing.rst +++ b/docs/contracts/sharing.rst @@ -594,6 +594,8 @@ getSharings Get sharing from a contract, if _partner, _section, _block matches. +Sharings can also be retrieved using ENS address. + ---------- Parameters ---------- @@ -608,7 +610,7 @@ Parameters Returns ------- -``Promise`` returns ``void``: resolved when done +``Promise`` returns ``any``: sharings as an object ------- Example @@ -618,7 +620,10 @@ Example const randomSecret = `super secret; ${Math.random()}`; await sharing.addSharing(testAddress, accounts[1], accounts[0], '*', 0, randomSecret); - const sharings = await sharing.getSharings(testAddress); + const sharings = await sharing.getSharings(contract.options.address, null, null, null, sharingId); + //Output: + { '0x6760305476495b089868ae42c2293d5e8c1c7bf9bfe51a9ad85b36d85f4113cb': + { '0x04994f67dc55b09e814ab7ffc8df3686b4afb2bb53e60eae97ef043fe03fb829': { '0': randomSecret } } } @@ -686,7 +691,9 @@ getSharingsFromContract Get encrypted sharings from smart contract. -This can be used in combination with :ref:`getSharingsFromContract` to bulk editing sharing info. +The encrypted sharings are usually used in combination with other functions for purposes of adding, removing, extending sharings etc. +For Example: +This can be used in combination with :ref:`saveSharingsToContract` to bulk editing sharing info. ---------- Parameters @@ -699,7 +706,7 @@ Parameters Returns ------- -``Promise`` returns ``void``: resolved when done +``Promise`` returns ``any``: sharings as an object ------- Example @@ -709,6 +716,9 @@ Example // get sharings (encrypted) const sharings = await sharing.getSharingsFromContract(serviceContract, callIdHash); + // Output: + { '0x6760305476495b089868ae42c2293d5e8c1c7bf9bfe51a9ad85b36d85f4113cb': + { '0x04994f67dc55b09e814ab7ffc8df3686b4afb2bb53e60eae97ef043fe03fb829': { hashKey: [Object] } // make changes to sharing await sharing.extendSharings(sharings, accountId, target, section, 0, contentKeyToShare, null); From 76ddbb63ad4b105e3df4c049deea7ac31b80b38f Mon Sep 17 00:00:00 2001 From: OmairLiaquatAli Date: Thu, 6 Feb 2020 16:12:41 +0100 Subject: [PATCH 008/104] add two functions createJwtForDid and createProofForDid -[CORE-946] --- src/did/did.ts | 100 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/src/did/did.ts b/src/did/did.ts index 1df46f3b..6f7c795e 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -17,7 +17,10 @@ the following URL: https://evan.network/license/ */ +import * as didJWT from 'did-jwt'; + import { + AccountStore, ContractLoader, DfsInterface, Executor, @@ -55,6 +58,13 @@ export interface DidDocumentTemplate { type: string; publicKeyHex: string; }[]; + updated?: { + time: string; + }; + created?: { + time: string; + }; + proof?: DidProof; service?: { id: string; type: string; @@ -73,10 +83,32 @@ export interface DidServiceEntry { [id: string]: any; } +/** + * interface for proof in DIDs + */ +export interface DidProof { + type: string; + created: string; + proofPurpose: string; + verificationMethod: string; + jws: string; +} + +/** + * Holds a list of supported proof types for DID (JWS) proofs + */ +export const enum DidProofType { + EcdsaPublicKeySecp256k1 = 'EcdsaPublicKeySecp256k1', +} + +const JWTProofMapping = {}; +JWTProofMapping[(DidProofType.EcdsaPublicKeySecp256k1)] = 'ES256K-R'; + /** * options for Did constructor */ export interface DidOptions extends LoggerOptions { + accountStore: AccountStore; contractLoader: ContractLoader; dfs: DfsInterface; executor: Executor; @@ -307,6 +339,74 @@ export class Did extends Logger { await this.validateDidAndGetSections(did); } + /** + * Create a JWT over a DID document + * + * @param {didDocument} DID The DID document + * @param {DidProofType} proofType The type of algorithm used for generating the JWT + */ + private async createJwtForDid(didDocument, proofType: DidProofType): Promise { + const signer = didJWT.SimpleSigner( + await this.options.accountStore.getPrivateKey(this.options.signerIdentity.underlyingAccount), + ); + let jwt = ''; + await didJWT.createJWT( + { + didDocument, + }, { + alg: JWTProofMapping[proofType], + issuer: didDocument.id, + signer, + }, + ).then((response) => { jwt = response; }); + + return jwt; + } + + /** + * Creates a new `VcProof` object for a given VC document, including generating a JWT token over + * the whole document. + * + * @param {DidDocument} Did The VC document to create the proof for. + * @param {DidProofType} proofType Specify if you want a proof type different from the + * default one. + * @returns {DidProof} A proof object containing a JWT. + * @throws If the Decentralized identity and the signer identity differ from each other + */ + private async createProofForDid(didDocument, + proofType: DidProofType = DidProofType.EcdsaPublicKeySecp256k1): Promise { + let issuerIdentity; + try { + issuerIdentity = await this.convertDidToIdentity(didDocument.id); + } catch (e) { + throw Error(`Invalid issuer DID: ${didDocument.id}`); + } + + if (this.options.signerIdentity.activeIdentity !== issuerIdentity) { + throw Error('You are not authorized to issue this Did'); + } + + const jwt = await this.createJwtForDid(didDocument, proofType); + const signaturePublicKey = await this.options.signerIdentity.getPublicKey( + this.options.signerIdentity.underlyingAccount, + ); + const key = didDocument.publicKey + .filter((entry) => entry.publicKeyHex === signaturePublicKey)[0]; + if (!key) { + throw Error('The signature key for the active account is not associated to its DID document.'); + } + + const proof: DidProof = { + type: `${proofType}`, + created: new Date(Date.now()).toISOString(), + proofPurpose: 'assertionMethod', + verificationMethod: key.id, + jws: jwt, + }; + + return proof; + } + /** * Retrieve a default DID document for identities that do not have a document associated yet. * @param did DID to fetch a document for. From 29f75ff9c52306a8e96a923a7f74eca656d2cb41 Mon Sep 17 00:00:00 2001 From: OmairLiaquatAli Date: Thu, 6 Feb 2020 16:14:02 +0100 Subject: [PATCH 009/104] add accountStore when returning Did -[CORE-946] --- src/test/test-utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/test-utils.ts b/src/test/test-utils.ts index 08d91b56..159f68ea 100644 --- a/src/test/test-utils.ts +++ b/src/test/test-utils.ts @@ -179,6 +179,7 @@ export class TestUtils { await executor.init({ eventHub: await TestUtils.getEventHub(web3) }); return new Did({ + accountStore: this.getAccountStore(), contractLoader: await this.getContractLoader(web3), dfs: dfs || (await this.getIpfs()), executor, From de71cc46c045b25f592535a39dbee72fac83907a Mon Sep 17 00:00:00 2001 From: OmairLiaquatAli Date: Thu, 6 Feb 2020 16:15:11 +0100 Subject: [PATCH 010/104] add accountStore to new did -[CORE-946] --- src/onboarding.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/onboarding.ts b/src/onboarding.ts index 50f5f0ef..44d723ae 100644 --- a/src/onboarding.ts +++ b/src/onboarding.ts @@ -618,6 +618,7 @@ export class Onboarding extends Logger { }); const did = new Did({ + accountStore: runtime.accountStore, contractLoader: runtime.contractLoader, dfs: runtime.dfs, executor: runtime.executor, From f13af83843a7b3df6eedd3576bd0dba020c1c0a9 Mon Sep 17 00:00:00 2001 From: OmairLiaquatAli Date: Thu, 6 Feb 2020 16:16:00 +0100 Subject: [PATCH 011/104] add accountStore to did in runtime -[CORE-946] --- src/runtime.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/runtime.ts b/src/runtime.ts index fb15865f..5dff3b7b 100644 --- a/src/runtime.ts +++ b/src/runtime.ts @@ -484,6 +484,7 @@ export async function createDefaultRuntime( let vc: Vc; if (runtimeConfig.useIdentity) { did = new Did({ + accountStore, contractLoader, dfs, executor, From 51a5f83dc4b6dfe7fc662f7fba5d1102fd511a63 Mon Sep 17 00:00:00 2001 From: Omair Ali <36410182+OmairLiaquatAli@users.noreply.github.com> Date: Thu, 6 Feb 2020 16:24:25 +0100 Subject: [PATCH 012/104] Apply suggestions from code review Co-Authored-By: wulfraem --- docs/contracts/sharing.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/contracts/sharing.rst b/docs/contracts/sharing.rst index 663828ca..c8a0df4f 100644 --- a/docs/contracts/sharing.rst +++ b/docs/contracts/sharing.rst @@ -718,7 +718,7 @@ Example const sharings = await sharing.getSharingsFromContract(serviceContract, callIdHash); // Output: { '0x6760305476495b089868ae42c2293d5e8c1c7bf9bfe51a9ad85b36d85f4113cb': - { '0x04994f67dc55b09e814ab7ffc8df3686b4afb2bb53e60eae97ef043fe03fb829': { hashKey: [Object] } + { '0x04994f67dc55b09e814ab7ffc8df3686b4afb2bb53e60eae97ef043fe03fb829': { hashKey: [Object] } } // make changes to sharing await sharing.extendSharings(sharings, accountId, target, section, 0, contentKeyToShare, null); @@ -861,4 +861,4 @@ Example .. _source logLogInterface: ../common/logger.html#logloginterface .. |source nameResolver| replace:: ``NameResolver`` -.. _source nameResolver: ../blockchain/name-resolver.html \ No newline at end of file +.. _source nameResolver: ../blockchain/name-resolver.html From a76ba688b4fae39635887cc19321561bbc6131db Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Fri, 7 Feb 2020 09:30:47 +0100 Subject: [PATCH 013/104] Added check for fetching deactivated twins [CORE-947] --- src/did/did.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/did/did.ts b/src/did/did.ts index f39eeac8..0da4a75a 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -142,6 +142,7 @@ export class Did extends Logger { * * @param {string} did DID to fetch DID document for * @return {Promise} a DID document that MAY resemble `DidDocumentTemplate` format + * @throws if a DID has been deactivated or if no DID document has been set yet */ public async getDidDocument(did: string): Promise { let result = null; @@ -150,11 +151,23 @@ export class Did extends Logger { ? await this.convertDidToIdentity(did) : this.options.signerIdentity.activeIdentity, ); + + const isDeactivated = await this.options.executor.executeContractCall( + await this.getRegistryContract(), + 'deactivatedDids', + identity, + ); + + if (isDeactivated) { + throw Error(`DID ${did} has been deactivated.`); + } + const documentHash = await this.options.executor.executeContractCall( await this.getRegistryContract(), 'didDocuments', identity, ); + if (documentHash === nullBytes32) { throw Error(`There is no DID document associated to ${did} yet`); } From afb78933066a4e162eeaee4748df2dd328da8b08 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Fri, 7 Feb 2020 13:35:07 +0100 Subject: [PATCH 014/104] Added did deactivation functionality [CORE-947] --- .../digital-twin/digital-twin.spec.ts | 26 ++--- src/contracts/digital-twin/digital-twin.ts | 2 +- src/did/did.spec.ts | 97 +++++++++++++++++++ src/did/did.ts | 57 +++++++---- 4 files changed, 142 insertions(+), 40 deletions(-) diff --git a/src/contracts/digital-twin/digital-twin.spec.ts b/src/contracts/digital-twin/digital-twin.spec.ts index f90f1190..0a376bef 100644 --- a/src/contracts/digital-twin/digital-twin.spec.ts +++ b/src/contracts/digital-twin/digital-twin.spec.ts @@ -385,21 +385,14 @@ describe('DigitalTwin', function test() { expect(twinAuthority).to.equal(nullAddress); expect(twinDescriptionHash).to.equal(nullBytes32); - // Check if did registry points to 0x0 - // Directly access the registry to be independent of DID api implementation + // Check if did has been deactivated const did = await TestUtils.getDid( localRuntime.web3, localRuntime.activeAccount, localRuntime.dfs, ); - const didContract = await (did as any).getRegistryContract(); - const didDocumentHash = await runtime.executor.executeContractCall( - didContract, - 'didDocuments', - (did as any).padIdentity(twinIdentity), - ); - - expect(didDocumentHash).to.eq(nullBytes32); + const twinDid = await did.convertIdentityToDid(twinIdentity); + expect(did.didIsDeactivated(twinDid)).to.eventually.be.true; // Check if all pinned hashes have been unpinned const pinnedHashesAfterDeactivation = await getPinnedFileHashes(); @@ -494,21 +487,14 @@ describe('DigitalTwin', function test() { expect(twinAuthority).to.equal(nullAddress); expect(twinDescriptionHash).to.equal(nullBytes32); - // Check if did registry points to 0x0 - // Directly access the registry to be independent of DID api implementation + // Check if did has been deactivated const did = await TestUtils.getDid( localRuntime.web3, localRuntime.activeAccount, localRuntime.dfs, ); - const didContract = await (did as any).getRegistryContract(); - const didDocumentHash = await runtime.executor.executeContractCall( - didContract, - 'didDocuments', - (did as any).padIdentity(twinIdentity), - ); - - expect(didDocumentHash).to.eq(nullBytes32); + const twinDid = await did.convertIdentityToDid(twinIdentity); + expect(did.didIsDeactivated(twinDid)).to.eventually.be.true; }); }); diff --git a/src/contracts/digital-twin/digital-twin.ts b/src/contracts/digital-twin/digital-twin.ts index 31289cc7..9b4245dc 100644 --- a/src/contracts/digital-twin/digital-twin.ts +++ b/src/contracts/digital-twin/digital-twin.ts @@ -461,7 +461,7 @@ export class DigitalTwin extends Logger { // Unset did const twinDid = await this.options.did.convertIdentityToDid(description.identity); - await this.options.did.removeDidDocument(twinDid); + await this.options.did.deactivateDidDocument(twinDid); // Deactivate identity if (this.options.verifications.contracts.registry) { diff --git a/src/did/did.spec.ts b/src/did/did.spec.ts index 56a12cc9..12fdbe12 100644 --- a/src/did/did.spec.ts +++ b/src/did/did.spec.ts @@ -279,6 +279,103 @@ describe('DID Resolver', function test() { const defaultDidDoc = await runtimes[0].did.getDidDocument(twinDid); await expect(defaultDidDoc).to.deep.eq(expectedDefaultDid); }); + + it('allows to deactivate a DID', async () => { + const twin = await DigitalTwin.create( + runtimes[0] as DigitalTwinOptions, + { + accountId: runtimes[0].activeAccount, + containerConfig: null, + description: twinDescription, + }, + ); + + const twinIdentity = await runtimes[0].verifications.getIdentityForAccount( + await twin.getContractAddress(), + true, + ); + const twinDid = await runtimes[0].did.convertIdentityToDid(twinIdentity); + await runtimes[0].did.deactivateDidDocument(twinDid); + + const deactivated = await runtimes[0].did.didIsDeactivated(twinDid); + expect(deactivated).to.be.true; + }); + + it('does not allow to deactivate someone else\'s DID', async () => { + const twin = await DigitalTwin.create( + runtimes[1] as DigitalTwinOptions, + { + accountId: runtimes[1].activeAccount, + containerConfig: null, + description: twinDescription, + }, + ); + + const twinIdentity = await runtimes[0].verifications.getIdentityForAccount( + await twin.getContractAddress(), + true, + ); + const twinDid = await runtimes[0].did.convertIdentityToDid(twinIdentity); + expect( + runtimes[0].did.deactivateDidDocument(twinDid), + ).to.eventually.be.rejectedWith('Deactivation failed'); + }); + + it('does not allow to deactivate a DID twice', async () => { + const twin = await DigitalTwin.create( + runtimes[0] as DigitalTwinOptions, + { + accountId: runtimes[0].activeAccount, + containerConfig: null, + description: twinDescription, + }, + ); + + const twinIdentity = await runtimes[0].verifications.getIdentityForAccount( + await twin.getContractAddress(), + true, + ); + const twinDid = await runtimes[0].did.convertIdentityToDid(twinIdentity); + await runtimes[0].did.deactivateDidDocument(twinDid); + expect( + runtimes[0].did.deactivateDidDocument(twinDid), + ).to.eventually.be.rejectedWith('Deactivation failed'); + }); + + it('does not allow to set a DID after deactivating it', async () => { + const twin = await DigitalTwin.create( + runtimes[0] as DigitalTwinOptions, + { + accountId: runtimes[0].activeAccount, + containerConfig: null, + description: twinDescription, + }, + ); + + const twinIdentity = await runtimes[0].verifications.getIdentityForAccount( + await twin.getContractAddress(), + true, + ); + + const twinDid = await runtimes[0].did.convertIdentityToDid(twinIdentity); + await runtimes[0].did.deactivateDidDocument(twinDid); + const dummyDocument = await runtimes[0].did.getDidDocumentTemplate(); + + const promise = runtimes[0].did.setDidDocument(twinDid, dummyDocument); + expect(promise).to.eventually.be.rejectedWith('Cannot set document for deactivated DID'); + + // Also check on smart contract level + const didRegistryContract = await (runtimes[0].did as any).getRegistryContract(); + + const contractPromise = runtimes[0].executor.executeContractTransaction( + didRegistryContract, + 'setDidDocument', + { from: runtimes[0].activeIdentity }, + twinIdentity, + TestUtils.getRandomBytes32(), + ); + expect(contractPromise).to.eventually.be.rejected; + }); }); describe('when storing did documents for alias identities', () => { diff --git a/src/did/did.ts b/src/did/did.ts index 5e564aa0..0486bf83 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -139,6 +139,25 @@ export class Did extends Logger { return `did:evan:${await this.getDidInfix()}${identity}`; } + /** + * Gets the deactivation status of a DID + * + * @param did DID to check + * @returns {boolean} true, if the DID is deactivated + */ + public async didIsDeactivated(did: string): Promise { + let identity = await this.convertDidToIdentity(did); + identity = this.padIdentity(identity); + + const isDeactivated = await this.options.executor.executeContractCall( + await this.getRegistryContract(), + 'deactivatedDids', + identity, + ); + + return isDeactivated; + } + /** * Get DID document for given DID. * @@ -148,22 +167,16 @@ export class Did extends Logger { */ public async getDidDocument(did: string): Promise { let result = null; + if (await this.didIsDeactivated(did)) { + throw Error(`DID ${did} has been deactivated.`); + } + const identity = this.padIdentity( did ? await this.convertDidToIdentity(did) : this.options.signerIdentity.activeIdentity, ); - const isDeactivated = await this.options.executor.executeContractCall( - await this.getRegistryContract(), - 'deactivatedDids', - identity, - ); - - if (isDeactivated) { - throw Error(`DID ${did} has been deactivated.`); - } - const documentHash = await this.options.executor.executeContractCall( await this.getRegistryContract(), 'didDocuments', @@ -252,6 +265,10 @@ export class Did extends Logger { * @return {Promise} resolved when done */ public async setDidDocument(did: string, document: any): Promise { + if (await this.didIsDeactivated(did)) { + throw Error('Cannot set document for deactivated DID'); + } + const identity = this.padIdentity(did ? await this.convertDidToIdentity(did) : this.options.signerIdentity.activeIdentity); @@ -314,18 +331,20 @@ export class Did extends Logger { * Unlinks the current DID document from the DID * @param did DID to unlink the DID document from */ - public async removeDidDocument(did: string): Promise { + public async deactivateDidDocument(did: string): Promise { const identity = this.padIdentity(did ? await this.convertDidToIdentity(did) : this.options.signerIdentity.activeIdentity); - - await this.options.executor.executeContractTransaction( - await this.getRegistryContract(), - 'setDidDocument', - { from: this.options.signerIdentity.activeIdentity }, - identity, - nullBytes32, - ); + try { + await this.options.executor.executeContractTransaction( + await this.getRegistryContract(), + 'deactivateDid', + { from: this.options.signerIdentity.activeIdentity }, + identity, + ); + } catch (e) { + throw Error('Deactivation failed. Is the DID active and do you have permission to deactivate the DID?'); + } } /** From 20a822f993760d4a2410f1771634a57710ae4600 Mon Sep 17 00:00:00 2001 From: wulfraem Date: Mon, 10 Feb 2020 08:59:48 +0100 Subject: [PATCH 015/104] update encoding to `utf8` - [CORE-921] --- src/contracts/digital-twin/digital-twin.ts | 2 +- src/contracts/sharing.ts | 2 +- src/dfs/ipfs.spec.ts | 51 +++++++++++++++++----- src/dfs/ipfs.ts | 17 +++++++- src/onboarding.ts | 2 +- src/verifications/verifications.ts | 2 +- 6 files changed, 60 insertions(+), 16 deletions(-) diff --git a/src/contracts/digital-twin/digital-twin.ts b/src/contracts/digital-twin/digital-twin.ts index aad02640..e5a18857 100644 --- a/src/contracts/digital-twin/digital-twin.ts +++ b/src/contracts/digital-twin/digital-twin.ts @@ -856,7 +856,7 @@ export class DigitalTwin extends Logger { private async getContainerEntryHashes(containerContract: any, descriptionHash: string): Promise { const description = JSON.parse( - (await this.options.dataContract.getDfsContent(descriptionHash)).toString('binary'), + (await this.options.dataContract.getDfsContent(descriptionHash)).toString('utf8'), ); // Collect entry hashes const encryptedHashes = []; diff --git a/src/contracts/sharing.ts b/src/contracts/sharing.ts index c1929cd6..0fd7f795 100644 --- a/src/contracts/sharing.ts +++ b/src/contracts/sharing.ts @@ -55,7 +55,7 @@ export interface SharingOptions extends LoggerOptions { export class Sharing extends Logger { public options: SharingOptions; - private readonly encodingUnencrypted = 'binary'; + private readonly encodingUnencrypted = 'utf8'; private readonly encodingEncrypted = 'hex'; diff --git a/src/dfs/ipfs.spec.ts b/src/dfs/ipfs.spec.ts index bfd613c4..b9863a24 100644 --- a/src/dfs/ipfs.spec.ts +++ b/src/dfs/ipfs.spec.ts @@ -74,15 +74,6 @@ describe('IPFS handler', function test() { + 'entries found in redis'); }); - it('should be able to add a file with special characters', async () => { - const content = 'öäüßÖÄÜ'; - const encoding = 'binary'; - const hash = await ipfs.add('test', Buffer.from(content, encoding)); - expect(hash).not.to.be.undefined; - const fileContent = await ipfs.get(hash); - expect(fileContent.toString(encoding)).to.eq(content); - }); - it('should be able to add multiple files', async () => { const randomContents = [ Math.random().toString(), @@ -114,8 +105,48 @@ describe('IPFS handler', function test() { const randomContent = Math.random().toString(); const hash = await ipfs.add('test', Buffer.from(randomContent, 'utf-8')); const cacheResponse = await ipfs.cache.get(Ipfs.bytes32ToIpfsHash(hash)); - expect(Buffer.from(cacheResponse).toString('binary')).to.eq(randomContent); + expect(Buffer.from(cacheResponse).toString('utf8')).to.eq(randomContent); // remove cache after test delete ipfs.cache; }); + + describe('when dealing with special characters', () => { + it('should be able to add a file with umlauts, that have been encoded as binary', async () => { + const content = 'öäüßÖÄÜ'; + const encoding = 'binary'; + const hash = await ipfs.add('test', Buffer.from(content, encoding)); + expect(hash).not.to.be.undefined; + const fileContent = await ipfs.get(hash); + expect(fileContent.toString(encoding)).to.eq(content); + }); + + it('should be able to add a file with umlauts, that have been encoded as utf8', async () => { + const content = 'öäüßÖÄÜ'; + const encoding = 'utf8'; + const hash = await ipfs.add('test', Buffer.from(content, encoding)); + expect(hash).not.to.be.undefined; + const fileContent = await ipfs.get(hash); + expect(fileContent.toString(encoding)).to.eq(content); + }); + + it('should be not be able to add a file with extended unicodes (properly), that have been encoded as binary', async () => { + const content = '🌂'; + const encoding = 'binary'; + const broken = Buffer.from('🌂', encoding).toString(encoding); + expect(broken).to.not.eq(content); // Buffer.from binary breaks characters + const hash = await ipfs.add('test', Buffer.from(content, encoding)); + expect(hash).not.to.be.undefined; + const fileContent = await ipfs.get(hash); + expect(fileContent.toString(encoding)).to.eq(broken); + }); + + it('should be able to add a file with extended unicodes, that have been encoded as utf8', async () => { + const content = '🌂'; + const encoding = 'utf8'; + const hash = await ipfs.add('test', Buffer.from(content, encoding)); + expect(hash).not.to.be.undefined; + const fileContent = await ipfs.get(hash); + expect(fileContent.toString(encoding)).to.eq(content); + }); + }); }); diff --git a/src/dfs/ipfs.ts b/src/dfs/ipfs.ts index a58edab3..e7a49dae 100644 --- a/src/dfs/ipfs.ts +++ b/src/dfs/ipfs.ts @@ -200,7 +200,7 @@ export class Ipfs extends Logger implements DfsInterface { if (returnBuffer) { return Buffer.from(buffer); } - return Buffer.from(buffer).toString('binary'); + return this.decodeBuffer(Buffer.from(buffer)); } } let wait; @@ -215,7 +215,7 @@ export class Ipfs extends Logger implements DfsInterface { const getRemoteHash = this.remoteNode.files.cat(ipfsHash) .then((buffer: any) => { const fileBuffer = buffer; - const ret = fileBuffer.toString('binary'); + const ret = this.decodeBuffer(buffer); if (this.cache) { this.cache.add(ipfsHash, fileBuffer); } @@ -298,4 +298,17 @@ export class Ipfs extends Logger implements DfsInterface { }, 60 * 1000); } } + + /** + * Tries to decode given Buffer to UTF-8, if this leads to invalid characters, decode to Latin-1. + * + * @param {Buffer} buffer buffer to decrypt, may be UTF-8 or Latin-1 encoded. + * @return {string} decoded string + */ + private decodeBuffer(buffer: Buffer): string { + const decodedToUtf8 = buffer.toString('utf8'); + return decodedToUtf8.indexOf('�') === -1 + ? decodedToUtf8 + : buffer.toString('binary'); + } } diff --git a/src/onboarding.ts b/src/onboarding.ts index 4d272e8a..0f4a6594 100644 --- a/src/onboarding.ts +++ b/src/onboarding.ts @@ -172,7 +172,7 @@ export class Onboarding extends Logger { }, }; const descriptionHash = await runtime.dfs.add( - 'description', Buffer.from(JSON.stringify(description), 'binary'), + 'description', Buffer.from(JSON.stringify(description), 'utf8'), ); const factory = runtime.contractLoader.loadContract( diff --git a/src/verifications/verifications.ts b/src/verifications/verifications.ts index 1ba44231..c6f75c04 100644 --- a/src/verifications/verifications.ts +++ b/src/verifications/verifications.ts @@ -301,7 +301,7 @@ export class Verifications extends Logger { public contracts: any = { }; - public encodingEnvelope = 'binary'; + public encodingEnvelope = 'utf8'; /** cache all the ens owners */ public ensOwners: any = { }; From dc309cdb82642456278f497dd61076484dba63e0 Mon Sep 17 00:00:00 2001 From: wulfraem Date: Mon, 10 Feb 2020 10:14:23 +0100 Subject: [PATCH 016/104] fix type issue - [CORE-921] --- src/contracts/digital-twin/container.spec.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/contracts/digital-twin/container.spec.ts b/src/contracts/digital-twin/container.spec.ts index de19372c..28aee577 100644 --- a/src/contracts/digital-twin/container.spec.ts +++ b/src/contracts/digital-twin/container.spec.ts @@ -19,15 +19,13 @@ import 'mocha'; import * as chaiAsPromised from 'chai-as-promised'; +import { Executor } from '@evan.network/dbcp'; import { expect, use } from 'chai'; import { promisify } from 'util'; import { readFile } from 'fs'; -import { - Executor, - Ipfs, -} from '@evan.network/dbcp'; import { accounts } from '../../test/accounts'; +import { Ipfs } from '../../dfs/ipfs'; import { TestUtils } from '../../test/test-utils'; import { VerificationsStatus } from '../../verifications/verifications'; import { From ca104579e4e2c3216ae0bf42f876c4ae42baff19 Mon Sep 17 00:00:00 2001 From: wulfraem Date: Mon, 10 Feb 2020 11:32:48 +0100 Subject: [PATCH 017/104] add `member-ordering` rule and update code - [CORE-950] --- .eslintrc.js | 3 +- VERSIONS.md | 1 + package.json | 1 + src/contracts/digital-twin/container.ts | 28 +- src/contracts/digital-twin/digital-twin.ts | 64 +-- src/contracts/wallet.ts | 16 +- src/dfs/ipfs.ts | 32 +- src/dfs/ipld.ts | 32 +- src/did/did.ts | 2 +- src/encryption/aes-blob.ts | 10 +- src/keyExchange.ts | 16 +- src/onboarding.ts | 10 +- src/profile/profile.ts | 30 +- src/verifications/verifications.ts | 572 ++++++++++----------- 14 files changed, 410 insertions(+), 407 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index b29eff64..6ddc9ff2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -18,6 +18,7 @@ module.exports = { // note you must disable the base rule as it can report incorrect errors '@typescript-eslint/indent': ['error', 2], '@typescript-eslint/explicit-function-return-type': ['off'], + '@typescript-eslint/member-ordering': ['error'], '@typescript-eslint/no-explicit-any': ['off'], '@typescript-eslint/no-unused-expressions': ['off'], 'chai-friendly/no-unused-expressions': ['error'], @@ -26,6 +27,6 @@ module.exports = { // exclude cycle dependencies 'import/no-cycle': ['off'], 'no-await-in-loop': ['off'], - 'no-restricted-syntax': ['off'] + 'no-restricted-syntax': ['off'], }, }; diff --git a/VERSIONS.md b/VERSIONS.md index 98686128..9ad66a6b 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -4,6 +4,7 @@ ### Features ### Fixes +- add `member-ordering` rule to eslint config ### Deprecations diff --git a/package.json b/package.json index 4d90b2bd..29d25a6a 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "build-all": "env-cmd --fallback -f ./.env.local npm run build-contracts && env-cmd --fallback -f ./.env.local npm run build", "build-contracts": "node ./scripts/buildContracts.js", "installl": "npm install && ./scripts/link.sh", + "lint": "eslint --ext .js,.ts -c .eslintrc.js ./src", "test:contracts": "env-cmd --fallback -f ./.env.local npm run build && env-cmd --fallback -f ./.env.local mocha --exit --inspect -r ts-node/register src/contracts/*.spec.ts src/contracts/base-contract/*.spec.ts src/contracts/business-center/*.spec.ts src/contracts/service-contract/*.spec.ts", "test:datacontract": "env-cmd --fallback -f ./.env.local npm run build && env-cmd --fallback -f ./.env.local mocha --exit --inspect -r ts-node/register src/contracts/data-contract/*.spec.ts", "test:services": "env-cmd --fallback -f ./.env.local npm run build && env-cmd --fallback -f ./.env.local mocha --exit --inspect -r ts-node/register src/claims/*.spec.ts src/dfs/*.spec.ts src/encryption/*.spec.ts src/profile/*.spec.ts src/*.spec.ts", diff --git a/src/contracts/digital-twin/container.ts b/src/contracts/digital-twin/container.ts index b7591e90..7c9d72ec 100644 --- a/src/contracts/digital-twin/container.ts +++ b/src/contracts/digital-twin/container.ts @@ -249,6 +249,20 @@ export class Container extends Logger { private reservedRoles = 64; + /** + * Create new ``Container`` instance. This will not create a smart contract contract but is used + * to load existing containers. To create a new contract, use the static ``create`` function. + * + * @param {ContainerOptions} options runtime for new container + * @param {ContainerConfig} config config for new container + */ + public constructor(options: ContainerOptions, config: ContainerConfig) { + super(options as LoggerOptions); + this.options = options; + this.config = config; + this.mutexes = {}; + } + /** * Clone ``Container`` instance into template and creates new ``Container`` with it. * @@ -480,20 +494,6 @@ export class Container extends Logger { await profile.loadForAccount(profile.treeLabels.dtContainerPlugins); } - /** - * Create new ``Container`` instance. This will not create a smart contract contract but is used - * to load existing containers. To create a new contract, use the static ``create`` function. - * - * @param {ContainerOptions} options runtime for new container - * @param {ContainerConfig} config config for new container - */ - public constructor(options: ContainerOptions, config: ContainerConfig) { - super(options as LoggerOptions); - this.options = options; - this.config = config; - this.mutexes = {}; - } - /** * Add list entries to a list list property. * diff --git a/src/contracts/digital-twin/digital-twin.ts b/src/contracts/digital-twin/digital-twin.ts index aad02640..73fb7aef 100644 --- a/src/contracts/digital-twin/digital-twin.ts +++ b/src/contracts/digital-twin/digital-twin.ts @@ -146,6 +146,25 @@ export class DigitalTwin extends Logger { private mutexes: { [id: string]: Mutex }; + /** + * Create new DigitalTwin instance. This will not create a smart contract contract but is used + * to load existing containers. To create a new contract, use the static ``create`` function. + * + * @param {DigitalTwinOptions} options runtime-like object with required modules + * @param {DigitalTwinConfig} config digital twin related config + */ + public constructor(options: DigitalTwinOptions, config: DigitalTwinConfig) { + super(options as LoggerOptions); + this.config = config; + // fallback to twin accountId, if containerConfig is specified + this.config.containerConfig = { + accountId: this.config.accountId, + ...this.config.containerConfig, + }; + this.options = options; + this.mutexes = {}; + } + /** * Create digital twin contract. * @@ -340,25 +359,6 @@ export class DigitalTwin extends Logger { return { valid, error, exists }; } - /** - * Create new DigitalTwin instance. This will not create a smart contract contract but is used - * to load existing containers. To create a new contract, use the static ``create`` function. - * - * @param {DigitalTwinOptions} options runtime-like object with required modules - * @param {DigitalTwinConfig} config digital twin related config - */ - public constructor(options: DigitalTwinOptions, config: DigitalTwinConfig) { - super(options as LoggerOptions); - this.config = config; - // fallback to twin accountId, if containerConfig is specified - this.config.containerConfig = { - accountId: this.config.accountId, - ...this.config.containerConfig, - }; - this.options = options; - this.mutexes = {}; - } - /** * Add the digital twin with given address to profile. */ @@ -682,19 +682,6 @@ export class DigitalTwin extends Logger { }); } - /** - * Removes entry from index contract - * @param name Name of entry - */ - private async removeEntry(name: string): Promise { - await this.options.executor.executeContractTransaction( - this.contract, - 'removeEntry', - { from: this.config.accountId }, - name, - ); - } - /** * Write given description to digital twins DBCP. * @@ -768,6 +755,19 @@ export class DigitalTwin extends Logger { ); } + /** + * Removes entry from index contract + * @param name Name of entry + */ + private async removeEntry(name: string): Promise { + await this.options.executor.executeContractTransaction( + this.contract, + 'removeEntry', + { from: this.config.accountId }, + name, + ); + } + private async createAndStoreDidDocument(twinIdentityId: string, controllerId: string): Promise { const twinDid = await this.options.did.convertIdentityToDid(twinIdentityId); diff --git a/src/contracts/wallet.ts b/src/contracts/wallet.ts index 4e6f4e97..7c8bb7f8 100644 --- a/src/contracts/wallet.ts +++ b/src/contracts/wallet.ts @@ -280,7 +280,14 @@ export class Wallet extends Logger { return `${signature}${coder.encodeParameters(types, params).replace('0x', '')}`; } - public async submitAndHandleConfirmation( + private ensureContract(): any { + if (!this.walletContract) { + throw new Error('no wallet contract specified at wallet helper, load or create one'); + } + return this.walletContract; + } + + private async submitAndHandleConfirmation( target: any, functionName: string, inputOptions: any, ...functionArguments ): Promise { const subscriptions = []; @@ -420,11 +427,4 @@ export class Wallet extends Logger { return receipt; } - - private ensureContract(): any { - if (!this.walletContract) { - throw new Error('no wallet contract specified at wallet helper, load or create one'); - } - return this.walletContract; - } } diff --git a/src/dfs/ipfs.ts b/src/dfs/ipfs.ts index a58edab3..c900a7b5 100644 --- a/src/dfs/ipfs.ts +++ b/src/dfs/ipfs.ts @@ -70,6 +70,22 @@ export class Ipfs extends Logger implements DfsInterface { public runtime: Runtime; + public constructor(options) { + super(options); + this.disablePin = options.disablePin || false; + if (options.cache) { + this.cache = options.cache; + } + if (options.remoteNode) { + this.remoteNode = options.remoteNode; + } else if (options.dfsConfig) { + this.dfsConfig = options.dfsConfig; + this.remoteNode = new IpfsLib(options.dfsConfig); + } else { + this.log('No IPFS config of ipfs remotenode are given', 'error'); + } + } + /** * convert IPFS hash to bytes 32 see * https://www.reddit.com/r/ethdev/comments/6lbmhy/a_practical_guide_to_cheap_ipfs_hash_storage_in @@ -102,22 +118,6 @@ export class Ipfs extends Logger implements DfsInterface { return hash; } - public constructor(options) { - super(options); - this.disablePin = options.disablePin || false; - if (options.cache) { - this.cache = options.cache; - } - if (options.remoteNode) { - this.remoteNode = options.remoteNode; - } else if (options.dfsConfig) { - this.dfsConfig = options.dfsConfig; - this.remoteNode = new IpfsLib(options.dfsConfig); - } else { - this.log('No IPFS config of ipfs remotenode are given', 'error'); - } - } - /** * @brief add content to ipfs * diff --git a/src/dfs/ipld.ts b/src/dfs/ipld.ts index 4d255f70..0baff60a 100644 --- a/src/dfs/ipld.ts +++ b/src/dfs/ipld.ts @@ -86,22 +86,6 @@ export class Ipld extends Logger { private readonly encodingEncrypted = 'hex'; - /** - * remove all cryptoInfos from tree - * - * @param {any} toPurge tree to purge - */ - public static purgeCryptoInfo(toPurge: any): void { - Object.keys(toPurge).forEach((key) => { - if (key === 'cryptoInfo') { - // eslint-disable-next-line no-param-reassign - delete toPurge.cryptoInfo; - } else if (typeof toPurge[key] === 'object' && toPurge[key] !== null) { - this.purgeCryptoInfo(toPurge[key]); - } - }); - } - public constructor(options: IpldOptions) { super(options); this.ipfs = options.ipfs; @@ -184,6 +168,22 @@ export class Ipld extends Logger { }; } + /** + * remove all cryptoInfos from tree + * + * @param {any} toPurge tree to purge + */ + public static purgeCryptoInfo(toPurge: any): void { + Object.keys(toPurge).forEach((key) => { + if (key === 'cryptoInfo') { + // eslint-disable-next-line no-param-reassign + delete toPurge.cryptoInfo; + } else if (typeof toPurge[key] === 'object' && toPurge[key] !== null) { + this.purgeCryptoInfo(toPurge[key]); + } + }); + } + /** * Get a path from a tree; resolve only if required (depends on requested path) * diff --git a/src/did/did.ts b/src/did/did.ts index 97ed7976..36dfbd55 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -80,11 +80,11 @@ export interface DidDocumentTemplate { * interface for services in DIDs */ export interface DidServiceEntry { + [id: string]: any; type: any; serviceEndpoint: any; '@context'?: any; id?: any; - [id: string]: any; } /** diff --git a/src/encryption/aes-blob.ts b/src/encryption/aes-blob.ts index 643d4567..bc736d30 100644 --- a/src/encryption/aes-blob.ts +++ b/src/encryption/aes-blob.ts @@ -54,16 +54,16 @@ export class AesBlob extends Logger implements Cryptor { algorithm: 'aes-blob', }; - private readonly encodingUnencrypted = 'utf-8'; - - private readonly encodingEncrypted = 'hex'; + public algorithm: string; public options: any; - public algorithm: string; - public webCryptoAlgo: string; + private readonly encodingUnencrypted = 'utf-8'; + + private readonly encodingEncrypted = 'hex'; + public constructor(options?: AesBlobOptions) { super(options); this.algorithm = 'aes-256-cbc'; diff --git a/src/keyExchange.ts b/src/keyExchange.ts index 291abbad..e4431624 100644 --- a/src/keyExchange.ts +++ b/src/keyExchange.ts @@ -51,25 +51,25 @@ export interface KeyExchangeOptions extends LoggerOptions { * @class KeyExchange (name) */ export class KeyExchange extends Logger { - private SHARED_SECRET = Buffer.from('a832d7a4c60473d4fcddabf5c31f5b64dcb2382bbebbeb7c49b6cfc2f08fe9c3', 'hex'); - - private diffieHellman: any; + public publicKey: string; private COMM_KEY_CONTEXT = 'mailboxKeyExchange'; - private mailbox: Mailbox; + private SHARED_SECRET = Buffer.from('a832d7a4c60473d4fcddabf5c31f5b64dcb2382bbebbeb7c49b6cfc2f08fe9c3', 'hex'); + + private account: string; + + private aes: Aes; private cryptoProvider: CryptoProvider; private defaultCryptoAlgo: string; - private account: string; + private diffieHellman: any; private keyProvider: KeyProvider; - private aes: Aes; - - public publicKey: string; + private mailbox: Mailbox; /** * Creates an instance of KeyExchange. diff --git a/src/onboarding.ts b/src/onboarding.ts index 4d272e8a..a5238bba 100644 --- a/src/onboarding.ts +++ b/src/onboarding.ts @@ -65,6 +65,11 @@ export interface OnboardingOptions extends LoggerOptions { export class Onboarding extends Logger { public options: OnboardingOptions; + public constructor(optionsInput: OnboardingOptions) { + super(optionsInput); + this.options = optionsInput; + } + /** * creates a new random mnemonic */ @@ -676,11 +681,6 @@ export class Onboarding extends Logger { return 443; } - public constructor(optionsInput: OnboardingOptions) { - super(optionsInput); - this.options = optionsInput; - } - /** * send invitation to another user via smart agent that sends a mail * diff --git a/src/profile/profile.ts b/src/profile/profile.ts index 2ab12c81..b4a7d666 100644 --- a/src/profile/profile.ts +++ b/src/profile/profile.ts @@ -112,11 +112,25 @@ export class Profile extends Logger { }; /** - * All available account types, mapped to it's data contract template specification. Each account + * All available account types, mapped to its data contract template specification. Each account * type is based on the uspecified type, so each type includes this data too. */ public accountTypes = accountTypes; + public constructor(options: ProfileOptions) { + super(options); + this.activeAccount = options.accountId; + this.contractLoader = options.contractLoader; + this.dataContract = options.dataContract; + this.defaultCryptoAlgo = options.defaultCryptoAlgo; + this.executor = options.executor; + this.ipld = options.ipld; + this.nameResolver = options.nameResolver; + this.options = options; + this.profileOwner = options.profileOwner || this.activeAccount; + this.trees = {}; + } + /** * Check if profile data is correct, according to a specific profile type. Throws, when the data * is invalid. @@ -140,20 +154,6 @@ export class Profile extends Logger { return true; } - public constructor(options: ProfileOptions) { - super(options); - this.activeAccount = options.accountId; - this.contractLoader = options.contractLoader; - this.dataContract = options.dataContract; - this.defaultCryptoAlgo = options.defaultCryptoAlgo; - this.executor = options.executor; - this.ipld = options.ipld; - this.nameResolver = options.nameResolver; - this.options = options; - this.profileOwner = options.profileOwner || this.activeAccount; - this.trees = {}; - } - /** * add a contract (task contract etc. ) to a business center scope of the current profile * diff --git a/src/verifications/verifications.ts b/src/verifications/verifications.ts index a1ef968e..d6e62641 100644 --- a/src/verifications/verifications.ts +++ b/src/verifications/verifications.ts @@ -703,33 +703,104 @@ export class Verifications extends Logger { } /** - * Executes a pre-signed verification transaction with given account. - * This account will be the origin of the transaction and not of the verification. - * Second argument is generated with ``signSetVerificationTransaction``. + * run given data (serialized contract transaction (tx)) with given users identity * - * @param {string} accountId account, that submits the transaction - * @param {VerificationsDelegationInfo} txInfo information with verification tx data - * @return {Promise} id of new verification + * @param {string} accountId account with whose identity the given tx is + * executed + * @param {string} data serialized function data + * @param {any} eventInfo (optional) object with properties: 'eventName' and + * 'contract' (web3 contract instance, that triggers + * event) + * @param {Function} getEventResults (optional) function with arguments event and + * eventArgs, that returns result of + * `executeAndHandleEventResult` call + * @return {Promise} if `eventInfo` and `getEventResults`, result of `getEventResults`, + * otherwise void */ - public async executeVerification( + // eslint-disable-next-line consistent-return + public async executeAndHandleEventResult( accountId: string, - txInfo: VerificationsDelegationInfo, + data: string, + eventInfo?: any, + getEventResults?: Function, + sourceIdentity?: string, + value = 0, + to?: string, + signedTransactionInfo?: string, ): Promise { - return this.executeTransaction( - accountId, - txInfo, - { - event: { - target: 'VerificationHolderLibrary', - targetAddress: txInfo.targetIdentity, - eventName: 'VerificationAdded', - contract: this.options.contractLoader.loadContract( - 'VerificationHolderLibrary', txInfo.targetIdentity, - ), - }, - getEventResult: (_, args) => args.verificationId, + // get users identity + const userIdentity = sourceIdentity + ? this.options.contractLoader.loadContract('VerificationHolder', sourceIdentity) + : await this.getIdentityForAccount(accountId); + + // prepare success + result event handling + const options = { + event: { eventName: 'Approved', target: 'KeyHolderLibrary' }, + from: accountId, + getEventResult: (event, eventArgs) => [eventArgs.executionId, event.blockNumber], + value, + }; + + // run tx + const { executor } = this.options; + const [executionId, blockNumber] = await (executor.executeContractTransaction as any)(...[ + userIdentity, + signedTransactionInfo ? 'executeDelegated' : 'execute', + options, + to || this.contracts.registry.options.address, + value, + data, + ].concat(signedTransactionInfo ? [signedTransactionInfo] : [])); + + // fetch result from event + // load user identity as a library, to retrieve library events from users identity + const keyHolderLibrary = this.options.contractLoader.loadContract( + 'KeyHolderLibrary', userIdentity.options.address, + ); + const [executed, failed] = await Promise.all([ + keyHolderLibrary.getPastEvents( + 'Executed', { fromBlock: blockNumber, toBlock: blockNumber }, + ), + keyHolderLibrary.getPastEvents( + 'ExecutionFailed', { fromBlock: blockNumber, toBlock: blockNumber }, + ), + ]); + // flatten and filter events on execution id from identity tx + const filtered = [...executed, ...failed].filter( + (event) => { + if (event.returnValues && event.returnValues.executionId) { + // check if executionId is a BigNumber object + if (event.returnValues.executionId.eq) { + return event.returnValues.executionId.eq(executionId); + } + // otherwise check normal equality + return event.returnValues.executionId === executionId; + } + return false; }, ); + if (filtered.length && filtered[0].event === 'Executed') { + // if execution was successful + if (eventInfo) { + // if original options had an event property for retrieving event results + let targetIdentityEvents = await eventInfo.contract.getPastEvents( + eventInfo.eventName, { fromBlock: blockNumber, toBlock: blockNumber }, + ); + targetIdentityEvents = targetIdentityEvents.filter( + (event) => event.transactionHash === filtered[0].transactionHash, + ); + if (targetIdentityEvents.length) { + return getEventResults(targetIdentityEvents[0], targetIdentityEvents[0].returnValues); + } + } + } else if (filtered.length && filtered[0].event === 'ExecutionFailed') { + const values = filtered[0].returnValues; + throw new Error('executeOnIdentity failed; ExecutionFailed event was triggered: ' + + `executionId: "${values.executionId}", to: "${values.to}", value: "${values.value}"`); + } else { + throw new Error('executeOnIdentity failed; subject type was \'account\', ' + + 'but no proper identity tx status event could be retrieved'); + } } /** @@ -776,33 +847,33 @@ export class Verifications extends Logger { } /** - * Gets an identity's owner's address. This can be either an account or an identity address. + * Executes a pre-signed verification transaction with given account. + * This account will be the origin of the transaction and not of the verification. + * Second argument is generated with ``signSetVerificationTransaction``. * - * @param {string} identityAddress The identity address to fetch the owner for. - * @returns {string} The address of the owner. + * @param {string} accountId account, that submits the transaction + * @param {VerificationsDelegationInfo} txInfo information with verification tx data + * @return {Promise} id of new verification */ - public async getOwnerAddressForIdentity(identityAddress: string): Promise { - await this.ensureStorage(); - let ownerAddress; - if (identityAddress.length === 42) { // 20 bytes address + '0x' prefix - ownerAddress = await this.options.executor.executeContractCall( - this.contracts.storage, - 'owners', - identityAddress, - ); - } else if (identityAddress.length === 66) { // 32 bytes address + '0x' prefix - ownerAddress = await this.options.executor.executeContractCall( - this.contracts.registry, - 'getOwner', - identityAddress, - ); - } - - if (ownerAddress === nullAddress) { - throw Error(`No record found for ${identityAddress}. Is this a valid identity address?`); - } - - return ownerAddress; + public async executeVerification( + accountId: string, + txInfo: VerificationsDelegationInfo, + ): Promise { + return this.executeTransaction( + accountId, + txInfo, + { + event: { + target: 'VerificationHolderLibrary', + targetAddress: txInfo.targetIdentity, + eventName: 'VerificationAdded', + contract: this.options.contractLoader.loadContract( + 'VerificationHolderLibrary', txInfo.targetIdentity, + ), + }, + getEventResult: (_, args) => args.verificationId, + }, + ); } /** @@ -1194,6 +1265,36 @@ export class Verifications extends Logger { return this.formatToV2(nested, queryOptions || this.defaultQueryOptions); } + /** + * Gets an identity's owner's address. This can be either an account or an identity address. + * + * @param {string} identityAddress The identity address to fetch the owner for. + * @returns {string} The address of the owner. + */ + public async getOwnerAddressForIdentity(identityAddress: string): Promise { + await this.ensureStorage(); + let ownerAddress; + if (identityAddress.length === 42) { // 20 bytes address + '0x' prefix + ownerAddress = await this.options.executor.executeContractCall( + this.contracts.storage, + 'owners', + identityAddress, + ); + } else if (identityAddress.length === 66) { // 32 bytes address + '0x' prefix + ownerAddress = await this.options.executor.executeContractCall( + this.contracts.registry, + 'getOwner', + identityAddress, + ); + } + + if (ownerAddress === nullAddress) { + throw Error(`No record found for ${identityAddress}. Is this a valid identity address?`); + } + + return ownerAddress; + } + /** * Builds required data for a transaction from an identity (offchain) and returns data, that can * be used to submit it later on. Return value can be passed to ``executeTransaction``. @@ -1462,8 +1563,7 @@ export class Verifications extends Logger { } /** - * Sets or creates a verification and a Verfiable credential document; - * this requires the issuer to have permissions for the parent + * Sets or creates a verification; this requires the issuer to have permissions for the parent * verification (if verification name seen as a path, the parent 'folder'). * * @param {string} issuer issuer of the verification @@ -1485,9 +1585,9 @@ export class Verifications extends Logger { * identity * @param {string} uri when given this uri will be stored on * the new verification - * @return {Promise<{vcId: string; verificationId: string}>} verificationId and vcId + * @return {Promise} verificationId */ - public async setVerificationAndVc( + public async setVerification( issuer: string, subject: string, topic: string, @@ -1497,62 +1597,59 @@ export class Verifications extends Logger { disableSubVerifications = false, isIdentity = false, uri = '', - ): Promise<{vcId: string; verificationId: string}> { - const verificationId = await this.setVerification( + ): Promise { + await this.ensureStorage(); + + const { + targetIdentity, + subjectType, + uint256VerificationName, + sourceIdentity, + signature, + verificationData, + verificationDataUrl, + ensFullNodeHash, + } = await this.getSetVerificationData( issuer, subject, topic, - expirationDate, verificationValue, descriptionDomain, - disableSubVerifications, isIdentity, uri, ); - const allVerifications = await this.getVerifications( - subject, - topic, - isIdentity, - ); - const [lastVerification] = allVerifications.filter( - (verification) => verification.id === verificationId, - ); - const creationDateNew = new Date(lastVerification.creationDate * 1000).toISOString(); - const issuerIdentity = await this.getIdentityForAccount(issuer, true); - const vcData: VcDocumentTemplate = { - issuer: { - id: await this.options.did.convertIdentityToDid(issuerIdentity), - }, - credentialSubject: { - id: await this.options.did.convertIdentityToDid(subject), - data: - { - value: verificationValue, - }, - uri, - name: lastVerification.name, - }, - validFrom: creationDateNew, // get verification creationdate - }; - if (lastVerification.expirationDate) { - vcData.validUntil = new Date( - lastVerification.expirationdate * 1000, - ).toISOString(); - } - if (descriptionDomain) { - const hash = this.options.nameResolver.soliditySha3(topic); - vcData.credentialSubject.description = `${hash.substr(2)}.${descriptionDomain}.verifications.evan`; - } - if (uri) { - vcData.credentialSubject.description = uri; - } - const docToReturn: VcDocument = await this.options.vc.storeVc(vcData); - return { vcId: docToReturn.id, verificationId: lastVerification.id }; + this.deleteFromVerificationCache(subject, topic); + + // add the verification to the target identity + return this.executeOnIdentity( + targetIdentity, + true, + 'addVerificationWithMetadata', + { + from: issuer, + event: { + target: subjectType === 'contract' + ? 'VerificationsRegistryLibrary' : 'VerificationHolderLibrary', + eventName: 'VerificationAdded', + }, + getEventResult: (_, args) => args.verificationId, + }, + uint256VerificationName, + '1', + sourceIdentity, + signature, + verificationData, + verificationDataUrl, + expirationDate, + ensFullNodeHash, + disableSubVerifications, + ); } /** - * Sets or creates a verification; this requires the issuer to have permissions for the parent + * Sets or creates a verification and a Verfiable credential document; + * this requires the issuer to have permissions for the parent * verification (if verification name seen as a path, the parent 'folder'). * * @param {string} issuer issuer of the verification @@ -1574,9 +1671,9 @@ export class Verifications extends Logger { * identity * @param {string} uri when given this uri will be stored on * the new verification - * @return {Promise} verificationId + * @return {Promise<{vcId: string; verificationId: string}>} verificationId and vcId */ - public async setVerification( + public async setVerificationAndVc( issuer: string, subject: string, topic: string, @@ -1586,54 +1683,58 @@ export class Verifications extends Logger { disableSubVerifications = false, isIdentity = false, uri = '', - ): Promise { - await this.ensureStorage(); - - const { - targetIdentity, - subjectType, - uint256VerificationName, - sourceIdentity, - signature, - verificationData, - verificationDataUrl, - ensFullNodeHash, - } = await this.getSetVerificationData( + ): Promise<{vcId: string; verificationId: string}> { + const verificationId = await this.setVerification( issuer, subject, topic, + expirationDate, verificationValue, descriptionDomain, + disableSubVerifications, isIdentity, uri, ); - - this.deleteFromVerificationCache(subject, topic); - - // add the verification to the target identity - return this.executeOnIdentity( - targetIdentity, - true, - 'addVerificationWithMetadata', - { - from: issuer, - event: { - target: subjectType === 'contract' - ? 'VerificationsRegistryLibrary' : 'VerificationHolderLibrary', - eventName: 'VerificationAdded', - }, - getEventResult: (_, args) => args.verificationId, - }, - uint256VerificationName, - '1', - sourceIdentity, - signature, - verificationData, - verificationDataUrl, - expirationDate, - ensFullNodeHash, - disableSubVerifications, + const allVerifications = await this.getVerifications( + subject, + topic, + isIdentity, ); + const [lastVerification] = allVerifications.filter( + (verification) => verification.id === verificationId, + ); + const creationDateNew = new Date(lastVerification.creationDate * 1000).toISOString(); + const issuerIdentity = await this.getIdentityForAccount(issuer, true); + const vcData: VcDocumentTemplate = { + issuer: { + id: await this.options.did.convertIdentityToDid(issuerIdentity), + }, + credentialSubject: { + id: await this.options.did.convertIdentityToDid(subject), + data: + { + value: verificationValue, + }, + uri, + name: lastVerification.name, + }, + validFrom: creationDateNew, // get verification creationdate + + }; + if (lastVerification.expirationDate) { + vcData.validUntil = new Date( + lastVerification.expirationdate * 1000, + ).toISOString(); + } + if (descriptionDomain) { + const hash = this.options.nameResolver.soliditySha3(topic); + vcData.credentialSubject.description = `${hash.substr(2)}.${descriptionDomain}.verifications.evan`; + } + if (uri) { + vcData.credentialSubject.description = uri; + } + const docToReturn: VcDocument = await this.options.vc.storeVc(vcData); + return { vcId: docToReturn.id, verificationId: lastVerification.id }; } /** @@ -1662,56 +1763,6 @@ export class Verifications extends Logger { this.deleteFromVerificationCache('*', topic); } - /** - * Signs a transaction from an identity (offchain) and returns data, that can be used to submit it - * later on. Return value can be passed to ``executeTransaction``. - * - * Note that, when creating multiple signed transactions, the ``nonce`` argument **has to be - * specified and incremented between calls**, as the nonce is included in transaction data and - * restricts the order of transactions, that can be made. - * - * @param {any} contract target contract of transaction or ``null`` if just sending - * funds - * @param {string} functionName function for transaction or ``null`` if just sending funds - * @param {any} options options for transaction, supports from, to, nonce, input, - * value - * @param {any[]} args arguments for function transaction - * @return {VerificationsDelegationInfo} prepared transaction for ``executeTransaction`` - */ - public async signTransaction( - contract: any = null, - functionName: string = null, - options: any, - ...args - ): Promise { - const { - sourceIdentity, - to, - value, - input, - nonce, - } = await this.getTransactionInfo(contract, functionName, options, ...args); - - // note that issuer is given for signing, as this ACCOUNT is used to sign the message - // try to sign with underlying account for active identity, as they are related - // otherwise try to use given account - const signedTransactionInfo = await this.signPackedHash( - options.from === this.config.activeIdentity - ? this.config.underlyingAccount - : options.from, - [sourceIdentity, nonce, to, value, input], - ); - - return { - sourceIdentity, - to, - value, - input, - signedTransactionInfo, - nonce, - }; - } - /** * Signs a verification (off-chain) and returns data, that can be used to submit it later on. * Return value can be passed to ``executeVerification``. @@ -1806,6 +1857,56 @@ export class Verifications extends Logger { }; } + /** + * Signs a transaction from an identity (offchain) and returns data, that can be used to submit it + * later on. Return value can be passed to ``executeTransaction``. + * + * Note that, when creating multiple signed transactions, the ``nonce`` argument **has to be + * specified and incremented between calls**, as the nonce is included in transaction data and + * restricts the order of transactions, that can be made. + * + * @param {any} contract target contract of transaction or ``null`` if just sending + * funds + * @param {string} functionName function for transaction or ``null`` if just sending funds + * @param {any} options options for transaction, supports from, to, nonce, input, + * value + * @param {any[]} args arguments for function transaction + * @return {VerificationsDelegationInfo} prepared transaction for ``executeTransaction`` + */ + public async signTransaction( + contract: any = null, + functionName: string = null, + options: any, + ...args + ): Promise { + const { + sourceIdentity, + to, + value, + input, + nonce, + } = await this.getTransactionInfo(contract, functionName, options, ...args); + + // note that issuer is given for signing, as this ACCOUNT is used to sign the message + // try to sign with underlying account for active identity, as they are related + // otherwise try to use given account + const signedTransactionInfo = await this.signPackedHash( + options.from === this.config.activeIdentity + ? this.config.underlyingAccount + : options.from, + [sourceIdentity, nonce, to, value, input], + ); + + return { + sourceIdentity, + to, + value, + input, + signedTransactionInfo, + nonce, + }; + } + /** * Trim ``VerificationsResultV2`` result down to statusFlags and status values for analysis * purposes and debugging. @@ -2065,107 +2166,6 @@ export class Verifications extends Logger { } } - /** - * run given data (serialized contract transaction (tx)) with given users identity - * - * @param {string} accountId account with whose identity the given tx is - * executed - * @param {string} data serialized function data - * @param {any} eventInfo (optional) object with properties: 'eventName' and - * 'contract' (web3 contract instance, that triggers - * event) - * @param {Function} getEventResults (optional) function with arguments event and - * eventArgs, that returns result of - * `executeAndHandleEventResult` call - * @return {Promise} if `eventInfo` and `getEventResults`, result of `getEventResults`, - * otherwise void - */ - // eslint-disable-next-line consistent-return - public async executeAndHandleEventResult( - accountId: string, - data: string, - eventInfo?: any, - getEventResults?: Function, - sourceIdentity?: string, - value = 0, - to?: string, - signedTransactionInfo?: string, - ): Promise { - // get users identity - const userIdentity = sourceIdentity - ? this.options.contractLoader.loadContract('VerificationHolder', sourceIdentity) - : await this.getIdentityForAccount(accountId); - - // prepare success + result event handling - const options = { - event: { eventName: 'Approved', target: 'KeyHolderLibrary' }, - from: accountId, - getEventResult: (event, eventArgs) => [eventArgs.executionId, event.blockNumber], - value, - }; - - // run tx - const { executor } = this.options; - const [executionId, blockNumber] = await (executor.executeContractTransaction as any)(...[ - userIdentity, - signedTransactionInfo ? 'executeDelegated' : 'execute', - options, - to || this.contracts.registry.options.address, - value, - data, - ].concat(signedTransactionInfo ? [signedTransactionInfo] : [])); - - // fetch result from event - // load user identity as a library, to retrieve library events from users identity - const keyHolderLibrary = this.options.contractLoader.loadContract( - 'KeyHolderLibrary', userIdentity.options.address, - ); - const [executed, failed] = await Promise.all([ - keyHolderLibrary.getPastEvents( - 'Executed', { fromBlock: blockNumber, toBlock: blockNumber }, - ), - keyHolderLibrary.getPastEvents( - 'ExecutionFailed', { fromBlock: blockNumber, toBlock: blockNumber }, - ), - ]); - // flatten and filter events on execution id from identity tx - const filtered = [...executed, ...failed].filter( - (event) => { - if (event.returnValues && event.returnValues.executionId) { - // check if executionId is a BigNumber object - if (event.returnValues.executionId.eq) { - return event.returnValues.executionId.eq(executionId); - } - // otherwise check normal equality - return event.returnValues.executionId === executionId; - } - return false; - }, - ); - if (filtered.length && filtered[0].event === 'Executed') { - // if execution was successful - if (eventInfo) { - // if original options had an event property for retrieving event results - let targetIdentityEvents = await eventInfo.contract.getPastEvents( - eventInfo.eventName, { fromBlock: blockNumber, toBlock: blockNumber }, - ); - targetIdentityEvents = targetIdentityEvents.filter( - (event) => event.transactionHash === filtered[0].transactionHash, - ); - if (targetIdentityEvents.length) { - return getEventResults(targetIdentityEvents[0], targetIdentityEvents[0].returnValues); - } - } - } else if (filtered.length && filtered[0].event === 'ExecutionFailed') { - const values = filtered[0].returnValues; - throw new Error('executeOnIdentity failed; ExecutionFailed event was triggered: ' - + `executionId: "${values.executionId}", to: "${values.to}", value: "${values.value}"`); - } else { - throw new Error('executeOnIdentity failed; subject type was \'account\', ' - + 'but no proper identity tx status event could be retrieved'); - } - } - /** * execute contract transaction on identity, checks if account or contract identity is used and if * given subject is already an identity From 7296278a4f005c31376e92cde03511f341b06eb4 Mon Sep 17 00:00:00 2001 From: wulfraem Date: Mon, 10 Feb 2020 12:02:36 +0100 Subject: [PATCH 018/104] update versions info - [CORE-921] --- VERSIONS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/VERSIONS.md b/VERSIONS.md index 98686128..b7c1b8f1 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -4,6 +4,7 @@ ### Features ### Fixes +- fix buffer to-string conversion, try to decode to `utf8`, if this fails, decode it to `binary` ### Deprecations From 7a93711ddc5b0ff60b1085811a718bf6da1a49c7 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Mon, 10 Feb 2020 14:57:32 +0100 Subject: [PATCH 019/104] Added did deactivation functionality [CORE-947] --- .../digital-twin/digital-twin.spec.ts | 26 ++--- src/contracts/digital-twin/digital-twin.ts | 2 +- src/did/did.spec.ts | 97 +++++++++++++++++++ src/did/did.ts | 57 +++++++---- 4 files changed, 142 insertions(+), 40 deletions(-) diff --git a/src/contracts/digital-twin/digital-twin.spec.ts b/src/contracts/digital-twin/digital-twin.spec.ts index f90f1190..0a376bef 100644 --- a/src/contracts/digital-twin/digital-twin.spec.ts +++ b/src/contracts/digital-twin/digital-twin.spec.ts @@ -385,21 +385,14 @@ describe('DigitalTwin', function test() { expect(twinAuthority).to.equal(nullAddress); expect(twinDescriptionHash).to.equal(nullBytes32); - // Check if did registry points to 0x0 - // Directly access the registry to be independent of DID api implementation + // Check if did has been deactivated const did = await TestUtils.getDid( localRuntime.web3, localRuntime.activeAccount, localRuntime.dfs, ); - const didContract = await (did as any).getRegistryContract(); - const didDocumentHash = await runtime.executor.executeContractCall( - didContract, - 'didDocuments', - (did as any).padIdentity(twinIdentity), - ); - - expect(didDocumentHash).to.eq(nullBytes32); + const twinDid = await did.convertIdentityToDid(twinIdentity); + expect(did.didIsDeactivated(twinDid)).to.eventually.be.true; // Check if all pinned hashes have been unpinned const pinnedHashesAfterDeactivation = await getPinnedFileHashes(); @@ -494,21 +487,14 @@ describe('DigitalTwin', function test() { expect(twinAuthority).to.equal(nullAddress); expect(twinDescriptionHash).to.equal(nullBytes32); - // Check if did registry points to 0x0 - // Directly access the registry to be independent of DID api implementation + // Check if did has been deactivated const did = await TestUtils.getDid( localRuntime.web3, localRuntime.activeAccount, localRuntime.dfs, ); - const didContract = await (did as any).getRegistryContract(); - const didDocumentHash = await runtime.executor.executeContractCall( - didContract, - 'didDocuments', - (did as any).padIdentity(twinIdentity), - ); - - expect(didDocumentHash).to.eq(nullBytes32); + const twinDid = await did.convertIdentityToDid(twinIdentity); + expect(did.didIsDeactivated(twinDid)).to.eventually.be.true; }); }); diff --git a/src/contracts/digital-twin/digital-twin.ts b/src/contracts/digital-twin/digital-twin.ts index 31289cc7..9b4245dc 100644 --- a/src/contracts/digital-twin/digital-twin.ts +++ b/src/contracts/digital-twin/digital-twin.ts @@ -461,7 +461,7 @@ export class DigitalTwin extends Logger { // Unset did const twinDid = await this.options.did.convertIdentityToDid(description.identity); - await this.options.did.removeDidDocument(twinDid); + await this.options.did.deactivateDidDocument(twinDid); // Deactivate identity if (this.options.verifications.contracts.registry) { diff --git a/src/did/did.spec.ts b/src/did/did.spec.ts index 56a12cc9..12fdbe12 100644 --- a/src/did/did.spec.ts +++ b/src/did/did.spec.ts @@ -279,6 +279,103 @@ describe('DID Resolver', function test() { const defaultDidDoc = await runtimes[0].did.getDidDocument(twinDid); await expect(defaultDidDoc).to.deep.eq(expectedDefaultDid); }); + + it('allows to deactivate a DID', async () => { + const twin = await DigitalTwin.create( + runtimes[0] as DigitalTwinOptions, + { + accountId: runtimes[0].activeAccount, + containerConfig: null, + description: twinDescription, + }, + ); + + const twinIdentity = await runtimes[0].verifications.getIdentityForAccount( + await twin.getContractAddress(), + true, + ); + const twinDid = await runtimes[0].did.convertIdentityToDid(twinIdentity); + await runtimes[0].did.deactivateDidDocument(twinDid); + + const deactivated = await runtimes[0].did.didIsDeactivated(twinDid); + expect(deactivated).to.be.true; + }); + + it('does not allow to deactivate someone else\'s DID', async () => { + const twin = await DigitalTwin.create( + runtimes[1] as DigitalTwinOptions, + { + accountId: runtimes[1].activeAccount, + containerConfig: null, + description: twinDescription, + }, + ); + + const twinIdentity = await runtimes[0].verifications.getIdentityForAccount( + await twin.getContractAddress(), + true, + ); + const twinDid = await runtimes[0].did.convertIdentityToDid(twinIdentity); + expect( + runtimes[0].did.deactivateDidDocument(twinDid), + ).to.eventually.be.rejectedWith('Deactivation failed'); + }); + + it('does not allow to deactivate a DID twice', async () => { + const twin = await DigitalTwin.create( + runtimes[0] as DigitalTwinOptions, + { + accountId: runtimes[0].activeAccount, + containerConfig: null, + description: twinDescription, + }, + ); + + const twinIdentity = await runtimes[0].verifications.getIdentityForAccount( + await twin.getContractAddress(), + true, + ); + const twinDid = await runtimes[0].did.convertIdentityToDid(twinIdentity); + await runtimes[0].did.deactivateDidDocument(twinDid); + expect( + runtimes[0].did.deactivateDidDocument(twinDid), + ).to.eventually.be.rejectedWith('Deactivation failed'); + }); + + it('does not allow to set a DID after deactivating it', async () => { + const twin = await DigitalTwin.create( + runtimes[0] as DigitalTwinOptions, + { + accountId: runtimes[0].activeAccount, + containerConfig: null, + description: twinDescription, + }, + ); + + const twinIdentity = await runtimes[0].verifications.getIdentityForAccount( + await twin.getContractAddress(), + true, + ); + + const twinDid = await runtimes[0].did.convertIdentityToDid(twinIdentity); + await runtimes[0].did.deactivateDidDocument(twinDid); + const dummyDocument = await runtimes[0].did.getDidDocumentTemplate(); + + const promise = runtimes[0].did.setDidDocument(twinDid, dummyDocument); + expect(promise).to.eventually.be.rejectedWith('Cannot set document for deactivated DID'); + + // Also check on smart contract level + const didRegistryContract = await (runtimes[0].did as any).getRegistryContract(); + + const contractPromise = runtimes[0].executor.executeContractTransaction( + didRegistryContract, + 'setDidDocument', + { from: runtimes[0].activeIdentity }, + twinIdentity, + TestUtils.getRandomBytes32(), + ); + expect(contractPromise).to.eventually.be.rejected; + }); }); describe('when storing did documents for alias identities', () => { diff --git a/src/did/did.ts b/src/did/did.ts index 5e564aa0..0486bf83 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -139,6 +139,25 @@ export class Did extends Logger { return `did:evan:${await this.getDidInfix()}${identity}`; } + /** + * Gets the deactivation status of a DID + * + * @param did DID to check + * @returns {boolean} true, if the DID is deactivated + */ + public async didIsDeactivated(did: string): Promise { + let identity = await this.convertDidToIdentity(did); + identity = this.padIdentity(identity); + + const isDeactivated = await this.options.executor.executeContractCall( + await this.getRegistryContract(), + 'deactivatedDids', + identity, + ); + + return isDeactivated; + } + /** * Get DID document for given DID. * @@ -148,22 +167,16 @@ export class Did extends Logger { */ public async getDidDocument(did: string): Promise { let result = null; + if (await this.didIsDeactivated(did)) { + throw Error(`DID ${did} has been deactivated.`); + } + const identity = this.padIdentity( did ? await this.convertDidToIdentity(did) : this.options.signerIdentity.activeIdentity, ); - const isDeactivated = await this.options.executor.executeContractCall( - await this.getRegistryContract(), - 'deactivatedDids', - identity, - ); - - if (isDeactivated) { - throw Error(`DID ${did} has been deactivated.`); - } - const documentHash = await this.options.executor.executeContractCall( await this.getRegistryContract(), 'didDocuments', @@ -252,6 +265,10 @@ export class Did extends Logger { * @return {Promise} resolved when done */ public async setDidDocument(did: string, document: any): Promise { + if (await this.didIsDeactivated(did)) { + throw Error('Cannot set document for deactivated DID'); + } + const identity = this.padIdentity(did ? await this.convertDidToIdentity(did) : this.options.signerIdentity.activeIdentity); @@ -314,18 +331,20 @@ export class Did extends Logger { * Unlinks the current DID document from the DID * @param did DID to unlink the DID document from */ - public async removeDidDocument(did: string): Promise { + public async deactivateDidDocument(did: string): Promise { const identity = this.padIdentity(did ? await this.convertDidToIdentity(did) : this.options.signerIdentity.activeIdentity); - - await this.options.executor.executeContractTransaction( - await this.getRegistryContract(), - 'setDidDocument', - { from: this.options.signerIdentity.activeIdentity }, - identity, - nullBytes32, - ); + try { + await this.options.executor.executeContractTransaction( + await this.getRegistryContract(), + 'deactivateDid', + { from: this.options.signerIdentity.activeIdentity }, + identity, + ); + } catch (e) { + throw Error('Deactivation failed. Is the DID active and do you have permission to deactivate the DID?'); + } } /** From 9e1b5c55f7578dea6a024bf9d310ab45f835ca4c Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Mon, 10 Feb 2020 14:58:06 +0100 Subject: [PATCH 020/104] Ordered did documentation alphabetically --- docs/profile/did.rst | 100 +++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/docs/profile/did.rst b/docs/profile/did.rst index 81b689f5..52521c24 100644 --- a/docs/profile/did.rst +++ b/docs/profile/did.rst @@ -47,6 +47,8 @@ Parameters * ``logLevel`` - |source logLevel|_ (optional): messages with this level will be logged with ``log`` * ``logLog`` - |source logLogInterface|_ (optional): container for collecting log messages * ``logLogLevel`` - |source logLevel|_ (optional): messages with this level will be pushed to ``logLog`` +#. ``config`` - ``DidConfig`` (optional): description, defaults to ``123`` + * ``registryAddress`` - ``string`` (optional): contract address or ENS name for `DidRegistry` ------- Returns @@ -76,29 +78,28 @@ Example = Working with DID documents = ============================== -.. _did_setDidDocument: +.. _did_getDidDocument: -setDidDocument +getDidDocument ================================================================================ .. code-block:: typescript - did.setDidDocument(did, document); + did.getDidDocument([did]); -Store given DID document for given DID. +Get DID document for given DID. ---------- Parameters ---------- -#. ``did`` - ``string``: DID to store DID document for -#. ``document`` - ``any``: DID document to store, ``getDidDocumentTemplate`` can be used as a starting point for DID documents +#. ``did`` - ``string``: DID to fetch DID document for. ------- Returns ------- -``Promise`` returns ``void``: resolved when done +``Promise`` returns ``any``: a DID document that MAY resemble `DidDocumentTemplate` format ------- Example @@ -110,33 +111,33 @@ Example const did = await runtime.did.convertIdentityToDid(identity); const document = await runtime.did.getDidDocumentTemplate(); await runtime.did.setDidDocument(did, document); - + const retrieved = await runtime.did.getDidDocument(did); -------------------------------------------------------------------------------- -.. _did_getDidDocument: +.. _did_getService: -getDidDocument +getService ================================================================================ .. code-block:: typescript - did.getDidDocument([did]); + did.getService([did]); -Get DID document for given DID. +Get service from DID document. ---------- Parameters ---------- -#. ``did`` - ``string``: DID to fetch DID document for. +#. ``did`` - ``string``: DID to fetch DID service for. ------- Returns ------- -``Promise`` returns ``any``: a DID document that MAY resemble `DidDocumentTemplate` format +``Promise`` returns ``DidServiceEntry[] | DidServiceEntry``: service ------- Example @@ -144,33 +145,35 @@ Example .. code-block:: typescript - const identity = await runtime.verifications.getIdentityForAccount(accountsId, true); - const did = await runtime.did.convertIdentityToDid(identity); const document = await runtime.did.getDidDocumentTemplate(); + const identity = await runtime.verifications.getIdentityForAccount(account, true); + const did = await runtime.did.convertIdentityToDid(identity); await runtime.did.setDidDocument(did, document); - const retrieved = await runtime.did.getDidDocument(did); - - - --------------------------------------------------------------------------------- + const service = [{ + id: `${did}#randomService`, + type: `randomService-${random}`, + serviceEndpoint: `https://openid.example.com/${random}`, + }]; + await runtime.did.setService(did, service); + const retrieved = await runtime.did.getService(did); -.. _did_setService: +.. _did_setDidDocument: -setService +setDidDocument ================================================================================ .. code-block:: typescript - did.setService(service[, did]); + did.setDidDocument(did, document); -Sets service in DID document. +Store given DID document for given DID. ---------- Parameters ---------- -#. ``did`` - ``string``: DID name to set service for -#. ``service`` - ``DidServiceEntry[] | DidServiceEntry``: service to set +#. ``did`` - ``string``: DID to store DID document for +#. ``document`` - ``any``: DID document to store, ``getDidDocumentTemplate`` can be used as a starting point for DID documents ------- Returns @@ -184,43 +187,36 @@ Example .. code-block:: typescript - const document = await runtime.did.getDidDocumentTemplate(); - const identity = await runtime.verifications.getIdentityForAccount(account, true); + const identity = await runtime.verifications.getIdentityForAccount(accountsId, true); const did = await runtime.did.convertIdentityToDid(identity); + const document = await runtime.did.getDidDocumentTemplate(); await runtime.did.setDidDocument(did, document); - const service = [{ - id: `${did}#randomService`, - type: `randomService-${random}`, - serviceEndpoint: `https://openid.example.com/${random}`, - }]; - await runtime.did.setService(did, service); - - -------------------------------------------------------------------------------- -.. _did_getService: +.. _did_setService: -getService +setService ================================================================================ .. code-block:: typescript - did.getService([did]); + did.setService(service[, did]); -Get service from DID document. +Sets service in DID document. ---------- Parameters ---------- -#. ``did`` - ``string``: DID to fetch DID service for. +#. ``did`` - ``string``: DID name to set service for +#. ``service`` - ``DidServiceEntry[] | DidServiceEntry``: service to set ------- Returns ------- -``Promise`` returns ``DidServiceEntry[] | DidServiceEntry``: service +``Promise`` returns ``void``: resolved when done ------- Example @@ -238,7 +234,6 @@ Example serviceEndpoint: `https://openid.example.com/${random}`, }]; await runtime.did.setService(did, service); - const retrieved = await runtime.did.getService(did); @@ -321,7 +316,7 @@ Example // Output: // did:evan:testcore:0x000000000000000000000000000000000000001234 - +deactivateDidDocument -------------------------------------------------------------------------------- @@ -372,11 +367,9 @@ Example // "publicKey": [ // { // "id": "did:evan:testcore:0x126E901F6F408f5E260d95c62E7c73D9B60fd734#key-1", - // "type": [ - // "Secp256k1SignatureVerificationKey2018", - // "ERC725ManagementKey" - // ], - // "publicKeyHex": "045adfd502c0bc55f4fcb90eea36368d7e19c5b3045aa6f51dfa3699046e9751251d21bc6bdd06c1ff0014fcbbf9f1d83c714434f2b33d713aaf46760f2d53f10d" + // "type": "Secp256k1VerificationKey2018", + // "owner": "did:evan:testcore:0x126E901F6F408f5E260d95c62E7c73D9B60fd734", + // "ethereumAddress": "0x126E901F6F408f5E260d95c62E7c73D9B60fd734" // } // ], // "authentication": [ @@ -411,3 +404,10 @@ Example .. |source web3| replace:: ``Web3`` .. _source web3: https://github.com/ethereum/web3.js/ +.. _source nameResolver: ../blockchain/name-resolver.html + +.. |source signerIdentity| replace:: ``SignerIdentity`` +.. _source signerIdentity: ../blockchain/signer-identity.html + +.. |source web3| replace:: ``Web3`` +.. _source web3: https://github.com/ethereum/web3.js/ From 4f548f8a6fa7f8b1ac6380d61e428aa891438984 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Mon, 10 Feb 2020 15:40:45 +0100 Subject: [PATCH 021/104] Added did deactivation docs [CORE-947] --- docs/profile/did.rst | 84 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 3 deletions(-) diff --git a/docs/profile/did.rst b/docs/profile/did.rst index 52521c24..177012f6 100644 --- a/docs/profile/did.rst +++ b/docs/profile/did.rst @@ -78,6 +78,82 @@ Example = Working with DID documents = ============================== +.. _did_deactivateDidDocument: + +deactivateDidDocument +================================================================================ + +.. code-block:: typescript + + did.deactivateDidDocument(did); + +Unlinks the current DID document from the given DID + +---------- +Parameters +---------- + +#. ``did`` - ``string``: DID to unlink the DID document from + +------- +Returns +------- + +``Promise`` returns ``void``: Resolves when done + +------- +Example +------- + +.. code-block:: typescript + + const twinIdentity = '0x1234512345123451234512345123451234512345'; + const twinDid = await runtime.did.convertIdentityToDid(twinIdentity); + await runtime.did.deactivateDidDocument(twinDid); + + +-------------------------------------------------------------------------------- + + +.. _did_didIsDeactivated: + +didIsDeactivated +================================================================================ + +.. code-block:: typescript + + did.didIsDeactivated(did); + +Gets the deactivation status of a DID. + +---------- +Parameters +---------- + +#. ``did`` - ``string``: DID to check + +------- +Returns +------- + +``Promise`` returns ``boolean``: True if the DID has been deactivated + +------- +Example +------- + +.. code-block:: typescript + + const twinIdentity = '0x1234512345123451234512345123451234512345'; + const twinDid = await runtime.did.convertIdentityToDid(twinIdentity); + await runtime.did.deactivateDidDocument(twinDid); + console.log(await runtime.did.didIsDeactivated(twinDid)); + // Output: true + + +-------------------------------------------------------------------------------- + + .. _did_getDidDocument: getDidDocument @@ -85,7 +161,7 @@ getDidDocument .. code-block:: typescript - did.getDidDocument([did]); + did.getDidDocument(did); Get DID document for given DID. @@ -123,7 +199,7 @@ getService .. code-block:: typescript - did.getService([did]); + did.getService(did); Get service from DID document. @@ -157,6 +233,8 @@ Example await runtime.did.setService(did, service); const retrieved = await runtime.did.getService(did); +-------------------------------------------------------------------------------- + .. _did_setDidDocument: setDidDocument @@ -201,7 +279,7 @@ setService .. code-block:: typescript - did.setService(service[, did]); + did.setService(did, [service]); Sets service in DID document. From 3fc2474fe2b5264bb0a5fe8c451875ff032f8207 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Mon, 10 Feb 2020 16:05:02 +0100 Subject: [PATCH 022/104] Return standard did document for deactivated DIDs [CORE-947] --- src/did/did.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/did/did.ts b/src/did/did.ts index 0486bf83..7efee8b8 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -168,7 +168,7 @@ export class Did extends Logger { public async getDidDocument(did: string): Promise { let result = null; if (await this.didIsDeactivated(did)) { - throw Error(`DID ${did} has been deactivated.`); + return this.getDeactivatedDidDocument(did); } const identity = this.padIdentity( @@ -358,6 +358,19 @@ export class Did extends Logger { await this.validateDidAndGetSections(did); } + /** + * Returns the standard DID document for deactivated DIDs + */ + private async getDeactivatedDidDocument(did: string): Promise { + return JSON.parse(`{ + "@context": "https://w3id.org/did/v1", + "id": "${did}", + "publicKey": [], + "authentication": [], + "services": [] + }`); + } + /** * Retrieve a default DID document for identities that do not have a document associated yet. * @param did DID to fetch a document for. From 90ba1fe141e87c14c60c59a7a311ec61ca6b33de Mon Sep 17 00:00:00 2001 From: wulfraem Date: Tue, 11 Feb 2020 08:04:33 +0100 Subject: [PATCH 023/104] Update src/dfs/ipfs.spec.ts Co-Authored-By: Philip Kaiser --- src/dfs/ipfs.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dfs/ipfs.spec.ts b/src/dfs/ipfs.spec.ts index b9863a24..726ea0b3 100644 --- a/src/dfs/ipfs.spec.ts +++ b/src/dfs/ipfs.spec.ts @@ -129,7 +129,7 @@ describe('IPFS handler', function test() { expect(fileContent.toString(encoding)).to.eq(content); }); - it('should be not be able to add a file with extended unicodes (properly), that have been encoded as binary', async () => { + it('should not be able to add a file with extended unicodes (properly), that have been encoded as binary', async () => { const content = '🌂'; const encoding = 'binary'; const broken = Buffer.from('🌂', encoding).toString(encoding); From a6a190fda5bf0fc636e78a980fc02f9437a7f56d Mon Sep 17 00:00:00 2001 From: wulfraem Date: Tue, 11 Feb 2020 08:18:12 +0100 Subject: [PATCH 024/104] Update VERSIONS.md Co-Authored-By: Sebastian Dechant --- VERSIONS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSIONS.md b/VERSIONS.md index b7c1b8f1..909d9026 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -4,7 +4,7 @@ ### Features ### Fixes -- fix buffer to-string conversion, try to decode to `utf8`, if this fails, decode it to `binary` +- fix buffer-to-string conversion, try to decode to `utf8`, if this fails, decode it to `binary` ### Deprecations From 540e60710271f66116ebdb642fe9c1412f446345 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Tue, 11 Feb 2020 08:46:29 +0100 Subject: [PATCH 025/104] Improved method description --- src/did/did.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/did/did.ts b/src/did/did.ts index 7efee8b8..364964aa 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -162,8 +162,9 @@ export class Did extends Logger { * Get DID document for given DID. * * @param {string} did DID to fetch DID document for - * @return {Promise} a DID document that MAY resemble `DidDocumentTemplate` format - * @throws if a DID has been deactivated or if no DID document has been set yet + * @return {Promise} a DID document that MAY resemble `DidDocumentTemplate` format. + * For deactiated DIDs it returns a default DID document containing + * no authentication mateiral. */ public async getDidDocument(did: string): Promise { let result = null; From ba181a35cf2b0da7ba53b7ab02e2502968b0a332 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Tue, 11 Feb 2020 08:53:24 +0100 Subject: [PATCH 026/104] Minor doc refactoring [CORE-947] --- docs/profile/did.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/profile/did.rst b/docs/profile/did.rst index 177012f6..3d816c1d 100644 --- a/docs/profile/did.rst +++ b/docs/profile/did.rst @@ -201,7 +201,7 @@ getService did.getService(did); -Get service from DID document. +Get the services from a DID document. ---------- Parameters @@ -213,7 +213,7 @@ Parameters Returns ------- -``Promise`` returns ``DidServiceEntry[] | DidServiceEntry``: service +``Promise`` returns ``DidServiceEntry[] | DidServiceEntry``: Array of services, or a single service entry object. ------- Example @@ -279,7 +279,7 @@ setService .. code-block:: typescript - did.setService(did, [service]); + did.setService(did, service); Sets service in DID document. @@ -288,7 +288,7 @@ Parameters ---------- #. ``did`` - ``string``: DID name to set service for -#. ``service`` - ``DidServiceEntry[] | DidServiceEntry``: service to set +#. ``service`` - ``DidServiceEntry[] | DidServiceEntry``: service or array of services to set ------- Returns From d71d06139949ae4e9fdafa154953418871d5da86 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Tue, 11 Feb 2020 09:00:20 +0100 Subject: [PATCH 027/104] Fix --- docs/profile/did.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/profile/did.rst b/docs/profile/did.rst index 3d816c1d..d6bd19e1 100644 --- a/docs/profile/did.rst +++ b/docs/profile/did.rst @@ -394,7 +394,6 @@ Example // Output: // did:evan:testcore:0x000000000000000000000000000000000000001234 -deactivateDidDocument -------------------------------------------------------------------------------- From d077760914bc0ebd43c8e9e24d160f9cd571a713 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Tue, 11 Feb 2020 09:06:39 +0100 Subject: [PATCH 028/104] Added to VERSIONS.md --- VERSIONS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/VERSIONS.md b/VERSIONS.md index 7db9f823..b925a7a2 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -10,6 +10,7 @@ - ensure `DigitalTwin` and `Container` description to use at least `dbcpVersion` 2 - update root verification handling to use genesis account - add lookup method for an identity's owner's address +- add methods `deactivateDidDocument` and `didIsDeactivated` to check and handle DID deactivation status ### Fixes - use typescript version `3.7.4` From aa6c5d1d13006cb14c4ee9a9e896f61203704040 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Wed, 12 Feb 2020 11:29:05 +0100 Subject: [PATCH 029/104] Adding DID properties upon setting did [CORE-946] --- src/contracts/digital-twin/container.spec.ts | 7 ++- .../digital-twin/digital-twin.spec.ts | 6 +-- src/did/did.spec.ts | 2 +- src/did/did.ts | 51 +++++++++++++++---- src/vc/vc.ts | 8 +-- 5 files changed, 51 insertions(+), 23 deletions(-) diff --git a/src/contracts/digital-twin/container.spec.ts b/src/contracts/digital-twin/container.spec.ts index de19372c..2566c23e 100644 --- a/src/contracts/digital-twin/container.spec.ts +++ b/src/contracts/digital-twin/container.spec.ts @@ -22,10 +22,9 @@ import * as chaiAsPromised from 'chai-as-promised'; import { expect, use } from 'chai'; import { promisify } from 'util'; import { readFile } from 'fs'; -import { - Executor, - Ipfs, -} from '@evan.network/dbcp'; +import { Executor } from '../..'; +import { Ipfs } from '../../dfs/ipfs'; + import { accounts } from '../../test/accounts'; import { TestUtils } from '../../test/test-utils'; diff --git a/src/contracts/digital-twin/digital-twin.spec.ts b/src/contracts/digital-twin/digital-twin.spec.ts index 5b8d071e..8842a936 100644 --- a/src/contracts/digital-twin/digital-twin.spec.ts +++ b/src/contracts/digital-twin/digital-twin.spec.ts @@ -20,10 +20,7 @@ import 'mocha'; import * as chaiAsPromised from 'chai-as-promised'; import { expect, use } from 'chai'; -import { - Executor, - Ipfs, -} from '@evan.network/dbcp'; +import { Ipfs } from '../../dfs/ipfs'; import { accounts } from '../../test/accounts'; import { configTestcore as config } from '../../config-testcore'; @@ -38,6 +35,7 @@ import { DigitalTwinOptions, DigitalTwinVerificationEntry, } from './digital-twin'; +import { Executor } from '../..'; use(chaiAsPromised); diff --git a/src/did/did.spec.ts b/src/did/did.spec.ts index 56a12cc9..c35df690 100644 --- a/src/did/did.spec.ts +++ b/src/did/did.spec.ts @@ -50,7 +50,7 @@ describe('DID Resolver', function test() { }); describe('when storing did documents for account identities', async () => { - it('allows to store a DID document for the own identity', async () => { + it.only('allows to store a DID document for the own identity', async () => { const document = await runtimes[0].did.getDidDocumentTemplate(); const promise = runtimes[0].did.setDidDocument(accounts0Did, document); await expect(promise).not.to.be.rejected; diff --git a/src/did/did.ts b/src/did/did.ts index 6f7c795e..f6e80e94 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -216,6 +216,7 @@ export class Did extends Logger { public async getDidDocumentTemplate( did?: string, controllerDid?: string, authenticationKey?: string, ): Promise { + const now = new Date(Date.now()).toISOString(); if (did && controllerDid && authenticationKey) { // use given key to create a contract DID document return JSON.parse(`{ @@ -274,6 +275,20 @@ export class Did extends Logger { const identity = this.padIdentity(did ? await this.convertDidToIdentity(did) : this.options.signerIdentity.activeIdentity); + + const newDoc = document; + + const now = (new Date(Date.now())).toISOString(); + // Only set 'created' for new did documents + if (await this.didDocumentWasNeverSet(did)) { + newDoc.created = now; + } else if (!document.created) { + throw Error('DID documents must provide a \'created\' field'); + } + + newDoc.updated = now; + newDoc.proof = await this.createProofForDid(document, DidProofType.EcdsaPublicKeySecp256k1); + const documentHash = await this.options.dfs.add( 'did-document', Buffer.from(JSON.stringify(document), 'utf8'), ); @@ -349,8 +364,7 @@ export class Did extends Logger { const signer = didJWT.SimpleSigner( await this.options.accountStore.getPrivateKey(this.options.signerIdentity.underlyingAccount), ); - let jwt = ''; - await didJWT.createJWT( + const jwt = await didJWT.createJWT( { didDocument, }, { @@ -358,11 +372,12 @@ export class Did extends Logger { issuer: didDocument.id, signer, }, - ).then((response) => { jwt = response; }); + ); return jwt; } + /** * Creates a new `VcProof` object for a given VC document, including generating a JWT token over * the whole document. @@ -375,12 +390,7 @@ export class Did extends Logger { */ private async createProofForDid(didDocument, proofType: DidProofType = DidProofType.EcdsaPublicKeySecp256k1): Promise { - let issuerIdentity; - try { - issuerIdentity = await this.convertDidToIdentity(didDocument.id); - } catch (e) { - throw Error(`Invalid issuer DID: ${didDocument.id}`); - } + const issuerIdentity = await this.convertDidToIdentity(didDocument.id); if (this.options.signerIdentity.activeIdentity !== issuerIdentity) { throw Error('You are not authorized to issue this Did'); @@ -393,7 +403,7 @@ export class Did extends Logger { const key = didDocument.publicKey .filter((entry) => entry.publicKeyHex === signaturePublicKey)[0]; if (!key) { - throw Error('The signature key for the active account is not associated to its DID document.'); + throw Error('The signature key of the active account is not associated to its DID document.'); } const proof: DidProof = { @@ -407,6 +417,27 @@ export class Did extends Logger { return proof; } + /** + * Determines whether a did document has been set before + * (relevant for setting 'created' field) + * @param did DID to check document status for + */ + private async didDocumentWasNeverSet(did: string): Promise { + const identity = await this.convertDidToIdentity(did); + + const didHash = await this.options.executor.executeContractCall( + await this.getRegistryContract(), + 'didDocuments', + this.padIdentity(identity), + ); + + if (didHash === nullBytes32) { + return true; + } + return false; + } + + /** * Retrieve a default DID document for identities that do not have a document associated yet. * @param did DID to fetch a document for. diff --git a/src/vc/vc.ts b/src/vc/vc.ts index 56e8fa51..03a5b93d 100644 --- a/src/vc/vc.ts +++ b/src/vc/vc.ts @@ -422,8 +422,8 @@ export class Vc extends Logger { const signer = didJWT.SimpleSigner( await this.options.accountStore.getPrivateKey(this.options.signerIdentity.underlyingAccount), ); - let jwt = ''; - await didJWT.createJWT( + + const jwt = await didJWT.createJWT( { vc, exp: vc.validUntil, @@ -432,7 +432,7 @@ export class Vc extends Logger { issuer: vc.issuer.id, signer, }, - ).then((response) => { jwt = response; }); + ); return jwt; } @@ -509,7 +509,7 @@ export class Vc extends Logger { const key = doc.publicKey.filter((entry) => entry.publicKeyHex === signaturePublicKey)[0]; if (!key) { - throw Error('The signature key for the active account is not associated to its DID document. Cannot sign VC.'); + throw Error('The signature key of the active account is not associated to its DID document. Cannot sign VC.'); } return key.id; From 8f84ed462885beb4bf66f85735a8f6e47f22ca5d Mon Sep 17 00:00:00 2001 From: dtrinh Date: Wed, 12 Feb 2020 13:58:11 +0100 Subject: [PATCH 030/104] fix error for accounts without DID - [CORE-939] --- src/contracts/digital-twin/digital-twin.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/contracts/digital-twin/digital-twin.ts b/src/contracts/digital-twin/digital-twin.ts index 05b0a9e6..bb1f2257 100644 --- a/src/contracts/digital-twin/digital-twin.ts +++ b/src/contracts/digital-twin/digital-twin.ts @@ -470,8 +470,11 @@ export class DigitalTwin extends Logger { await this.deactivateEntries(); // Unset did - const twinDid = await this.options.did.convertIdentityToDid(description.identity); - await this.options.did.removeDidDocument(twinDid); + // Handle accounts with missing DIDs for migration + if (this.options.did) { + const twinDid = await this.options.did.convertIdentityToDid(description.identity); + await this.options.did.removeDidDocument(twinDid); + } // Deactivate identity if (this.options.verifications.contracts.registry) { From bbb1d338d46c412515b8b1b7cdada77334506e9f Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Thu, 13 Feb 2020 12:02:59 +0100 Subject: [PATCH 031/104] Added created, updated. and proof properties to DIDS & adapted tests [CORE-946] --- src/did/did.spec.ts | 141 ++++++++++++++++++++++++++++++++++--------- src/did/did.ts | 143 +++++++++++++++++++++++++++++--------------- 2 files changed, 208 insertions(+), 76 deletions(-) diff --git a/src/did/did.spec.ts b/src/did/did.spec.ts index c35df690..20c6fd4a 100644 --- a/src/did/did.spec.ts +++ b/src/did/did.spec.ts @@ -19,8 +19,8 @@ import 'mocha'; import * as chaiAsPromised from 'chai-as-promised'; +import * as _ from 'lodash'; import { expect, use } from 'chai'; - import { accounts } from '../test/accounts'; import { TestUtils } from '../test/test-utils'; import { @@ -28,6 +28,7 @@ import { DigitalTwinOptions, Runtime, } from '../index'; +import { DidProofType } from './did'; use(chaiAsPromised); @@ -50,7 +51,7 @@ describe('DID Resolver', function test() { }); describe('when storing did documents for account identities', async () => { - it.only('allows to store a DID document for the own identity', async () => { + it('allows to store a DID document for the own identity', async () => { const document = await runtimes[0].did.getDidDocumentTemplate(); const promise = runtimes[0].did.setDidDocument(accounts0Did, document); await expect(promise).not.to.be.rejected; @@ -60,15 +61,18 @@ describe('DID Resolver', function test() { const document = await runtimes[0].did.getDidDocumentTemplate(); await runtimes[0].did.setDidDocument(accounts0Did, document); const retrieved = await runtimes[0].did.getDidDocument(accounts0Did); - expect(retrieved).to.deep.eq(document); + expect(retrieved.authentication).to.deep.eq(document.authentication); + expect(retrieved.id).to.eq(document.id); + expect(retrieved.publicKey).to.deep.eq(document.publicKey); }); it('allows to get a DID document of another identity', async () => { const document = await runtimes[0].did.getDidDocumentTemplate(); await runtimes[0].did.setDidDocument(accounts0Did, document); const retrieved = await runtimes[1].did.getDidDocument(accounts0Did); - - expect(retrieved).to.deep.eq(document); + expect(retrieved.authentication).to.deep.eq(document.authentication); + expect(retrieved.id).to.eq(document.id); + expect(retrieved.publicKey).to.deep.eq(document.publicKey); }); it('does not allow to store a DID document for another identity', async () => { @@ -83,8 +87,9 @@ describe('DID Resolver', function test() { }); it('allows to define services in a DID document', async () => { - const document = await runtimes[0].did.getDidDocumentTemplate(); - await runtimes[0].did.setDidDocument(accounts0Did, document); + const template = await runtimes[0].did.getDidDocumentTemplate(); + await runtimes[0].did.setDidDocument(accounts0Did, template); + const servicelessDocument = await runtimes[0].did.getDidDocument(accounts0Did); // set new service const random = Math.floor(Math.random() * 1e9); @@ -94,11 +99,12 @@ describe('DID Resolver', function test() { serviceEndpoint: `https://openid.example.com/${random}`, }]; await runtimes[0].did.setService(accounts0Did, service); - + const serviceDocument = await runtimes[0].did.getDidDocument(accounts0Did); expect(await runtimes[0].did.getService(accounts0Did)) .to.deep.eq(service); - expect(await runtimes[0].did.getDidDocument(accounts0Did)) - .to.deep.eq({ ...document, service }); + expect(serviceDocument.created).to.eq(servicelessDocument.created); + expect(serviceDocument.id).to.eq(servicelessDocument.id); + expect(serviceDocument.publicKey).to.deep.eq(servicelessDocument.publicKey); }); it('allows me to fetch a default did document for accounts that have not set a did document yet', async () => { @@ -120,6 +126,54 @@ describe('DID Resolver', function test() { const identityDidDoc = await runtimes[0].did.getDidDocument(did); expect(identityDidDoc).to.deep.eq(expectedDefaultDid); }); + + it('automatically sets created, proof, and updated properties', async () => { + const document = await runtimes[0].did.getDidDocumentTemplate(); + await runtimes[0].did.setDidDocument(accounts0Did, document); + const didDoc = await runtimes[0].did.getDidDocument(accounts0Did); + expect(didDoc).to.have.property('created'); + expect(didDoc).to.have.property('updated'); + expect(didDoc).to.have.property('proof'); + }); + + it('throws if a DID document has an invalid proof', async () => { + const didContract = await (runtimes[0].did as any).getRegistryContract(); + + // Create scenario where a document carries a proof that has been + // created over a differing document + const documentToSave = await runtimes[0].did.getDidDocumentTemplate(); + const documentToCreateProofOver = _.cloneDeep(documentToSave); + documentToCreateProofOver.updated = new Date(Date.now()).toISOString(); + + const invalidProof = await (runtimes[0].did as any).createJwtForDid( + documentToCreateProofOver, + accounts0Did, + DidProofType.EcdsaPublicKeySecp256k1, + ); + documentToSave.proof = { + type: DidProofType.EcdsaPublicKeySecp256k1, + created: new Date(Date.now()).toISOString(), + proofPurpose: 'assertionMethod', + verificationMethod: documentToSave.publicKey[0].id, + jws: invalidProof, + }; + + // Set did document with invalid proof manually (the api wouldn't allow this) + const identity = await runtimes[0].did.convertDidToIdentity(accounts0Did); + const targetHash = await (runtimes[0].did as any).padIdentity(identity); + const documentHash = await runtimes[0].dfs.add('did', Buffer.from(JSON.stringify(documentToSave))); + await runtimes[0].executor.executeContractTransaction( + didContract, + 'setDidDocument', + { from: runtimes[0].activeIdentity }, + targetHash, + documentHash, + ); + + // Try to retrieve it + expect(runtimes[0].did.getDidDocument(accounts0Did)) + .to.be.eventually.rejectedWith('Invalid proof'); + }); }); describe('when storing did documents for contract identities', () => { @@ -130,6 +184,11 @@ describe('DID Resolver', function test() { version: '0.1.0', dbcpVersion: 2, }; + before(async () => { + // Set DID document of testaccount back to normal, or else these tests will fail ( :( ) + const document = await runtimes[0].did.getDidDocumentTemplate(); + await runtimes[0].did.setDidDocument(accounts0Did, document); + }); it('allows to store a DID document for the identity of an own contract', async () => { const accountRuntime = await TestUtils.getRuntime( @@ -159,7 +218,7 @@ describe('DID Resolver', function test() { await expect(promise).not.to.be.rejected; }); - it('can get retrieve an contract identities DID document', async () => { + it('can retrieve a contract identities DID document', async () => { const accountRuntime = await TestUtils.getRuntime( runtimes[0].underlyingAccount, null, { useIdentity: true }, ); @@ -185,7 +244,14 @@ describe('DID Resolver', function test() { ); await runtimes[0].did.setDidDocument(twinDid, document); const retrieved = await runtimes[0].did.getDidDocument(twinDid); - expect(retrieved).to.deep.eq(document); + + expect(retrieved.authentication).to.deep.eq(document.authentication); + expect(retrieved.id).to.eq(document.id); + expect(retrieved.publicKey).to.deep.eq(document.publicKey); + expect(retrieved.controller).to.eq(document.controller); + expect(retrieved).to.have.property('created'); + expect(retrieved).to.have.property('updated'); + expect(retrieved).to.have.property('proof'); }); it('allows to get a DID document of another identity', async () => { @@ -215,10 +281,14 @@ describe('DID Resolver', function test() { await runtimes[0].did.setDidDocument(twinDid, document); const retrieved = await runtimes[1].did.getDidDocument(twinDid); - expect(retrieved).to.deep.eq(document); + + expect(retrieved.authentication).to.deep.eq(document.authentication); + expect(retrieved.id).to.eq(document.id); + expect(retrieved.publicKey).to.deep.eq(document.publicKey); + expect(retrieved.controller).to.eq(document.controller); }); - it('does not allow to store a DID document for the identity of an own contract', async () => { + it('does not allow to store a DID document for the identity of a contract I do not own', async () => { const accountRuntime = await TestUtils.getRuntime( runtimes[0].underlyingAccount, null, { useIdentity: true }, ); @@ -233,6 +303,7 @@ describe('DID Resolver', function test() { const twinIdentity = await runtimes[0].verifications.getIdentityForAccount( await twin.getContractAddress(), true, ); + const twinDid = await runtimes[0].did.convertIdentityToDid(twinIdentity); const controllerDid = await runtimes[0].did.convertIdentityToDid( @@ -244,13 +315,14 @@ describe('DID Resolver', function test() { ); const runtime1 = runtimes[1]; const promise = runtime1.did.setDidDocument(twinDid, document); - await expect(promise).to.be.rejectedWith(/^could not estimate gas usage for setDidDocument/); + await expect(promise).to.be.rejectedWith(/^You are not authorized to issue this Did/); }); it('allows to fetch a default DID document for newly created contract identities', async () => { const accountRuntime = await TestUtils.getRuntime( runtimes[0].underlyingAccount, null, { useIdentity: true }, ); + delete accountRuntime.did; // Do not create DID document const twin = await DigitalTwin.create( accountRuntime as DigitalTwinOptions, { @@ -262,38 +334,44 @@ describe('DID Resolver', function test() { const twinIdentity = await runtimes[0].verifications.getIdentityForAccount( await twin.getContractAddress(), true, ); - const twinDid = await runtimes[0].did.convertIdentityToDid(twinIdentity); + const did = await runtimes[0].did.convertIdentityToDid(twinIdentity); const controllerDid = await runtimes[0].did.convertIdentityToDid(runtimes[0].activeIdentity); const controllerDidDoc = await runtimes[0].did.getDidDocument(controllerDid); const authKeyIds = controllerDidDoc.publicKey.map((key) => key.id).join(','); const expectedDefaultDid = { '@context': 'https://w3id.org/did/v1', - id: twinDid, + id: did, controller: controllerDidDoc.id, authentication: [ authKeyIds, ], }; - - const defaultDidDoc = await runtimes[0].did.getDidDocument(twinDid); + const defaultDidDoc = await runtimes[0].did.getDidDocument(did); await expect(defaultDidDoc).to.deep.eq(expectedDefaultDid); }); }); describe('when storing did documents for alias identities', () => { + before(async () => { + // Set DID document of testaccount back to normal, or else these tests will fail ( :( ) + const document = await runtimes[0].did.getDidDocumentTemplate(); + await runtimes[0].did.setDidDocument(accounts0Did, document); + }); + it('can create did documents for alias identities', async () => { const aliasHash = TestUtils.getRandomBytes32(); const aliasIdentity = await runtimes[0].verifications.createIdentity( runtimes[0].underlyingAccount, aliasHash, false, ); + const did = await runtimes[0].did.convertIdentityToDid(aliasIdentity); const controllerDid = await runtimes[0].did.convertIdentityToDid( runtimes[0].activeIdentity, ); const controllerDidDocument = await runtimes[0].did.getDidDocument(accounts0Did); const document = await runtimes[0].did.getDidDocumentTemplate( - aliasIdentity, controllerDid, controllerDidDocument.authentication[0], + did, controllerDid, controllerDidDocument.authentication[0], ); const promise = runtimes[0].did.setDidDocument(did, document); @@ -305,13 +383,14 @@ describe('DID Resolver', function test() { const aliasIdentity = await runtimes[0].verifications.createIdentity( runtimes[0].underlyingAccount, aliasHash, false, false, ); + const did = await runtimes[0].did.convertIdentityToDid(aliasIdentity); const controllerDid = await runtimes[0].did.convertIdentityToDid( runtimes[0].activeIdentity, ); const controllerDidDocument = await runtimes[0].did.getDidDocument(accounts0Did); const document = await runtimes[0].did.getDidDocumentTemplate( - aliasIdentity, controllerDid, controllerDidDocument.authentication[0], + did, controllerDid, controllerDidDocument.authentication[0], ); const promise = runtimes[0].did.setDidDocument(did, document); @@ -323,20 +402,26 @@ describe('DID Resolver', function test() { const aliasIdentity = await runtimes[0].verifications.createIdentity( runtimes[0].underlyingAccount, aliasHash, false, ); + const did = await runtimes[0].did.convertIdentityToDid(aliasIdentity); const controllerDid = await runtimes[0].did.convertIdentityToDid( runtimes[0].activeIdentity, ); const controllerDidDocument = await runtimes[0].did.getDidDocument(accounts0Did); - const document = await runtimes[0].did.getDidDocumentTemplate( - aliasIdentity, controllerDid, controllerDidDocument.authentication[0], - ); - await runtimes[0].did.setDidDocument(did, document); - - expect(runtimes[0].did.getDidDocument(did)).to.eventually.deep.eq(document); + const documentTemplate = await runtimes[0].did.getDidDocumentTemplate( + did, controllerDid, controllerDidDocument.authentication[0], + ); + await runtimes[0].did.setDidDocument(did, documentTemplate); + const actualDocument = await runtimes[0].did.getDidDocument(did); + expect(actualDocument.authentication).to.deep.eq(documentTemplate.authentication); + expect(actualDocument.controller).to.eq(documentTemplate.controller); + expect(actualDocument.id).to.eq(documentTemplate.id); + expect(actualDocument).to.have.property('created'); + expect(actualDocument).to.have.property('updated'); + expect(actualDocument).to.have.property('proof'); }); - it('can fetch did documents for alias identities that have not set a doc themselves, yet', async () => { + it('can fetch did documents for alias identities that have not set a doc themselves yet', async () => { const aliasHash = TestUtils.getRandomBytes32(); const aliasIdentity = await runtimes[0].verifications.createIdentity( runtimes[0].underlyingAccount, aliasHash, false, diff --git a/src/did/did.ts b/src/did/did.ts index f6e80e94..04864a93 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -18,7 +18,7 @@ */ import * as didJWT from 'did-jwt'; - +import * as _ from 'lodash'; import { AccountStore, ContractLoader, @@ -27,7 +27,6 @@ import { Logger, LoggerOptions, } from '@evan.network/dbcp'; - import { getEnvironment, nullBytes32, @@ -40,12 +39,14 @@ import { VerificationsDelegationInfo, Verifications } from '../verifications/ver const didRegEx = /^did:evan:(?:(testcore|core):)?(0x(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{64}))$/; + /** * template for a new DID document, can be used as a starting point for building own documents */ export interface DidDocumentTemplate { '@context': string; id: string; + controller: string; authentication: { type: string; publicKey: string; @@ -176,6 +177,7 @@ export class Did extends Logger { * * @param {string} did DID to fetch DID document for * @return {Promise} a DID document that MAY resemble `DidDocumentTemplate` format + * @throws if a DID document contains an invalid proof */ public async getDidDocument(did: string): Promise { let result = null; @@ -195,6 +197,9 @@ export class Did extends Logger { result = JSON.parse(await this.options.dfs.get(documentHash) as any); result = await this.removePublicKeyTypeArray(result); + if (result.proof) { + await this.validateProof(result); + } return result; } @@ -216,8 +221,9 @@ export class Did extends Logger { public async getDidDocumentTemplate( did?: string, controllerDid?: string, authenticationKey?: string, ): Promise { - const now = new Date(Date.now()).toISOString(); if (did && controllerDid && authenticationKey) { + await this.validateDid(did); + await this.validateDid(controllerDid); // use given key to create a contract DID document return JSON.parse(`{ "@context": "https://w3id.org/did/v1", @@ -268,7 +274,7 @@ export class Did extends Logger { * @param {string} did DID to store DID document for * @param {any} document DID document to store * @param {VerificationDelegationInfo} txInfo Optional. If given, the transaction - * is executed on behalf of the tx signer. + * is executed on behalf of the tx signer. * @return {Promise} resolved when done */ public async setDidDocument(did: string, document: any): Promise { @@ -276,21 +282,10 @@ export class Did extends Logger { ? await this.convertDidToIdentity(did) : this.options.signerIdentity.activeIdentity); - const newDoc = document; - - const now = (new Date(Date.now())).toISOString(); - // Only set 'created' for new did documents - if (await this.didDocumentWasNeverSet(did)) { - newDoc.created = now; - } else if (!document.created) { - throw Error('DID documents must provide a \'created\' field'); - } - - newDoc.updated = now; - newDoc.proof = await this.createProofForDid(document, DidProofType.EcdsaPublicKeySecp256k1); - + const finalDoc = await this.setAdditionalProperties(document); const documentHash = await this.options.dfs.add( - 'did-document', Buffer.from(JSON.stringify(document), 'utf8'), + 'did-document', + Buffer.from(JSON.stringify(finalDoc), 'utf8'), ); await this.options.executor.executeContractTransaction( await this.getRegistryContract(), @@ -314,8 +309,12 @@ export class Did extends Logger { const identity = this.padIdentity(did ? await this.convertDidToIdentity(did) : this.options.signerIdentity.activeIdentity); + + const finalDoc = await this.setAdditionalProperties(document); + const documentHash = await this.options.dfs.add( - 'did-document', Buffer.from(JSON.stringify(document), 'utf8'), + 'did-document', + Buffer.from(JSON.stringify(finalDoc), 'utf8'), ); const txInfo = await this.options.verifications.signTransaction( @@ -329,6 +328,20 @@ export class Did extends Logger { return [txInfo, documentHash]; } + private async setAdditionalProperties(document: any): Promise { + const clone = _.cloneDeep(document); + const now = (new Date(Date.now())).toISOString(); + // Only set 'created' for new did documents + if (!document.created) { + clone.created = now; + } + + clone.updated = now; + clone.proof = await this.createProofForDid(clone, DidProofType.EcdsaPublicKeySecp256k1); + + return clone; + } + /** * Sets service in DID document. * @@ -357,10 +370,12 @@ export class Did extends Logger { /** * Create a JWT over a DID document * - * @param {didDocument} DID The DID document + * @param {didDocument} DID The DID document + * @param {string} proofIssuer The issuer (key owner) of the proof * @param {DidProofType} proofType The type of algorithm used for generating the JWT */ - private async createJwtForDid(didDocument, proofType: DidProofType): Promise { + private async createJwtForDid(didDocument: any, proofIssuer: string, proofType: DidProofType): + Promise { const signer = didJWT.SimpleSigner( await this.options.accountStore.getPrivateKey(this.options.signerIdentity.underlyingAccount), ); @@ -369,7 +384,7 @@ export class Did extends Logger { didDocument, }, { alg: JWTProofMapping[proofType], - issuer: didDocument.id, + issuer: proofIssuer, signer, }, ); @@ -391,21 +406,32 @@ export class Did extends Logger { private async createProofForDid(didDocument, proofType: DidProofType = DidProofType.EcdsaPublicKeySecp256k1): Promise { const issuerIdentity = await this.convertDidToIdentity(didDocument.id); - - if (this.options.signerIdentity.activeIdentity !== issuerIdentity) { + const controllerIdentity = didDocument.controller + ? await this.convertDidToIdentity(didDocument.controller) + : ''; + + let keys; + let proofIssuer; + if (this.options.signerIdentity.activeIdentity === issuerIdentity) { + keys = didDocument.publicKey; + proofIssuer = didDocument.id; + } else if (this.options.signerIdentity.activeIdentity === controllerIdentity) { + const controllerDidDoc = await this.getDidDocument(didDocument.controller); + keys = controllerDidDoc.publicKey; + proofIssuer = didDocument.controller; + } else { throw Error('You are not authorized to issue this Did'); } - const jwt = await this.createJwtForDid(didDocument, proofType); const signaturePublicKey = await this.options.signerIdentity.getPublicKey( this.options.signerIdentity.underlyingAccount, ); - const key = didDocument.publicKey - .filter((entry) => entry.publicKeyHex === signaturePublicKey)[0]; + const key = keys.filter((entry) => entry.publicKeyHex === signaturePublicKey)[0]; if (!key) { throw Error('The signature key of the active account is not associated to its DID document.'); } + const jwt = await this.createJwtForDid(didDocument, proofIssuer, proofType); const proof: DidProof = { type: `${proofType}`, created: new Date(Date.now()).toISOString(), @@ -417,27 +443,6 @@ export class Did extends Logger { return proof; } - /** - * Determines whether a did document has been set before - * (relevant for setting 'created' field) - * @param did DID to check document status for - */ - private async didDocumentWasNeverSet(did: string): Promise { - const identity = await this.convertDidToIdentity(did); - - const didHash = await this.options.executor.executeContractCall( - await this.getRegistryContract(), - 'didDocuments', - this.padIdentity(identity), - ); - - if (didHash === nullBytes32) { - return true; - } - return false; - } - - /** * Retrieve a default DID document for identities that do not have a document associated yet. * @param did DID to fetch a document for. @@ -565,4 +570,46 @@ export class Did extends Logger { } return groups; } + + /** + * Validates the JWS of a VC Document proof + * + * @param {VcDocument} document The VC Document + * @returns {Promise} Resolves when done + */ + private async validateProof(document: any): Promise { + // Mock the did-resolver package that did-jwt usually requires + const getResolver = (didModule) => ({ + async resolve(did) { + if (did === document.id) { + return document; // Avoid JWT cycling through documents forever + } + return didModule.getDidDocument(document.controller); + }, + }); + + // fails if invalid signature + const verifiedSignature = await didJWT.verifyJWT( + document.proof.jws, + { resolver: getResolver(this) }, + ); + + // fails if signed payload and the VC differ + const payload = { + ...verifiedSignature.payload.didDocument, + }; + delete payload.proof; + const prooflessDocument = { + ...document, + }; + delete prooflessDocument.proof; + + const proofPayloadHash = await this.options.nameResolver.soliditySha3(JSON.stringify(payload)); + const documentHash = await this.options.nameResolver.soliditySha3( + JSON.stringify(prooflessDocument), + ); + if (proofPayloadHash !== documentHash) { + throw Error('Invalid proof. Signed payload does not match given document.'); + } + } } From 6a482cfa32bda3ccec4a7e7050983c1d71f0457b Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Thu, 13 Feb 2020 12:07:11 +0100 Subject: [PATCH 032/104] Update docs [CORE-946] --- docs/profile/did.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/profile/did.rst b/docs/profile/did.rst index 81b689f5..61aead90 100644 --- a/docs/profile/did.rst +++ b/docs/profile/did.rst @@ -85,7 +85,10 @@ setDidDocument did.setDidDocument(did, document); -Store given DID document for given DID. +Store given DID document for given DID. If the document misses the property `created`, it will automatically +be appended. The `updated` property will be updated accordingly. +A proof over the DID document will be generated automatically +and appended to the document. ---------- Parameters @@ -124,7 +127,8 @@ getDidDocument did.getDidDocument([did]); -Get DID document for given DID. +Get DID document for given DID. If the DID has a proof property, `getDidDocument` will attempt to validate the proof +and throw an error if the proof is invalid. ---------- Parameters From 659d298148b397b800a1b4eb49fc8b8b12d861a8 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Thu, 13 Feb 2020 12:19:09 +0100 Subject: [PATCH 033/104] Controller as optional did document property [CORE-946] --- src/did/did.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/did/did.ts b/src/did/did.ts index 04864a93..0df1f06e 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -46,7 +46,7 @@ const didRegEx = /^did:evan:(?:(testcore|core):)?(0x(?:[0-9a-fA-F]{40}|[0-9a-fA- export interface DidDocumentTemplate { '@context': string; id: string; - controller: string; + controller?: string; authentication: { type: string; publicKey: string; From 0a9eae9e6e3f98b59051c75956a0b6cac18a0692 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Thu, 13 Feb 2020 13:56:40 +0100 Subject: [PATCH 034/104] Fixed merging issues [CORE-946] --- .../digital-twin/digital-twin.spec.ts | 2 +- src/did/did.spec.ts | 7 +++-- src/did/did.ts | 28 ++++++++++--------- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/contracts/digital-twin/digital-twin.spec.ts b/src/contracts/digital-twin/digital-twin.spec.ts index 63329367..4c4e762e 100644 --- a/src/contracts/digital-twin/digital-twin.spec.ts +++ b/src/contracts/digital-twin/digital-twin.spec.ts @@ -35,7 +35,7 @@ import { DigitalTwinTemplate, DigitalTwinVerificationEntry, } from './digital-twin'; -import { Executor } from '../..'; +import { Executor, SignerInternal } from '../..'; import { nullAddress, nullBytes32, diff --git a/src/did/did.spec.ts b/src/did/did.spec.ts index 32ac177a..7b71e9dc 100644 --- a/src/did/did.spec.ts +++ b/src/did/did.spec.ts @@ -21,7 +21,7 @@ import 'mocha'; import * as chaiAsPromised from 'chai-as-promised'; import * as _ from 'lodash'; import { expect, use } from 'chai'; -import { Did } from './did'; +import { Did, DidProofType } from './did'; import { SignerIdentity } from '../contracts/signer-identity'; import { TestUtils } from '../test/test-utils'; import { accounts } from '../test/accounts'; @@ -30,7 +30,6 @@ import { DigitalTwinOptions, Runtime, } from '../index'; -import { DidProofType } from './did'; use(chaiAsPromised); @@ -56,6 +55,7 @@ describe('DID Resolver', function test() { 'DidRegistry', [], { from: accounts[2], gas: 1_000_000 }, ); const options0 = (({ + accountStore, contractLoader, dfs, executor, @@ -64,6 +64,7 @@ describe('DID Resolver', function test() { verifications, web3, }) => ({ + accountStore, contractLoader, dfs, executor, @@ -74,6 +75,7 @@ describe('DID Resolver', function test() { }))(runtimes[0]); runtimes[0].did = new Did(options0, { registryAddress: registry.options.address }); const options1 = (({ + accountStore, contractLoader, dfs, executor, @@ -82,6 +84,7 @@ describe('DID Resolver', function test() { verifications, web3, }) => ({ + accountStore, contractLoader, dfs, executor, diff --git a/src/did/did.ts b/src/did/did.ts index 5a1d2176..54732b6d 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -343,19 +343,6 @@ export class Did extends Logger { return [txInfo, documentHash]; } - private async setAdditionalProperties(document: any): Promise { - const clone = _.cloneDeep(document); - const now = (new Date(Date.now())).toISOString(); - // Only set 'created' for new did documents - if (!document.created) { - clone.created = now; - } - - clone.updated = now; - clone.proof = await this.createProofForDid(clone, DidProofType.EcdsaPublicKeySecp256k1); - - return clone; - } /** * Sets service in DID document. @@ -592,6 +579,21 @@ export class Did extends Logger { return cleanedResult; } + private async setAdditionalProperties(document: any): Promise { + const clone = _.cloneDeep(document); + const now = (new Date(Date.now())).toISOString(); + // Only set 'created' for new did documents + if (!document.created) { + clone.created = now; + } + + clone.updated = now; + clone.proof = await this.createProofForDid(clone, DidProofType.EcdsaPublicKeySecp256k1); + + return clone; + } + + /** * Validates if a given DID is a valid evan DID and returns its parts. * From 157e571845ad8c0a329ef103ab1cefbe0b5af133 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Thu, 13 Feb 2020 15:51:12 +0100 Subject: [PATCH 035/104] Fixed bug [CORE-939] --- src/contracts/digital-twin/digital-twin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contracts/digital-twin/digital-twin.ts b/src/contracts/digital-twin/digital-twin.ts index bb1f2257..e6d0dbf2 100644 --- a/src/contracts/digital-twin/digital-twin.ts +++ b/src/contracts/digital-twin/digital-twin.ts @@ -473,7 +473,7 @@ export class DigitalTwin extends Logger { // Handle accounts with missing DIDs for migration if (this.options.did) { const twinDid = await this.options.did.convertIdentityToDid(description.identity); - await this.options.did.removeDidDocument(twinDid); + await this.options.did.deactivateDidDocument(twinDid); } // Deactivate identity From ca9e2a758e633e8f0e8922e07dd2140a5b425bcb Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Thu, 13 Feb 2020 16:32:29 +0100 Subject: [PATCH 036/104] DID comparisons ignore cases [CORE-946] --- src/did/did.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/did/did.ts b/src/did/did.ts index c37122a1..d2fd5167 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -170,8 +170,8 @@ export class Did extends Logger { const groups = await this.validateDidAndGetSections(did); const [, didEnvironment = 'core', identity] = groups; const environment = await this.getEnvironment(); - if ((environment === 'testcore' && didEnvironment !== 'testcore') - || (environment === 'core' && didEnvironment !== 'core')) { + if ((environment.toLocaleLowerCase() === 'testcore' && didEnvironment.toLocaleLowerCase() !== 'testcore') + || (environment.toLocaleLowerCase() === 'core' && didEnvironment.toLocaleLowerCase() !== 'core')) { throw new Error(`DIDs environment "${environment} does not match ${didEnvironment}`); } @@ -464,10 +464,12 @@ export class Did extends Logger { let keys; let proofIssuer; - if (this.options.signerIdentity.activeIdentity === issuerIdentity) { + if (this.options.signerIdentity.activeIdentity.toLocaleLowerCase() + === issuerIdentity.toLocaleLowerCase()) { keys = didDocument.publicKey; proofIssuer = didDocument.id; - } else if (this.options.signerIdentity.activeIdentity === controllerIdentity) { + } else if (this.options.signerIdentity.activeIdentity.toLocaleLowerCase() + === controllerIdentity.toLocaleLowerCase()) { const controllerDidDoc = await this.getDidDocument(didDocument.controller); keys = controllerDidDoc.publicKey; proofIssuer = didDocument.controller; @@ -475,10 +477,8 @@ export class Did extends Logger { throw Error('You are not authorized to issue this Did'); } - const signaturePublicKey = await this.options.signerIdentity.getPublicKey( - this.options.signerIdentity.underlyingAccount, - ); - const key = keys.filter((entry) => entry.publicKeyHex === signaturePublicKey)[0]; + const key = keys.filter((entry) => entry.ethereumAddress.toLocaleLowerCase() + === this.options.signerIdentity.underlyingAccount.toLocaleLowerCase())[0]; if (!key) { throw Error('The signature key of the active account is not associated to its DID document.'); } @@ -551,7 +551,8 @@ export class Did extends Logger { */ private async getDidInfix(): Promise { if (typeof this.cached.didInfix === 'undefined') { - this.cached.didInfix = (await this.getEnvironment()) === 'testcore' ? 'testcore:' : ''; + this.cached.didInfix = (await this.getEnvironment()).toLocaleLowerCase() + === 'testcore' ? 'testcore:' : ''; } return this.cached.didInfix; } @@ -664,7 +665,7 @@ export class Did extends Logger { // Mock the did-resolver package that did-jwt usually requires const getResolver = (didModule) => ({ async resolve(did) { - if (did === document.id) { + if (did.toLowerCase() === document.id.toLowerCase()) { return document; // Avoid JWT cycling through documents forever } return didModule.getDidDocument(document.controller); From 6ce5bb3ef1ac7f1292ae1c26e6e435ea9bed46dc Mon Sep 17 00:00:00 2001 From: wulfraem Date: Thu, 13 Feb 2020 17:23:05 +0100 Subject: [PATCH 037/104] fix unshare property handling - by making properties to unshare unique - [CORE-901] --- src/contracts/digital-twin/container.ts | 70 +++++++++++++------------ 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/src/contracts/digital-twin/container.ts b/src/contracts/digital-twin/container.ts index f9be57b8..c8dcd828 100644 --- a/src/contracts/digital-twin/container.ts +++ b/src/contracts/digital-twin/container.ts @@ -1389,45 +1389,47 @@ export class Container extends Logger { let accessP = []; - accessP = accessP.concat([...readWrite, ...write].map((property) => async () => { - this.log(`removing write permissions for ${property}`, 'debug'); - const propertyType = getPropertyType(schemaProperties[property].type); - // search for role with permissions - const permittedRole = await this.getPermittedRole( - authority, property, propertyType, ModificationType.Set, - ); - if (permittedRole < this.reservedRoles) { - // if not found or included in reserved roles, exit - this.log(`can not find a role that has write permissions for property ${property}`); - } else { - // remove account from role - const hasRole = await this.options.executor.executeContractCall( - authority, 'hasUserRole', accountId, permittedRole, + accessP = accessP.concat([...new Set([...readWrite, ...write])] + .map((property) => async () => { + this.log(`removing write permissions for ${property}`, 'debug'); + const propertyType = getPropertyType(schemaProperties[property].type); + // search for role with permissions + const permittedRole = await this.getPermittedRole( + authority, property, propertyType, ModificationType.Set, ); - if (hasRole) { - await this.options.rightsAndRoles.removeAccountFromRole( - this.contract, this.config.accountId, accountId, permittedRole, + // console.table([property, propertyType, ModificationType.Set, permittedRole]) + if (permittedRole < this.reservedRoles) { + // if not found or included in reserved roles, exit + this.log(`can not find a role that has write permissions for property ${property}`); + } else { + // remove account from role + const hasRole = await this.options.executor.executeContractCall( + authority, 'hasUserRole', accountId, permittedRole, ); - } + if (hasRole) { + await this.options.rightsAndRoles.removeAccountFromRole( + this.contract, this.config.accountId, accountId, permittedRole, + ); + } - // if no members are left, remove role - const memberCount = await this.options.executor.executeContractCall( - authority, 'role2userCount', permittedRole, - ); - if (memberCount.eq(0)) { - this.log(`removing role for property "${property}"`, 'debug'); - await this.options.rightsAndRoles.setOperationPermission( - authority, - this.config.accountId, - permittedRole, - property, - getPropertyType(schemaProperties[property].type), - ModificationType.Set, - false, + // if no members are left, remove role + const memberCount = await this.options.executor.executeContractCall( + authority, 'role2userCount', permittedRole, ); + if (memberCount.eq(0)) { + this.log(`removing role for property "${property}"`, 'debug'); + await this.options.rightsAndRoles.setOperationPermission( + authority, + this.config.accountId, + permittedRole, + property, + getPropertyType(schemaProperties[property].type), + ModificationType.Set, + false, + ); + } } - } - })); + })); // ///////////////////////////////////////////////////////////// remove list entries handling accessP = accessP.concat(removeListEntries.map((property) => async () => { From 3fdad539790d695c7d55a172c0e67c583b13b7b1 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Thu, 13 Feb 2020 22:59:04 +0100 Subject: [PATCH 038/104] Fix bug --- src/did/did.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/did/did.ts b/src/did/did.ts index d2fd5167..11bf9319 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -457,19 +457,18 @@ export class Did extends Logger { */ private async createProofForDid(didDocument, proofType: DidProofType = DidProofType.EcdsaPublicKeySecp256k1): Promise { - const issuerIdentity = await this.convertDidToIdentity(didDocument.id); + const issuerIdentity = (await this.convertDidToIdentity(didDocument.id)).toLocaleLowerCase(); + const activeIdentity = this.options.signerIdentity.activeIdentity.toLocaleLowerCase(); const controllerIdentity = didDocument.controller - ? await this.convertDidToIdentity(didDocument.controller) + ? (await this.convertDidToIdentity(didDocument.controller)).toLocaleLowerCase() : ''; let keys; let proofIssuer; - if (this.options.signerIdentity.activeIdentity.toLocaleLowerCase() - === issuerIdentity.toLocaleLowerCase()) { + if (activeIdentity === issuerIdentity) { keys = didDocument.publicKey; proofIssuer = didDocument.id; - } else if (this.options.signerIdentity.activeIdentity.toLocaleLowerCase() - === controllerIdentity.toLocaleLowerCase()) { + } else if (activeIdentity === controllerIdentity) { const controllerDidDoc = await this.getDidDocument(didDocument.controller); keys = controllerDidDoc.publicKey; proofIssuer = didDocument.controller; From 649a5508451c9ab33923b99464f97ee7f853426a Mon Sep 17 00:00:00 2001 From: wulfraem Date: Fri, 14 Feb 2020 08:11:23 +0100 Subject: [PATCH 039/104] refactor code - [CORE-901] --- src/contracts/digital-twin/container.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/contracts/digital-twin/container.ts b/src/contracts/digital-twin/container.ts index c8dcd828..82a3d831 100644 --- a/src/contracts/digital-twin/container.ts +++ b/src/contracts/digital-twin/container.ts @@ -1015,8 +1015,6 @@ export class Container extends Logger { for (const { accountId, read = [], readWrite = [], removeListEntries = [], } of localShareConfig) { - let accessPromises = []; - // //////////////////////////////////////////////// ensure that account is member in contract if (!await this.options.executor.executeContractCall( this.contract, 'isConsumer', accountId, @@ -1033,7 +1031,7 @@ export class Container extends Logger { read.push('type'); } // ensure that roles for fields exist and that accounts have permissions - accessPromises = accessPromises.concat(readWrite.map((property, index) => async () => { + let accessPromises = readWrite.map((property, index) => async () => { // get permissions from contract const hash = this.options.rightsAndRoles.getOperationCapabilityHash( property, @@ -1078,7 +1076,7 @@ export class Container extends Logger { this.contract, this.config.accountId, accountId, permittedRole, ); } - })); + }); accessPromises = accessPromises.concat(removeListEntries.map((property) => async () => { const propertyType = getPropertyType(schemaProperties[property].type); From cc3462e7ea3ac1e8c453d9b6305f0089fc539f72 Mon Sep 17 00:00:00 2001 From: wulfraem Date: Fri, 14 Feb 2020 08:15:19 +0100 Subject: [PATCH 040/104] update version info - [CORE-901] --- VERSIONS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSIONS.md b/VERSIONS.md index 645269af..bc816c74 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -1,9 +1,9 @@ # api-blockchain-core ## Next Version -- add methods `deactivateDidDocument` and `didIsDeactivated` to check and handle DID deactivation status - ### Features +- add methods `deactivateDidDocument` and `didIsDeactivated` to check and handle DID deactivation status +- improve performance of `shareProperties`, `unshareProperties`, `setContainerShareConfigs` and related operations in `Container` ### Fixes - fix buffer-to-string conversion, try to decode to `utf8`, if this fails, decode it to `binary` From cfe6a016782f1658aee4ae37c6da26070a8d3251 Mon Sep 17 00:00:00 2001 From: wulfraem Date: Fri, 14 Feb 2020 08:29:43 +0100 Subject: [PATCH 041/104] refactor code - [CORE-901] --- src/contracts/digital-twin/container.ts | 75 ++++++++++++------------- 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/src/contracts/digital-twin/container.ts b/src/contracts/digital-twin/container.ts index d0231d79..bf2c9bb0 100644 --- a/src/contracts/digital-twin/container.ts +++ b/src/contracts/digital-twin/container.ts @@ -1383,51 +1383,48 @@ export class Container extends Logger { } = unshareConfig; this.log('checking unshare configs', 'debug'); - // remove write permissions for all in readWrite and write - - let accessP = []; - accessP = accessP.concat([...new Set([...readWrite, ...write])] - .map((property) => async () => { - this.log(`removing write permissions for ${property}`, 'debug'); - const propertyType = getPropertyType(schemaProperties[property].type); - // search for role with permissions - const permittedRole = await this.getPermittedRole( - authority, property, propertyType, ModificationType.Set, + // remove write permissions for all in readWrite and write + let accessP = [...new Set([...readWrite, ...write])].map((property) => async () => { + this.log(`removing write permissions for ${property}`, 'debug'); + const propertyType = getPropertyType(schemaProperties[property].type); + // search for role with permissions + const permittedRole = await this.getPermittedRole( + authority, property, propertyType, ModificationType.Set, + ); + // console.table([property, propertyType, ModificationType.Set, permittedRole]) + if (permittedRole < this.reservedRoles) { + // if not found or included in reserved roles, exit + this.log(`can not find a role that has write permissions for property ${property}`); + } else { + // remove account from role + const hasRole = await this.options.executor.executeContractCall( + authority, 'hasUserRole', accountId, permittedRole, ); - // console.table([property, propertyType, ModificationType.Set, permittedRole]) - if (permittedRole < this.reservedRoles) { - // if not found or included in reserved roles, exit - this.log(`can not find a role that has write permissions for property ${property}`); - } else { - // remove account from role - const hasRole = await this.options.executor.executeContractCall( - authority, 'hasUserRole', accountId, permittedRole, + if (hasRole) { + await this.options.rightsAndRoles.removeAccountFromRole( + this.contract, this.config.accountId, accountId, permittedRole, ); - if (hasRole) { - await this.options.rightsAndRoles.removeAccountFromRole( - this.contract, this.config.accountId, accountId, permittedRole, - ); - } + } - // if no members are left, remove role - const memberCount = await this.options.executor.executeContractCall( - authority, 'role2userCount', permittedRole, + // if no members are left, remove role + const memberCount = await this.options.executor.executeContractCall( + authority, 'role2userCount', permittedRole, + ); + if (memberCount.eq(0)) { + this.log(`removing role for property "${property}"`, 'debug'); + await this.options.rightsAndRoles.setOperationPermission( + authority, + this.config.accountId, + permittedRole, + property, + getPropertyType(schemaProperties[property].type), + ModificationType.Set, + false, ); - if (memberCount.eq(0)) { - this.log(`removing role for property "${property}"`, 'debug'); - await this.options.rightsAndRoles.setOperationPermission( - authority, - this.config.accountId, - permittedRole, - property, - getPropertyType(schemaProperties[property].type), - ModificationType.Set, - false, - ); - } } - })); + } + }); // ///////////////////////////////////////////////////////////// remove list entries handling accessP = accessP.concat(removeListEntries.map((property) => async () => { From 1573abbd4f17626dbf9035dd5060b351ccbdbc42 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Fri, 14 Feb 2020 09:04:25 +0100 Subject: [PATCH 042/104] Fixed casing for addres comparisons --- src/did/did.ts | 16 ++++++++++++++-- src/vc/vc.ts | 17 ++++++++++++----- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/did/did.ts b/src/did/did.ts index 11bf9319..7b8be023 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -462,6 +462,10 @@ export class Did extends Logger { const controllerIdentity = didDocument.controller ? (await this.convertDidToIdentity(didDocument.controller)).toLocaleLowerCase() : ''; + const account = this.options.signerIdentity.underlyingAccount.toLocaleLowerCase(); + const signaturePublicKey = (await this.options.signerIdentity.getPublicKey( + this.options.signerIdentity.underlyingAccount, + )).toLocaleLowerCase(); let keys; let proofIssuer; @@ -476,8 +480,16 @@ export class Did extends Logger { throw Error('You are not authorized to issue this Did'); } - const key = keys.filter((entry) => entry.ethereumAddress.toLocaleLowerCase() - === this.options.signerIdentity.underlyingAccount.toLocaleLowerCase())[0]; + const key = keys.filter((entry) => { + // Fallback for old DIDs still using publicKeyHex + if (entry.ethereumAddress) { + return entry.ethereumAddress.toLocaleLowerCase() === account; + } + if (entry.publicKeyHex) { + return entry.publicKeyHex.toLocaleLowerCase() === signaturePublicKey; + } + return false; + })[0]; if (!key) { throw Error('The signature key of the active account is not associated to its DID document.'); } diff --git a/src/vc/vc.ts b/src/vc/vc.ts index 14b2ffef..82e91c65 100644 --- a/src/vc/vc.ts +++ b/src/vc/vc.ts @@ -497,9 +497,10 @@ export class Vc extends Logger { * the active identity is found. */ private async getPublicKeyUriFromDid(issuerDid: string): Promise { - const signaturePublicKey = await this.options.signerIdentity.getPublicKey( + const signaturePublicKey = (await this.options.signerIdentity.getPublicKey( this.options.signerIdentity.underlyingAccount, - ); + )).toLocaleLowerCase(); + const account = this.options.signerIdentity.underlyingAccount.toLocaleLowerCase(); const doc = await this.options.did.getDidDocument(issuerDid); if (!(doc.authentication || doc.publicKey || doc.publicKey.length === 0)) { @@ -508,9 +509,15 @@ export class Vc extends Logger { } const key = doc.publicKey.filter( - (entry) => entry.ethereumAddress - === this.options.signerIdentity.underlyingAccount.toLowerCase() - || entry.publicKeyHex === signaturePublicKey, + (entry) => { + if (entry.ethereumAddress) { + return entry.ethereumAddress.toLocaleLowerCase() === account; + } + if (entry.publicKeyHex) { + return entry.publicKeyHex.toLocaleLowerCase() === signaturePublicKey; + } + return false; + }, )[0]; if (!key) { From 97970808ad1178dc7d06b8b72f840956b7e6d680 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Fri, 14 Feb 2020 13:43:03 +0100 Subject: [PATCH 043/104] Added more detailed explanation of sharings object structure [CORE-948] --- docs/contracts/sharing.rst | 64 +++++++++++++------ src/contracts/digital-twin/container.spec.ts | 7 +- .../digital-twin/digital-twin.spec.ts | 7 +- 3 files changed, 48 insertions(+), 30 deletions(-) diff --git a/docs/contracts/sharing.rst b/docs/contracts/sharing.rst index c8a0df4f..e08d8455 100644 --- a/docs/contracts/sharing.rst +++ b/docs/contracts/sharing.rst @@ -15,6 +15,9 @@ Sharing * - Examples - `sharing.spec.ts `_ +.. _Sharing_example: + + For getting a better understanding about how Sharings and Multikeys work, have a look at `Security `_ in the evan.network wiki. Following is a sample for a sharing info with these properties: @@ -36,12 +39,17 @@ Following is a sample for a sharing info with these properties: * ``secret area`` - available for all members * ``super secret area`` - available for ``0x03`` +Keep in mind, that an actual sharings object only stores the sha3-hashes of every property. For example, sharings for the user `0x01` were actually to be found +at the property `"0x5fe7f977e71dba2ea1a68e21057beebb9be2ac30c6410aa38d4f3fbe41dcffd2"`. +For the sake of understanding, the following sample uses clear text properties. For an example of an actual sharings object, please refer to the :ref:`getSharings ` +example section. + .. code-block:: typescript { "0x01": { - "82745": { - "*": { + "*": { + "82745": { "private": "secret for 0x01, starting from block 82745 for all data", "cryptoInfo": { "originator": "0x01,0x01", @@ -50,16 +58,18 @@ Following is a sample for a sharing info with these properties: } } }, - "90000": { - "secret area": { + "secret area": { + "90000": { "private": "secret for 0x01, starting from block 90000 for 'secret area'", "cryptoInfo": { "originator": "0x01,0x01", "keyLength": 256, "algorithm": "aes-256-cbc" } - }, - "super secret area": { + } + }, + "super secret area": { + "90000": { "private": "secret for 0x01, starting from block 90000 for 'super secret area'", "cryptoInfo": { "originator": "0x01,0x01", @@ -70,8 +80,8 @@ Following is a sample for a sharing info with these properties: } }, "0x02": { - "82745": { - "*": { + "*": { + "82745": { "private": "secret for 0x02, starting from block 82745 for all data", "cryptoInfo": { "originator": "0x01,0x02", @@ -80,16 +90,18 @@ Following is a sample for a sharing info with these properties: } } }, - "90000": { - "secret area": { + "secret area": { + "90000": { "private": "secret for 0x02, starting from block 90000 for 'secret area'", "cryptoInfo": { "originator": "0x01,0x02", "keyLength": 256, "algorithm": "aes-256-cbc" } - }, - "super secret area": { + } + }, + "super secret area": { + "90000": { "private": "secret for 0x02, starting from block 90000 for 'super secret area'", "cryptoInfo": { "originator": "0x01,0x02", @@ -97,11 +109,11 @@ Following is a sample for a sharing info with these properties: "algorithm": "aes-256-cbc" } } - }, + } }, "0x03": { - "90000": { - "secret area": { + "secret area": { + "90000": { "private": "secret for 0x03, starting from block 90000 for 'secret area'", "cryptoInfo": { "originator": "0x01,0x03", @@ -121,7 +133,7 @@ There are two functions to share keys with another user: - :ref:`extendSharing ` is used to edit a sharings configuration that has been pulled or "checked out" with :ref:`getSharingsFromContract `. Hash keys have to be shared manually, if required. :ref:`extendSharing ` make no transaction, so the contract isn't updated - this has to be done with :ref:`saveSharingsToContract `. See function documentation :ref:`below ` for an example with hash key and storing updates. -Be careful when performing multiple updates to sharings synchronously. As sharings are retrieved as a single file from a smart contract, updated and then saved back to it, doing two or more updates in parallel may overwrite each other and lead to unexpected and most probably undesired results. +Be careful when performing multiple updates to sharings synchronously. As sharings are retrieved as a single file from a smart contract, updated and then saved back to it, doing two or more updates in parallel may overwrite each other and lead to unexpected and most probably undesired results. Perform sharing updates for the same contracts **one after another**, this goes for :ref:`addSharing ` **and** for :ref:`extendSharing `. When wishing to speed things up, :ref:`extendSharing ` can be used, but its updates need to be performed synchronously as well. Keep in mind, that single updates will be made off-chain and therefore be performed much faster than multiple updates with :ref:`addSharing `. @@ -610,7 +622,7 @@ Parameters Returns ------- -``Promise`` returns ``any``: sharings as an object +``Promise`` returns ``any``: sharings as an object. For more details, refer to the :ref:`example at the top of the page `. ------- Example @@ -620,10 +632,22 @@ Example const randomSecret = `super secret; ${Math.random()}`; await sharing.addSharing(testAddress, accounts[1], accounts[0], '*', 0, randomSecret); + await sharing.addSharing(testAddress, accounts[1], accounts[0], 'test', 100, randomSecret); const sharings = await sharing.getSharings(contract.options.address, null, null, null, sharingId); - //Output: - { '0x6760305476495b089868ae42c2293d5e8c1c7bf9bfe51a9ad85b36d85f4113cb': - { '0x04994f67dc55b09e814ab7ffc8df3686b4afb2bb53e60eae97ef043fe03fb829': { '0': randomSecret } } } + /* Output: + { + '0x2260228fd705cd9420a07827b8e64e808daba1b6675c3956783cc09fcc56a327': { // sha3(contract owner) + '0x04994f67dc55b09e814ab7ffc8df3686b4afb2bb53e60eae97ef043fe03fb829': { hashKey: [Object] }, + '0x9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658': { '0': [Object] }, // sha3('test') + '0x02016836a56b71f0d02689e69e326f4f4c1b9057164ef592671cf0d37c8040c0': { '0': [Object] } // additional unshared field + }, + '0xb45ce1cd2e464ce53a8102a5f855c112a2a384c36923fe5c6e249c2a9286369e': { // sha3(accounts[1]) + '0x04994f67dc55b09e814ab7ffc8df3686b4afb2bb53e60eae97ef043fe03fb829': { hashKey: [Object] }, // '*' + '0x9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658': { // sha3('test') + '100': [Object] // Valid from block 100 + } + } + */ diff --git a/src/contracts/digital-twin/container.spec.ts b/src/contracts/digital-twin/container.spec.ts index de19372c..417c8020 100644 --- a/src/contracts/digital-twin/container.spec.ts +++ b/src/contracts/digital-twin/container.spec.ts @@ -22,11 +22,8 @@ import * as chaiAsPromised from 'chai-as-promised'; import { expect, use } from 'chai'; import { promisify } from 'util'; import { readFile } from 'fs'; -import { - Executor, - Ipfs, -} from '@evan.network/dbcp'; - +import { Ipfs } from '../../dfs/ipfs'; +import { Executor } from '../..'; import { accounts } from '../../test/accounts'; import { TestUtils } from '../../test/test-utils'; import { VerificationsStatus } from '../../verifications/verifications'; diff --git a/src/contracts/digital-twin/digital-twin.spec.ts b/src/contracts/digital-twin/digital-twin.spec.ts index 5b8d071e..ca4973c0 100644 --- a/src/contracts/digital-twin/digital-twin.spec.ts +++ b/src/contracts/digital-twin/digital-twin.spec.ts @@ -20,11 +20,8 @@ import 'mocha'; import * as chaiAsPromised from 'chai-as-promised'; import { expect, use } from 'chai'; -import { - Executor, - Ipfs, -} from '@evan.network/dbcp'; - +import { Ipfs } from '../../dfs/ipfs'; +import { Executor } from '../..'; import { accounts } from '../../test/accounts'; import { configTestcore as config } from '../../config-testcore'; import { Container } from './container'; From 4d6dc88e73c29f51880b621b1ae3f71bcd0532af Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Fri, 14 Feb 2020 13:52:05 +0100 Subject: [PATCH 044/104] Fixed test [CORE-946] --- src/did/did.spec.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/did/did.spec.ts b/src/did/did.spec.ts index fd0686bc..cf778fd0 100644 --- a/src/did/did.spec.ts +++ b/src/did/did.spec.ts @@ -206,8 +206,7 @@ describe('DID Resolver', function test() { ); // Try to retrieve it - expect(runtimes[0].did.getDidDocument(accounts0Did)) - .to.be.eventually.rejectedWith('Invalid proof'); + await expect(runtimes[0].did.getDidDocument(accounts0Did)).to.be.rejectedWith('Invalid proof'); }); }); From 64cdd093eb128b3cdda31f77bf4b7b3006ab5198 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Fri, 14 Feb 2020 13:55:02 +0100 Subject: [PATCH 045/104] Update docs/profile/did.rst Co-Authored-By: wulfraem --- docs/profile/did.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/profile/did.rst b/docs/profile/did.rst index 7914c1c3..2278aaad 100644 --- a/docs/profile/did.rst +++ b/docs/profile/did.rst @@ -246,7 +246,7 @@ setDidDocument did.setDidDocument(did, document); Store given DID document for given DID. -If the document misses the property `created`, it will automaticallybe appended. +If the document misses the property `created`, it will automatically be appended. The `updated` property will be updated accordingly. A proof over the DID document will be generated automatically and appended to the document. From ace926004611849ab3265ca324c06c578db91a2c Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Fri, 14 Feb 2020 13:56:34 +0100 Subject: [PATCH 046/104] Fixed method annotations [CORE-946] --- src/did/did.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/did/did.ts b/src/did/did.ts index 7b8be023..35789073 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -446,10 +446,10 @@ export class Did extends Logger { /** - * Creates a new `VcProof` object for a given VC document, including generating a JWT token over + * Creates a new `DidProof` object for a given DID document, including generating a JWT token over * the whole document. * - * @param {DidDocument} Did The VC document to create the proof for. + * @param {DidDocument} Did The DID document to create the proof for. * @param {DidProofType} proofType Specify if you want a proof type different from the * default one. * @returns {DidProof} A proof object containing a JWT. @@ -667,9 +667,9 @@ export class Did extends Logger { } /** - * Validates the JWS of a VC Document proof + * Validates the JWS of a DID Document proof * - * @param {VcDocument} document The VC Document + * @param {any} document The DID Document * @returns {Promise} Resolves when done */ private async validateProof(document: any): Promise { @@ -689,7 +689,7 @@ export class Did extends Logger { { resolver: getResolver(this) }, ); - // fails if signed payload and the VC differ + // fails if signed payload and the DID document differ const payload = { ...verifiedSignature.payload.didDocument, }; From d2732db6fa4a23a16b6b5b2c2d7cee82aec800a6 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Fri, 14 Feb 2020 14:02:10 +0100 Subject: [PATCH 047/104] Added new did properties feature to versions.md [CORE-946] --- VERSIONS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/VERSIONS.md b/VERSIONS.md index 645269af..9787283d 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -2,6 +2,8 @@ ## Next Version - add methods `deactivateDidDocument` and `didIsDeactivated` to check and handle DID deactivation status +- added additional properties `updated`, `created`, and `proof` to DID documents +- added proof validation to `getDidDocument` (only for documents that actually contain a proof) ### Features From 48452a0926b7234f0d40d265b26ce10d272fd4da Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Fri, 14 Feb 2020 14:05:11 +0100 Subject: [PATCH 048/104] Updated docs [CORE-946] --- docs/profile/did.rst | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/profile/did.rst b/docs/profile/did.rst index 2278aaad..bfe48721 100644 --- a/docs/profile/did.rst +++ b/docs/profile/did.rst @@ -37,6 +37,7 @@ Parameters ---------- #. ``options`` - ``DidOptions``: options for Did constructor. + * ``accountStore`` - |source accountStore|_: |source accountStore|_ instance * ``contractLoader`` - |source contractLoader|_: |source contractLoader|_ instance * ``dfs`` - |source dfsInterface|_: |source dfsInterface|_ instance * ``executor`` - |source executor|_: |source executor|_ instance @@ -462,6 +463,9 @@ Example .. required for building markup +.. |source accountStore| replace:: ``AccountStore`` +.. _source accountStore: ../blockchain/account-store.html + .. |source contractLoader| replace:: ``ContractLoader`` .. _source contractLoader: ../contracts/contract-loader.html @@ -485,10 +489,5 @@ Example .. |source web3| replace:: ``Web3`` .. _source web3: https://github.com/ethereum/web3.js/ -.. _source nameResolver: ../blockchain/name-resolver.html - -.. |source signerIdentity| replace:: ``SignerIdentity`` -.. _source signerIdentity: ../blockchain/signer-identity.html -.. |source web3| replace:: ``Web3`` -.. _source web3: https://github.com/ethereum/web3.js/ +.. _source nameResolver: ../blockchain/name-resolver.html From 64365fddfbf962f9b56f91b738dbebac65d2f6f2 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Fri, 14 Feb 2020 14:42:56 +0100 Subject: [PATCH 049/104] Removed case change when converting identities to dids [CORE-1004] --- src/did/did.spec.ts | 10 ++++++++++ src/did/did.ts | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/did/did.spec.ts b/src/did/did.spec.ts index 2d25200c..98c113cf 100644 --- a/src/did/did.spec.ts +++ b/src/did/did.spec.ts @@ -92,6 +92,16 @@ describe('DID Resolver', function test() { runtimes[1].did = new Did(options1, { registryAddress: registry.options.address }); }); + describe('when using general did functionality', async () => { + it('preserves the checksum\'s case when converting DIDs to identities and vice versa', async () => { + const identityMixedCase = '0xce651a6E82925e7c7acf462426e964C89E19F576d68941130013af42e812ffc1'; + const did = await runtimes[0].did.convertIdentityToDid(identityMixedCase); + const identityAfter = await runtimes[0].did.convertDidToIdentity(did); + + expect(identityAfter).to.eq(identityMixedCase); + }); + }); + describe('when storing did documents for account identities', async () => { it('allows to store a DID document for the own identity', async () => { const document = await runtimes[0].did.getDidDocumentTemplate(); diff --git a/src/did/did.ts b/src/did/did.ts index 1a53311f..39a098db 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -154,7 +154,7 @@ export class Did extends Logger { * did:evan:testcore:0x000000000000000000000000000000000000001234 */ public async convertIdentityToDid(identity: string): Promise { - return `did:evan:${await this.getDidInfix()}${identity.toLowerCase()}`; + return `did:evan:${await this.getDidInfix()}${identity}`; } /** From df80095f9014b233a7bff56ac7996e0594a37289 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Fri, 14 Feb 2020 15:10:07 +0100 Subject: [PATCH 050/104] DID to identity returns identity in checksum case --- src/did/did.spec.ts | 16 ++++++++++------ src/did/did.ts | 4 ++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/did/did.spec.ts b/src/did/did.spec.ts index 98c113cf..f8f0292b 100644 --- a/src/did/did.spec.ts +++ b/src/did/did.spec.ts @@ -93,12 +93,16 @@ describe('DID Resolver', function test() { }); describe('when using general did functionality', async () => { - it('preserves the checksum\'s case when converting DIDs to identities and vice versa', async () => { - const identityMixedCase = '0xce651a6E82925e7c7acf462426e964C89E19F576d68941130013af42e812ffc1'; - const did = await runtimes[0].did.convertIdentityToDid(identityMixedCase); - const identityAfter = await runtimes[0].did.convertDidToIdentity(did); - - expect(identityAfter).to.eq(identityMixedCase); + it('returns a checksum ethereum address when converting dids to identities', async () => { + const identityChecksumCase = '0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d'; + const identityLowerCase = identityChecksumCase.toLowerCase(); + const didMixed = await runtimes[0].did.convertIdentityToDid(identityChecksumCase); + const didLower = await runtimes[0].did.convertIdentityToDid(identityLowerCase); + const identityAfter = await runtimes[0].did.convertDidToIdentity(didMixed); + const identityLowerCaseAfter = await runtimes[0].did.convertDidToIdentity(didLower); + + expect(identityAfter).to.eq(identityChecksumCase); + expect(identityLowerCaseAfter).to.eq(identityChecksumCase); }); }); diff --git a/src/did/did.ts b/src/did/did.ts index 39a098db..cb6b6f78 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -142,7 +142,7 @@ export class Did extends Logger { throw new Error(`DIDs environment "${environment} does not match ${didEnvironment}`); } - return identity; + return this.options.web3.utils.toChecksumAddress(identity); } /** @@ -154,7 +154,7 @@ export class Did extends Logger { * did:evan:testcore:0x000000000000000000000000000000000000001234 */ public async convertIdentityToDid(identity: string): Promise { - return `did:evan:${await this.getDidInfix()}${identity}`; + return `did:evan:${await this.getDidInfix()}${identity.toLowerCase()}`; } /** From 0a8d38393b1ea6b0834112adcc0adddd5e986f80 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Fri, 14 Feb 2020 15:11:35 +0100 Subject: [PATCH 051/104] Update docs/profile/did.rst Co-Authored-By: wulfraem --- docs/profile/did.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/profile/did.rst b/docs/profile/did.rst index bfe48721..c073f706 100644 --- a/docs/profile/did.rst +++ b/docs/profile/did.rst @@ -489,5 +489,3 @@ Example .. |source web3| replace:: ``Web3`` .. _source web3: https://github.com/ethereum/web3.js/ - -.. _source nameResolver: ../blockchain/name-resolver.html From a08597593205feb248151543521cfacb8894901c Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Fri, 14 Feb 2020 15:14:24 +0100 Subject: [PATCH 052/104] Fix doc [CORE-946] --- docs/profile/did.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/profile/did.rst b/docs/profile/did.rst index bfe48721..c073f706 100644 --- a/docs/profile/did.rst +++ b/docs/profile/did.rst @@ -489,5 +489,3 @@ Example .. |source web3| replace:: ``Web3`` .. _source web3: https://github.com/ethereum/web3.js/ - -.. _source nameResolver: ../blockchain/name-resolver.html From 1a561b91104628b33edee91958729bc504af8aa6 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Fri, 14 Feb 2020 15:36:55 +0100 Subject: [PATCH 053/104] Update src/did/did.ts Co-Authored-By: wulfraem --- src/did/did.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/did/did.ts b/src/did/did.ts index cb6b6f78..eab27d3f 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -142,7 +142,10 @@ export class Did extends Logger { throw new Error(`DIDs environment "${environment} does not match ${didEnvironment}`); } - return this.options.web3.utils.toChecksumAddress(identity); + // convert identities, that are addresses to checksum address + return identity.length === 42 + ? this.options.web3.utils.toChecksumAddress(identity) + : identity; } /** From e0b708c4dd098ca32f7d8852207742be30954fb2 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Fri, 14 Feb 2020 17:28:51 +0100 Subject: [PATCH 054/104] Ignore case when comparing vc issuer addresses [CIRE-1004] --- src/vc/vc.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vc/vc.ts b/src/vc/vc.ts index e00ac898..f16aac14 100644 --- a/src/vc/vc.ts +++ b/src/vc/vc.ts @@ -457,7 +457,7 @@ export class Vc extends Logger { throw Error(`Invalid issuer DID: ${vc.issuer.id}`); } - if (this.options.signerIdentity.activeIdentity.toLowerCase() !== issuerIdentity) { + if (this.options.signerIdentity.activeIdentity.toLowerCase() !== issuerIdentity.toLowerCase()) { throw Error('You are not authorized to issue this VC'); } From 5d163656eaa110f08f2cd39a48b712f81b979309 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Mon, 17 Feb 2020 09:04:14 +0100 Subject: [PATCH 055/104] Update src/vc/vc.ts Co-Authored-By: wulfraem --- src/vc/vc.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vc/vc.ts b/src/vc/vc.ts index f16aac14..c7ffe468 100644 --- a/src/vc/vc.ts +++ b/src/vc/vc.ts @@ -457,7 +457,7 @@ export class Vc extends Logger { throw Error(`Invalid issuer DID: ${vc.issuer.id}`); } - if (this.options.signerIdentity.activeIdentity.toLowerCase() !== issuerIdentity.toLowerCase()) { + if (this.options.signerIdentity.activeIdentity !== issuerIdentity) { throw Error('You are not authorized to issue this VC'); } From 6861d974ca587c35b1732f8e434a29b5131ee8a5 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Tue, 18 Feb 2020 11:06:16 +0100 Subject: [PATCH 056/104] Fixed bug for getListEntry and added test [CORE-1009] --- src/contracts/digital-twin/container.spec.ts | 21 +++++++++++++-- src/contracts/digital-twin/container.ts | 27 ++++++++++++-------- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/contracts/digital-twin/container.spec.ts b/src/contracts/digital-twin/container.spec.ts index 417c8020..85a49c54 100644 --- a/src/contracts/digital-twin/container.spec.ts +++ b/src/contracts/digital-twin/container.spec.ts @@ -346,6 +346,19 @@ describe('Container', function test() { expect(await container.getListEntries('testList')).to.deep.eq(randomNumbers); }); + it('can set entries and get single entry for properties defined in (custom) template', async () => { + const plugin: ContainerPlugin = JSON.parse(JSON.stringify(Container.plugins.metadata)); + plugin.template.properties.testList = { + dataSchema: { type: 'array', items: { type: 'number' } }, + permissions: { 0: ['set'] }, + type: 'list', + }; + const container = await Container.create(runtimes[owner], { ...defaultConfig, plugin }); + const randomNumbers = [...Array(8)].map(() => Math.floor(Math.random() * 1e12)); + await container.addListEntries('testList', randomNumbers); + expect(await container.getListEntry('testList', 0)).to.deep.eq(randomNumbers[0]); + }); + it('can set list entries if not defined in template (auto adds properties)', async () => { const container = await Container.create(runtimes[owner], defaultConfig); const randomString = Math.floor(Math.random() * 1e12).toString(36); @@ -1460,9 +1473,13 @@ describe('Container', function test() { { accountId: owner, readWrite: ['testField1', 'testField2'] }, { accountId: consumer, readWrite: ['testField1'], read: ['testField2'] }, ]; - const shareConfigs = await container.getContainerShareConfigs(); + const byAccountId = (e1, e2) => (e1.accountId < e2.accountId ? -1 : 1); - expect(shareConfigs.sort(byAccountId)).to.deep.eq(expected.sort(byAccountId)); + const shareConfigs = await (await container.getContainerShareConfigs()).sort(byAccountId); + shareConfigs[0].readWrite = shareConfigs[0].readWrite.sort( + (prop1, prop2) => (prop1 < prop2 ? -1 : 1), + ); + expect(shareConfigs).to.deep.eq(expected.sort(byAccountId)); }); }); }); diff --git a/src/contracts/digital-twin/container.ts b/src/contracts/digital-twin/container.ts index bf2c9bb0..78479305 100644 --- a/src/contracts/digital-twin/container.ts +++ b/src/contracts/digital-twin/container.ts @@ -1563,22 +1563,29 @@ export class Container extends Logger { * encryption. * * @param {any} subSchema schema to check in - * @param {any} toInspect value to inspect + * @param {any} toInspect value(s) to inspect * @param {Function} toApply function, that will be applied to values in `toInspect` */ private async applyIfEncrypted(subSchema: any, toInspect: any, toApply: Function) { if (this.isEncryptedFile(subSchema)) { // simple entry return toApply(toInspect); - } if (subSchema.type === 'array' && this.isEncryptedFile(subSchema.items)) { - // list/array with entries - return Promise.all(toInspect.map((entry) => toApply(entry))); - } - // check if nested - if (subSchema.type === 'array') { - return Promise.all(toInspect.map((entry) => this.applyIfEncrypted( - subSchema.items, entry, toApply, - ))); + } if (subSchema.type === 'array') { + const isArray = Array.isArray(toInspect); // handle both getListEntry and getListEntries + if (this.isEncryptedFile(subSchema.items)) { + // list/array with entries + return isArray + ? Promise.all(toInspect.map((entry) => toApply(entry))) + : toApply(toInspect); + } + // check if nested + return isArray + ? Promise.all(toInspect.map((entry) => this.applyIfEncrypted( + subSchema.items, entry, toApply, + ))) + : this.applyIfEncrypted( + subSchema.items, toInspect, toApply, + ); } if (subSchema.type === 'object') { // check objects subproperties const transformed = {}; From ce1a980b23ae266c46589794e2cd0596867540d7 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Tue, 18 Feb 2020 11:08:11 +0100 Subject: [PATCH 057/104] update versions [CORE-1009] --- VERSIONS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/VERSIONS.md b/VERSIONS.md index a52ef4da..f2f106b8 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -12,6 +12,7 @@ ### Fixes - fix buffer-to-string conversion, try to decode to `utf8`, if this fails, decode it to `binary` - add `member-ordering` rule to eslint config +- fix `container.getListEntry` to not throw an exception on call anymore ### Deprecations From 60629c2e1aa93b2b277ecfa5bda7c5f45e8bddc4 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Tue, 18 Feb 2020 11:33:32 +0100 Subject: [PATCH 058/104] Ensure lowercase ethereum address on fallback did documents (and behaviour fix) [CORE-1028] --- src/did/did.spec.ts | 2 +- src/did/did.ts | 11 ++++++++--- src/verifications/verifications.ts | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/did/did.spec.ts b/src/did/did.spec.ts index fe5731ed..a87fbad7 100644 --- a/src/did/did.spec.ts +++ b/src/did/did.spec.ts @@ -172,7 +172,7 @@ describe('DID Resolver', function test() { const did = await runtimes[0].did.convertIdentityToDid(randomIdentity); const identityDidDocP = runtimes[0].did.getDidDocument(did); await expect(identityDidDocP).to.be.rejectedWith( - new RegExp(`^No record found for ${randomIdentity}\\. Is this a valid identity address\\?$`, 'i'), + new RegExp(`^Unable to resolve: Invalid DID ${did}`, 'i'), ); }); diff --git a/src/did/did.ts b/src/did/did.ts index 8bae71ca..874c5408 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -529,8 +529,13 @@ export class Did extends Logger { */ private async getDefaultDidDocument(did: string): Promise { const identity = await this.convertDidToIdentity(did); - const controllerIdentity = await this.options.verifications - .getOwnerAddressForIdentity(identity); + let controllerIdentity; + try { + controllerIdentity = await this.options.verifications + .getOwnerAddressForIdentity(identity); + } catch (e) { + throw Error(`Unable to resolve: Invalid DID ${did}`); + } if (identity.length === 42) { // Identity is account identity and therefore self-sovereign @@ -541,7 +546,7 @@ export class Did extends Logger { "id": "${did}#key-1", "type": "Secp256k1VerificationKey2018", "owner": "${did}", - "ethereumAddress": "${controllerIdentity}" + "ethereumAddress": "${controllerIdentity.toLowerCase()}" }], "authentication": [ "${did}#key-1" diff --git a/src/verifications/verifications.ts b/src/verifications/verifications.ts index f16e6047..59eb54cd 100644 --- a/src/verifications/verifications.ts +++ b/src/verifications/verifications.ts @@ -1289,7 +1289,7 @@ export class Verifications extends Logger { } if (ownerAddress === nullAddress) { - throw Error(`No record found for ${identityAddress}. Is this a valid identity address?`); + throw Error(`No owner found for ${identityAddress}.`); } return ownerAddress; From 575caca35e910d597147aca3b3261713e8c80829 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Tue, 18 Feb 2020 14:37:31 +0100 Subject: [PATCH 059/104] Fixed setVerificationAndVc doc and code doc --- docs/profile/verifications.rst | 24 +++++++++++++++--------- src/verifications/verifications.ts | 6 +++--- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/docs/profile/verifications.rst b/docs/profile/verifications.rst index 931550aa..14c9d0a9 100644 --- a/docs/profile/verifications.rst +++ b/docs/profile/verifications.rst @@ -410,16 +410,18 @@ Example .. _verifications_setVerificationAndVc: -setVerification +setVerificationAndVc ================================================================================ .. code-block:: typescript - verifications.setVerificationAndVc(issuer, subject, topic, expirationDate, verificationValue, descriptionDomain, disableSubVerifications); + verifications.setVerificationAndVc(issuer, subject, topic, expirationDate, verificationValue, descriptionDomain, disableSubVerifications, isIdentity, uri); -Sets or creates a verification along with a verifiable credential; the verification creation requires the issuer to have permissions for the parent verification (if verification name seen as a path, the parent 'folder'). +Sets or creates a verification along with a verifiable credential. +Creating a verification requires the issuer to have permissions for the parent verification (the parent directory, if the verification name is seen as a path). -The "verificationValue" field can also be set to a custom JSON object with any data. The verificationValue works as same as in the setVerification function however in this function it is also added to the VC document. +The `verificationValue` property can also be a custom JSON object with any data. +It is treated just like in the :ref:`setVerification ` function, except that this function also adds it to the VC document. ---------- Parameters @@ -449,11 +451,15 @@ Example // accounts[0] issues verification '/company' for accounts[1] const verification = verifications.setVerificationAndVc(accounts[0], accounts[1], '/company'); - -``{ vcId: - 'vc:evan:testcore:0x9becd31c7e5b6a1fe8ee9e76f1af56bcf84b6718548dbdfd1412f935b515ebe0', - verificationId: - '0x5373812c3cba3fdee77730a45e7bd05cf52a5f2195abf1f623a9b22d94cea939' };`` + console.log(verification); + /* Output: + { + vcId: + 'vc:evan:testcore:0x9becd31c7e5b6a1fe8ee9e76f1af56bcf84b6718548dbdfd1412f935b515ebe0', + verificationId: + '0x5373812c3cba3fdee77730a45e7bd05cf52a5f2195abf1f623a9b22d94cea939' + } + */ diff --git a/src/verifications/verifications.ts b/src/verifications/verifications.ts index 59eb54cd..e6edadb8 100644 --- a/src/verifications/verifications.ts +++ b/src/verifications/verifications.ts @@ -1648,9 +1648,9 @@ export class Verifications extends Logger { } /** - * Sets or creates a verification and a Verfiable credential document; - * this requires the issuer to have permissions for the parent - * verification (if verification name seen as a path, the parent 'folder'). + * Sets or creates a verification along with a verifiable credential. + * Creating a verification requires the issuer to have permissions for the parent verification + * (the parent directory, if the verification name is seen as a path). * * @param {string} issuer issuer of the verification * @param {string} subject subject of the verification and the From 36ba1b2b2f248a22a8a46661623002fd6df4261c Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Tue, 18 Feb 2020 17:21:16 +0100 Subject: [PATCH 060/104] Fixed test [CORE-1028] --- src/contracts/digital-twin/digital-twin.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/contracts/digital-twin/digital-twin.spec.ts b/src/contracts/digital-twin/digital-twin.spec.ts index db54a13b..e2efbaee 100644 --- a/src/contracts/digital-twin/digital-twin.spec.ts +++ b/src/contracts/digital-twin/digital-twin.spec.ts @@ -371,7 +371,7 @@ describe('DigitalTwin', function test() { twinIdentity, ); expect(twinIdentityOwnerPromise) - .to.be.eventually.rejectedWith('No record found for'); + .to.be.eventually.rejectedWith('No owner found for'); twinDescriptionHash = await localRuntime.executor.executeContractCall( twinContract, @@ -473,7 +473,7 @@ describe('DigitalTwin', function test() { twinIdentity, ); expect(twinIdentityOwnerPromise) - .to.be.eventually.rejectedWith('No record found for'); + .to.be.eventually.rejectedWith('No owner found for'); const twinDescriptionHash = await localRuntime.executor.executeContractCall( twinContract, From 791eca271bdf209e2a849aa55b75ca975377f838 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Wed, 19 Feb 2020 08:49:49 +0100 Subject: [PATCH 061/104] Update src/contracts/digital-twin/digital-twin.spec.ts Co-Authored-By: wulfraem --- src/contracts/digital-twin/digital-twin.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contracts/digital-twin/digital-twin.spec.ts b/src/contracts/digital-twin/digital-twin.spec.ts index e2efbaee..aa0904c8 100644 --- a/src/contracts/digital-twin/digital-twin.spec.ts +++ b/src/contracts/digital-twin/digital-twin.spec.ts @@ -472,7 +472,7 @@ describe('DigitalTwin', function test() { const twinIdentityOwnerPromise = localRuntime.verifications.getOwnerAddressForIdentity( twinIdentity, ); - expect(twinIdentityOwnerPromise) + await expect(twinIdentityOwnerPromise) .to.be.eventually.rejectedWith('No owner found for'); const twinDescriptionHash = await localRuntime.executor.executeContractCall( From 06ef659e18e90a09218f5c9325bf49c5ef55da87 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Wed, 19 Feb 2020 08:49:55 +0100 Subject: [PATCH 062/104] Update src/contracts/digital-twin/digital-twin.spec.ts Co-Authored-By: wulfraem --- src/contracts/digital-twin/digital-twin.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contracts/digital-twin/digital-twin.spec.ts b/src/contracts/digital-twin/digital-twin.spec.ts index aa0904c8..97d9d94d 100644 --- a/src/contracts/digital-twin/digital-twin.spec.ts +++ b/src/contracts/digital-twin/digital-twin.spec.ts @@ -370,7 +370,7 @@ describe('DigitalTwin', function test() { const twinIdentityOwnerPromise = localRuntime.verifications.getOwnerAddressForIdentity( twinIdentity, ); - expect(twinIdentityOwnerPromise) + await expect(twinIdentityOwnerPromise) .to.be.eventually.rejectedWith('No owner found for'); twinDescriptionHash = await localRuntime.executor.executeContractCall( From e092ce5548c9eea9d6d321636508ef27323717bf Mon Sep 17 00:00:00 2001 From: OmairLiaquatAli Date: Wed, 19 Feb 2020 14:43:25 +0100 Subject: [PATCH 063/104] update to container document -[CORE-1008] --- docs/contracts/container.rst | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/docs/contracts/container.rst b/docs/contracts/container.rst index bcad845a..97b2c1f1 100644 --- a/docs/contracts/container.rst +++ b/docs/contracts/container.rst @@ -722,10 +722,10 @@ getListEntries .. code-block:: typescript - container.getListEntries(contract, listName, accountId[, dfsStorage, encryptedHashes, count, offset, reverse]); + container.getListEntries(listName, count, offset, reverse); Return list entries from contract. -Note, that in the current implementation, this function retrieves the entries one at a time and may take a longer time when querying large lists, so be aware of that, when you retrieve lists with many entries. +Note, that in the current implementation, this function retrieves the entries one at a time and may take a longer time when querying large lists, so be aware of that, when you retrieve lists with many entries. Keep in mind if only the listName is passed it will not retrieve the entire list instead only the first 10 elements in the list. ---------- Parameters @@ -753,21 +753,27 @@ Example // Output: // 0 - const sampleValue = { - foo: 'sample', - bar: 123, - }; + const sampleValue = [ 'Hello', 'welcome', 'to', 'evan', 'network' ]; await container.addListEntries(listName, [sampleValue]); console.log(await container.getListEntryCount(listName)); // Output: - // 1 + // 5 console.dir(await container.getListEntries(listName)); // Output: - // [{ - // foo: 'sample', - // bar: 123, - // }] + // [ 'Hello', 'welcome', 'to', 'evan', 'network' ] + + console.log(await container.getListEntries(listName, 2)); + //Output: + // [ 'Hello', 'welcome' ] + + console.log(await container.getListEntries(listName, 10, 2)); + //Output + // [ 'to', 'evan', 'network' ] + + console.log(await container.getListEntries('testList', 10, 0, true)); + //Output: + // [ 'network', 'evan', 'to', 'welcome', 'Hello' ] From a3d07862f53eb3b327e2985f5b156ffc21b42f4a Mon Sep 17 00:00:00 2001 From: OmairLiaquatAli Date: Wed, 19 Feb 2020 14:51:53 +0100 Subject: [PATCH 064/104] update version.md -[CORE-1008] --- VERSIONS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/VERSIONS.md b/VERSIONS.md index f2f106b8..b1004c84 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -10,6 +10,7 @@ - added proof validation to `getDidDocument` (only for documents that actually contain a proof) ### Fixes +- fix `getListEntries` in the api docs - fix buffer-to-string conversion, try to decode to `utf8`, if this fails, decode it to `binary` - add `member-ordering` rule to eslint config - fix `container.getListEntry` to not throw an exception on call anymore From 776273d7b330cc70872ee3c86d8cd1f236322eb1 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Wed, 19 Feb 2020 15:54:07 +0100 Subject: [PATCH 065/104] Updated ipfs.get method to match interface and improved test [CORE-775] --- src/contracts/data-contract/data-contract.ts | 2 +- src/dfs/ipfs.spec.ts | 3 +++ src/dfs/ipfs.ts | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/contracts/data-contract/data-contract.ts b/src/contracts/data-contract/data-contract.ts index 8d127e77..57e24478 100644 --- a/src/contracts/data-contract/data-contract.ts +++ b/src/contracts/data-contract/data-contract.ts @@ -824,7 +824,7 @@ export class DataContract extends BaseContract { * @param hash Reference to the DFS file. Can be either an IPFS or a bytes32 hash. */ public async getDfsContent(hash: string): Promise { - return this.options.dfs.get(hash); + return this.options.dfs.get(hash) as Promise; } private async validate(description: any, fieldName: string, toCheck: any[]) { diff --git a/src/dfs/ipfs.spec.ts b/src/dfs/ipfs.spec.ts index 726ea0b3..491b4561 100644 --- a/src/dfs/ipfs.spec.ts +++ b/src/dfs/ipfs.spec.ts @@ -94,9 +94,12 @@ describe('IPFS handler', function test() { it('should be able to get files', async () => { const randomContent = Math.random().toString(); + const randomBufferContent = Buffer.from(randomContent); const hash = await ipfs.add('test', Buffer.from(randomContent, 'utf-8')); const fileContent = await ipfs.get(hash); + const fileBufferContent = await ipfs.get(hash, true) as Buffer; expect(fileContent).to.eq(randomContent); + expect(fileBufferContent.equals(randomBufferContent)).to.be.true; }); it('should cache previous added files', async () => { diff --git a/src/dfs/ipfs.ts b/src/dfs/ipfs.ts index 51d4cd97..1ac39d71 100644 --- a/src/dfs/ipfs.ts +++ b/src/dfs/ipfs.ts @@ -184,7 +184,7 @@ export class Ipfs extends Logger implements DfsInterface { * * @return data as text */ - public async get(hash: string, returnBuffer = false): Promise { + public async get(hash: string, returnBuffer = false): Promise { const ipfsHash = hash.startsWith('Qm') ? hash : Ipfs.bytes32ToIpfsHash(hash); // check if the hash equals 0x000000000000000000000000000000000 From 854a934090ad9fa63b6d45c6025f76e89578a6b4 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Wed, 19 Feb 2020 16:01:58 +0100 Subject: [PATCH 066/104] Added await to expect.eventually statements [CORE-1028] --- src/contracts/digital-twin/digital-twin.spec.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/contracts/digital-twin/digital-twin.spec.ts b/src/contracts/digital-twin/digital-twin.spec.ts index e2efbaee..7277ae07 100644 --- a/src/contracts/digital-twin/digital-twin.spec.ts +++ b/src/contracts/digital-twin/digital-twin.spec.ts @@ -251,9 +251,9 @@ describe('DigitalTwin', function test() { expect(ownerDidDocument).not.to.be.null; expect(promise).not.to.be.rejected; - expect(promise).to.eventually.have.property('id').that.equals(did); - expect(promise).to.eventually.have.property('controller').that.equals(ownerDid); - expect(promise).to.eventually.have.property('authentication').that.include(ownerDidDocument.authentication[0]); + await expect(promise).to.eventually.have.property('id').that.equals(did); + await expect(promise).to.eventually.have.property('controller').that.equals(ownerDid); + await expect(promise).to.eventually.have.property('authentication').that.include(ownerDidDocument.authentication[0]); }); it.skip('can deactivate a created twin (also checks for hashes, unskip this as soon as' @@ -370,7 +370,7 @@ describe('DigitalTwin', function test() { const twinIdentityOwnerPromise = localRuntime.verifications.getOwnerAddressForIdentity( twinIdentity, ); - expect(twinIdentityOwnerPromise) + await expect(twinIdentityOwnerPromise) .to.be.eventually.rejectedWith('No owner found for'); twinDescriptionHash = await localRuntime.executor.executeContractCall( @@ -389,7 +389,7 @@ describe('DigitalTwin', function test() { localRuntime.dfs, ); const twinDid = await did.convertIdentityToDid(twinIdentity); - expect(did.didIsDeactivated(twinDid)).to.eventually.be.true; + await expect(did.didIsDeactivated(twinDid)).to.eventually.be.true; // Check if all pinned hashes have been unpinned const pinnedHashesAfterDeactivation = await getPinnedFileHashes(); @@ -472,7 +472,7 @@ describe('DigitalTwin', function test() { const twinIdentityOwnerPromise = localRuntime.verifications.getOwnerAddressForIdentity( twinIdentity, ); - expect(twinIdentityOwnerPromise) + await expect(twinIdentityOwnerPromise) .to.be.eventually.rejectedWith('No owner found for'); const twinDescriptionHash = await localRuntime.executor.executeContractCall( @@ -491,7 +491,7 @@ describe('DigitalTwin', function test() { localRuntime.dfs, ); const twinDid = await did.convertIdentityToDid(twinIdentity); - expect(did.didIsDeactivated(twinDid)).to.eventually.be.true; + await expect(did.didIsDeactivated(twinDid)).to.eventually.be.true; }); }); From 3c44243f1ea21b92bb3805b60790a80ec8fba9b8 Mon Sep 17 00:00:00 2001 From: Tobias Winkler Date: Fri, 21 Feb 2020 11:39:01 +0100 Subject: [PATCH 067/104] fix update verification keys for onboarding, when use identity is enabled - [CORE-635] --- src/onboarding.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/onboarding.ts b/src/onboarding.ts index bf0593af..7881345a 100644 --- a/src/onboarding.ts +++ b/src/onboarding.ts @@ -627,13 +627,17 @@ export class Onboarding extends Logger { }); if (runtime.activeAccount !== runtime.activeIdentity) { + const updateConfig = { + activeIdentity: identity, + underlyingAccount: account, + }; runtime.verifications.options.executor.signer.updateConfig({ verifications: runtime.verifications, }, { - activeIdentity: identity, - underlyingAccount: account, + ...updateConfig, underlyingSigner, }); + runtime.verifications.updateConfig({ }, updateConfig); } const did = new Did({ From 10278a5a8d45ab0534cd229eaff73baa96a95d05 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Fri, 21 Feb 2020 14:49:49 +0100 Subject: [PATCH 068/104] Update param description --- docs/dfs/ipfs.rst | 2 +- src/dfs/ipfs.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/dfs/ipfs.rst b/docs/dfs/ipfs.rst index ef1d4c3e..645407d1 100644 --- a/docs/dfs/ipfs.rst +++ b/docs/dfs/ipfs.rst @@ -332,7 +332,7 @@ Parameters ---------- #. ``hash`` - ``string``: ipfs hash (or bytes32 encoded) of the data -#. ``returnBuffer`` - ``bool``: should the function return the plain buffer, defaults to ``false`` +#. ``returnBuffer`` - ``bool``: if true the method will return a raw buffer holding the data (default false) ------- Returns diff --git a/src/dfs/ipfs.ts b/src/dfs/ipfs.ts index 1ac39d71..c685da4d 100644 --- a/src/dfs/ipfs.ts +++ b/src/dfs/ipfs.ts @@ -180,9 +180,10 @@ export class Ipfs extends Logger implements DfsInterface { * @brief get data from ipfs by ipfs hash * * @param hash ipfs hash of the data - * @param returnBuffer should the function return the plain buffer (default false) + * @param returnBuffer if true the method will return a raw buffer holding + * the data (default false) * - * @return data as text + * @return data as text or raw buffer */ public async get(hash: string, returnBuffer = false): Promise { const ipfsHash = hash.startsWith('Qm') ? hash : Ipfs.bytes32ToIpfsHash(hash); From 72be0e40417b83860f05259c7333494bd695e38c Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Fri, 21 Feb 2020 15:28:53 +0100 Subject: [PATCH 069/104] Replaced owner with controller property [CORE-1049] --- src/did/did.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/did/did.ts b/src/did/did.ts index 8bae71ca..6964f6e0 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -70,7 +70,7 @@ export interface DidDocumentTemplate { }[] | { id: string; type: string; - owner: string; + controller: string; ethereumAddress: string; }[]; updated?: { @@ -290,7 +290,7 @@ export class Did extends Logger { "publicKey": [{ "id": "${didAddress}#key-1", "type": "Secp256k1VerificationKey2018", - "owner": "${didAddress}", + "controller": "${didAddress}", "ethereumAddress": "${this.options.signerIdentity.underlyingAccount.toLowerCase()}" }], "authentication": [ @@ -540,7 +540,7 @@ export class Did extends Logger { "publicKey": [{ "id": "${did}#key-1", "type": "Secp256k1VerificationKey2018", - "owner": "${did}", + "controller": "${did}", "ethereumAddress": "${controllerIdentity}" }], "authentication": [ From c62fd497a6ca6a5657e5bca9ad42155cb37357f0 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Mon, 24 Feb 2020 10:29:00 +0100 Subject: [PATCH 070/104] Updated doc --- docs/profile/did.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/profile/did.rst b/docs/profile/did.rst index c073f706..37de6da8 100644 --- a/docs/profile/did.rst +++ b/docs/profile/did.rst @@ -450,7 +450,7 @@ Example // { // "id": "did:evan:testcore:0x126E901F6F408f5E260d95c62E7c73D9B60fd734#key-1", // "type": "Secp256k1VerificationKey2018", - // "owner": "did:evan:testcore:0x126E901F6F408f5E260d95c62E7c73D9B60fd734", + // "controller": "did:evan:testcore:0x126E901F6F408f5E260d95c62E7c73D9B60fd734", // "ethereumAddress": "0x126E901F6F408f5E260d95c62E7c73D9B60fd734" // } // ], From 512a26ce0c0caaeb616120a0b18224d156f0a84a Mon Sep 17 00:00:00 2001 From: Sebastian Dechant Date: Tue, 25 Feb 2020 08:27:15 +0100 Subject: [PATCH 071/104] use real testnet - [CORE-775] --- .travis.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9ddd8578..bcfaab43 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,9 +51,6 @@ addons: - g++-5 before_install: - - docker pull evannetwork/testcore-snapshot - - git clone https://github.com/evannetwork/testcore-config.git && cd testcore-config - - docker run -d -p 8546:8546 -p 8545:8545 -v /home/travis/build/evannetwork/api-blockchain-core/testcore-config/testcore.json:/root/parity/spec.json -u root evannetwork/testcore-snapshot --chain /root/parity/spec.json --jsonrpc-interface all --unsafe-expose - export CC="gcc-5" && export CXX="g++-5" && export LINK="gcc-5" && export LINKXX="g++-5" - npm install -g npm@latest - cd .. @@ -91,7 +88,7 @@ install: fi npm i node ./scripts/build-contracts.js - ganache-cli --allowUnlimitedContractSize --gasLimit 0xE4E1C0 -p 7545 -f http://localhost:8545 > /dev/null & + ganache-cli --allowUnlimitedContractSize --gasLimit 0xE4E1C0 -p 7545 -f https://testcore.evan.network > /dev/null & cd /home/travis/build/evannetwork/api-blockchain-core after_success: - bash <(curl -s https://codecov.io/bash) -cF javascript From c3495a31274e5b28c52facf8cb826606390ad821 Mon Sep 17 00:00:00 2001 From: OmairLiaquatAli Date: Tue, 25 Feb 2020 11:41:42 +0100 Subject: [PATCH 072/104] fix to use shorthand path for credentialStatus -[CORE-1013] --- src/config-testcore.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config-testcore.ts b/src/config-testcore.ts index ba489419..e4f496ca 100644 --- a/src/config-testcore.ts +++ b/src/config-testcore.ts @@ -55,7 +55,7 @@ const configTestcore = { accountId: '0x063fB42cCe4CA5448D69b4418cb89E663E71A139', }, didAndVc: { - vcRevokationStatusEndpoint: 'https://testcore.evan.network/smart-agents/smart-agent-did-resolver/vc/status/', + vcRevokationStatusEndpoint: 'https://testcore.evan.network/vc/', }, }, alwaysAutoGasLimit: 10, From 560c5036bc7748a6d2e6ca45e101989c398cb375 Mon Sep 17 00:00:00 2001 From: OmairLiaquatAli Date: Tue, 25 Feb 2020 11:49:34 +0100 Subject: [PATCH 073/104] update versions.md -[CORE-1013] --- VERSIONS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/VERSIONS.md b/VERSIONS.md index b1004c84..68ac82e6 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -10,6 +10,7 @@ - added proof validation to `getDidDocument` (only for documents that actually contain a proof) ### Fixes +- fix `credentialStatus.id` uses short hand path for resolver links - fix `getListEntries` in the api docs - fix buffer-to-string conversion, try to decode to `utf8`, if this fails, decode it to `binary` - add `member-ordering` rule to eslint config From 1ab18602d75ab027d76c7fc43618f3c2d39c7070 Mon Sep 17 00:00:00 2001 From: Sebastian Dechant Date: Tue, 25 Feb 2020 13:10:47 +0100 Subject: [PATCH 074/104] reset test net --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index bcfaab43..9ddd8578 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,6 +51,9 @@ addons: - g++-5 before_install: + - docker pull evannetwork/testcore-snapshot + - git clone https://github.com/evannetwork/testcore-config.git && cd testcore-config + - docker run -d -p 8546:8546 -p 8545:8545 -v /home/travis/build/evannetwork/api-blockchain-core/testcore-config/testcore.json:/root/parity/spec.json -u root evannetwork/testcore-snapshot --chain /root/parity/spec.json --jsonrpc-interface all --unsafe-expose - export CC="gcc-5" && export CXX="g++-5" && export LINK="gcc-5" && export LINKXX="g++-5" - npm install -g npm@latest - cd .. @@ -88,7 +91,7 @@ install: fi npm i node ./scripts/build-contracts.js - ganache-cli --allowUnlimitedContractSize --gasLimit 0xE4E1C0 -p 7545 -f https://testcore.evan.network > /dev/null & + ganache-cli --allowUnlimitedContractSize --gasLimit 0xE4E1C0 -p 7545 -f http://localhost:8545 > /dev/null & cd /home/travis/build/evannetwork/api-blockchain-core after_success: - bash <(curl -s https://codecov.io/bash) -cF javascript From 75ad619c68115974a9187e133ebcbc20ac00e484 Mon Sep 17 00:00:00 2001 From: OmairLiaquatAli Date: Tue, 25 Feb 2020 17:02:41 +0100 Subject: [PATCH 075/104] fix reqOptions.port is provider port -[CORE-1054] --- src/dfs/ipfs-lib.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dfs/ipfs-lib.ts b/src/dfs/ipfs-lib.ts index 0bc9e06e..5eabef86 100644 --- a/src/dfs/ipfs-lib.ts +++ b/src/dfs/ipfs-lib.ts @@ -86,6 +86,7 @@ export class IpfsLib { const requestLib: any = this.provider.protocol === 'http' ? http : https; const reqOptions: http.RequestOptions = {}; reqOptions.hostname = this.provider.host; + reqOptions.port = this.provider.port; reqOptions.path = `${this.provider.base}${opts.uri}`; reqOptions.headers = { ...this.provider.headers }; if (opts.payload) { From 41d232921293580c5ee7e8bdd8b85d3398f9e278 Mon Sep 17 00:00:00 2001 From: OmairLiaquatAli Date: Tue, 25 Feb 2020 17:04:54 +0100 Subject: [PATCH 076/104] add testcase for addingfiles using misconfigured and reconfigured port -[CORE-1054] --- src/dfs/ipfs.spec.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/dfs/ipfs.spec.ts b/src/dfs/ipfs.spec.ts index 726ea0b3..f51608a0 100644 --- a/src/dfs/ipfs.spec.ts +++ b/src/dfs/ipfs.spec.ts @@ -21,6 +21,7 @@ import 'mocha'; import * as chaiAsPromised from 'chai-as-promised'; import { expect, use } from 'chai'; +import { IpfsLib } from './ipfs-lib'; import { Ipfs } from './ipfs'; import { InMemoryCache } from './in-memory-cache'; import { TestUtils } from '../test/test-utils'; @@ -110,6 +111,22 @@ describe('IPFS handler', function test() { delete ipfs.cache; }); + it('should not be able to add a file when misconfigured', async () => { + ipfs.remoteNode = new IpfsLib({ host: 'ipfs.test.evan.network', port: '600', protocol: 'https' }); + const randomContent = Math.random().toString(); + const hash = ipfs.add('test', Buffer.from(randomContent, 'utf-8')); + await expect(hash).to.be.rejected; + }); + + it('should be able to add a file when ipfs port reconfigured to same port', async () => { + ipfs.remoteNode = new IpfsLib({ host: 'ipfs.test.evan.network', port: '443', protocol: 'https' }); + const randomContent = Math.random().toString(); + const hash = await ipfs.add('test', Buffer.from(randomContent, 'utf-8')); + expect(hash).not.to.be.undefined; + const fileContent = await ipfs.get(hash); + expect(fileContent).to.eq(randomContent); + }); + describe('when dealing with special characters', () => { it('should be able to add a file with umlauts, that have been encoded as binary', async () => { const content = 'öäüßÖÄÜ'; From 82a550e996716fa8aeef8840a6f60778702795fb Mon Sep 17 00:00:00 2001 From: Sebastian Dechant Date: Tue, 25 Feb 2020 22:18:31 +0100 Subject: [PATCH 077/104] use ganache beta --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9ddd8578..431da64d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -57,7 +57,7 @@ before_install: - export CC="gcc-5" && export CXX="g++-5" && export LINK="gcc-5" && export LINKXX="g++-5" - npm install -g npm@latest - cd .. - - npm i -g ganache-cli + - npm i -g ganache-cli@beta install: - | cd /home/travis/build/evannetwork/api-blockchain-core From 81227c300a1e3388ad0eb7f61792cd585006c904 Mon Sep 17 00:00:00 2001 From: OmairLiaquatAli Date: Wed, 26 Feb 2020 10:29:30 +0100 Subject: [PATCH 078/104] update versions.md -[CORE-1054] --- VERSIONS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/VERSIONS.md b/VERSIONS.md index b1004c84..4b8235d7 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -10,6 +10,7 @@ - added proof validation to `getDidDocument` (only for documents that actually contain a proof) ### Fixes +- fix `ipfsLib` now uses the configured port - fix `getListEntries` in the api docs - fix buffer-to-string conversion, try to decode to `utf8`, if this fails, decode it to `binary` - add `member-ordering` rule to eslint config From 572f83cec59ea374a9e0b111775e6f422df5ee31 Mon Sep 17 00:00:00 2001 From: Sebastian Dechant Date: Wed, 26 Feb 2020 11:12:47 +0100 Subject: [PATCH 079/104] revert beta --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 431da64d..9ddd8578 100644 --- a/.travis.yml +++ b/.travis.yml @@ -57,7 +57,7 @@ before_install: - export CC="gcc-5" && export CXX="g++-5" && export LINK="gcc-5" && export LINKXX="g++-5" - npm install -g npm@latest - cd .. - - npm i -g ganache-cli@beta + - npm i -g ganache-cli install: - | cd /home/travis/build/evannetwork/api-blockchain-core From e30e95a4028e3d2ab32384881b55cc3da6f7c386 Mon Sep 17 00:00:00 2001 From: OmairLiaquatAli Date: Wed, 26 Feb 2020 11:26:02 +0100 Subject: [PATCH 080/104] fix vcRevokationStatusEndpoint -[CORE-1013] --- src/config-testcore.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config-testcore.ts b/src/config-testcore.ts index e4f496ca..369de079 100644 --- a/src/config-testcore.ts +++ b/src/config-testcore.ts @@ -55,7 +55,7 @@ const configTestcore = { accountId: '0x063fB42cCe4CA5448D69b4418cb89E663E71A139', }, didAndVc: { - vcRevokationStatusEndpoint: 'https://testcore.evan.network/vc/', + vcRevokationStatusEndpoint: 'https://testcore.evan.network/vc/status/', }, }, alwaysAutoGasLimit: 10, From 33ebb284d1fb31550102e87d7b08fc5251ae4c43 Mon Sep 17 00:00:00 2001 From: OmairLiaquatAli Date: Wed, 26 Feb 2020 15:28:46 +0100 Subject: [PATCH 081/104] add check for fetching revoke status of non-existing Vc -[CORE-1062] --- src/vc/vc.ts | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/vc/vc.ts b/src/vc/vc.ts index adabf85e..8cfb162d 100644 --- a/src/vc/vc.ts +++ b/src/vc/vc.ts @@ -262,14 +262,19 @@ export class Vc extends Logger { * @return {revokationStatus} A boolean value. False = not revoked, True = revoked */ public async getRevokeVcStatus(vcId: string): Promise { - const environment = await this.getEnvironment(); - const vcIdHash = vcId.replace(`vc:evan:${environment}:`, ''); - const revokationStatus = await this.options.executor.executeContractCall( - await this.getRegistryContract(), - 'vcRevoke', - vcIdHash, - ); - + let revokationStatus; + const vcDocument = await this.getVc(vcId); + if (!vcDocument) { + throw new Error(`Given "${vcId}" is not a valid evan VC`); + } else { + const environment = await this.getEnvironment(); + const vcIdHash = vcId.replace(`vc:evan:${environment}:`, ''); + revokationStatus = await this.options.executor.executeContractCall( + await this.getRegistryContract(), + 'vcRevoke', + vcIdHash, + ); + } return revokationStatus; } From 3f24503062516f6215c22ce617b8d30f9fa8ef33 Mon Sep 17 00:00:00 2001 From: OmairLiaquatAli Date: Wed, 26 Feb 2020 15:30:14 +0100 Subject: [PATCH 082/104] add test case for fetching status of non-existing vc -[CORE-1062] --- src/vc/vc.spec.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/vc/vc.spec.ts b/src/vc/vc.spec.ts index 720e047f..5823ef37 100644 --- a/src/vc/vc.spec.ts +++ b/src/vc/vc.spec.ts @@ -315,17 +315,11 @@ describe('VC Resolver', function () { expect(vcRevokeStatusNew).to.be.not.false; }); - it('does not allow me to revoke a non existing VC using the vc api', async () => { + it('does not allow me to fetch revoke status of a non existing VC using the vc api', async () => { const nonExistingVcId = '0x2a838a6961be98f6a182f375bb9158848ee9760ca97a379939ccdf03fc442a23'; + const vcRevokeStatus = runtime.vc.getRevokeVcStatus(nonExistingVcId); - const vcRevokeStatus = await runtime.vc.getRevokeVcStatus(nonExistingVcId); - expect(vcRevokeStatus).to.be.false; - - const revokeProcessed = runtime.vc.revokeVc(nonExistingVcId); - await expect(revokeProcessed).to.be.rejected; - - const vcRevokeStatusNew = await runtime.vc.getRevokeVcStatus(nonExistingVcId); - expect(vcRevokeStatusNew).to.be.false; + await expect(vcRevokeStatus).to.be.rejectedWith(`VC for address ${nonExistingVcId} does not exist`); }); it('allows me to get revoke status of an existing VC using the vc api', async () => { From 86e0a0f88dafdc119f18f9bd37b69455cd50ccf2 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Wed, 26 Feb 2020 15:50:52 +0100 Subject: [PATCH 083/104] Update versions [CORE-1049] --- VERSIONS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/VERSIONS.md b/VERSIONS.md index b1004c84..bbe724b6 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -14,6 +14,7 @@ - fix buffer-to-string conversion, try to decode to `utf8`, if this fails, decode it to `binary` - add `member-ordering` rule to eslint config - fix `container.getListEntry` to not throw an exception on call anymore +- replaced deprecated property `owner` in DID publicKey fields with `controller` ### Deprecations From eb3117260553137d1dee26a0835a3a313e622425 Mon Sep 17 00:00:00 2001 From: OmairLiaquatAli Date: Wed, 26 Feb 2020 17:16:04 +0100 Subject: [PATCH 084/104] update versions.md -[CORE-1062] --- VERSIONS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/VERSIONS.md b/VERSIONS.md index b1004c84..135b0e0b 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -10,6 +10,7 @@ - added proof validation to `getDidDocument` (only for documents that actually contain a proof) ### Fixes +- add check for `getRevokeVcStatus` to throw error when non existing VC is passed - fix `getListEntries` in the api docs - fix buffer-to-string conversion, try to decode to `utf8`, if this fails, decode it to `binary` - add `member-ordering` rule to eslint config From 17791dda14130b4f1647f25c459e177c9edd8322 Mon Sep 17 00:00:00 2001 From: wulfraem Date: Thu, 27 Feb 2020 09:38:10 +0100 Subject: [PATCH 085/104] update test behavior - use local `ipfs` to prevent side effects - use `Promise.race` to speed up tests - check for error message in test - [CORE-1054] --- src/dfs/ipfs.spec.ts | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/dfs/ipfs.spec.ts b/src/dfs/ipfs.spec.ts index f51608a0..f1e024c3 100644 --- a/src/dfs/ipfs.spec.ts +++ b/src/dfs/ipfs.spec.ts @@ -112,18 +112,31 @@ describe('IPFS handler', function test() { }); it('should not be able to add a file when misconfigured', async () => { - ipfs.remoteNode = new IpfsLib({ host: 'ipfs.test.evan.network', port: '600', protocol: 'https' }); + const localIpfs = await TestUtils.getIpfs(); + localIpfs.remoteNode = new IpfsLib( + { host: 'ipfs.test.evan.network', port: '600', protocol: 'https' }, + ); const randomContent = Math.random().toString(); - const hash = ipfs.add('test', Buffer.from(randomContent, 'utf-8')); - await expect(hash).to.be.rejected; + const requestPromise = (async () => { + const request = localIpfs.add('test', Buffer.from(randomContent, 'utf-8')); + await expect(request).to.be.rejectedWith( + /^could not add file to ipfs: problem with request/, + ); + })(); + const timeoutPromise = new Promise((s) => { setTimeout(s, 5_000); }); + // requeset will either receive a network error or timeout (after 120s) + await Promise.race([requestPromise, timeoutPromise]); }); it('should be able to add a file when ipfs port reconfigured to same port', async () => { - ipfs.remoteNode = new IpfsLib({ host: 'ipfs.test.evan.network', port: '443', protocol: 'https' }); + const localIpfs = await TestUtils.getIpfs(); + localIpfs.remoteNode = new IpfsLib( + { host: 'ipfs.test.evan.network', port: '443', protocol: 'https' }, + ); const randomContent = Math.random().toString(); - const hash = await ipfs.add('test', Buffer.from(randomContent, 'utf-8')); + const hash = await localIpfs.add('test', Buffer.from(randomContent, 'utf-8')); expect(hash).not.to.be.undefined; - const fileContent = await ipfs.get(hash); + const fileContent = await localIpfs.get(hash); expect(fileContent).to.eq(randomContent); }); From 62b3459779d8b8b296f7679bfb40c7840f42e44b Mon Sep 17 00:00:00 2001 From: Tobias Winkler Date: Thu, 27 Feb 2020 10:13:59 +0100 Subject: [PATCH 086/104] add todo comment - [CORE-635] --- src/onboarding.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/onboarding.ts b/src/onboarding.ts index 7881345a..3df2adc8 100644 --- a/src/onboarding.ts +++ b/src/onboarding.ts @@ -437,6 +437,7 @@ export class Onboarding extends Logger { const targetAccount = runtime.activeIdentity !== accountId ? newIdentity : accountId; const targetAccountHash = runtime.activeIdentity !== accountId ? identityHash : accountHash; + // TODO: Use identity encryption key here! const dataKey = runtime.keyProvider.keys[accountHash]; profile.ipld.originator = runtime.web3.utils.soliditySha3(targetAccount); From feb7ae6607907376cb9e48f265ab70a8d5ea3b81 Mon Sep 17 00:00:00 2001 From: Tobias Winkler Date: Thu, 27 Feb 2020 16:29:08 +0100 Subject: [PATCH 087/104] reset activeIdentity within createOfflineProfile - [CORE-] --- src/onboarding.ts | 55 ++++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/onboarding.ts b/src/onboarding.ts index 3df2adc8..e74a7f08 100644 --- a/src/onboarding.ts +++ b/src/onboarding.ts @@ -364,6 +364,10 @@ export class Onboarding extends Logger { recaptchaToken: string, network = 'testcore', ) { + const creationRuntime = { + ...runtime, + activeIdentity: '0x0000000000000000000000000000000000000000', + }; // check for correct profile data if (!profileData || !profileData.accountDetails || !profileData.accountDetails.accountName) { throw new Error('No profile data specified or accountDetails missing'); @@ -380,14 +384,14 @@ export class Onboarding extends Logger { // build array with allowed fields (may include duplicates) Profile.checkCorrectProfileData(profileData, profileData.accountDetails.profileType); - const { profile } = runtime; + const { profile } = creationRuntime; // disable pinning while profile files are being created profile.ipld.ipfs.disablePin = true; // clear hash log profile.ipld.hashLog = []; const pk = `0x${pKey}`; - const { signature } = runtime.web3.eth.accounts.sign('Gimme Gimme Gimme!', pk); + const { signature } = creationRuntime.web3.eth.accounts.sign('Gimme Gimme Gimme!', pk); // request a new profile contract const requestMode = process.env.TEST_ONBOARDING ? http : https; @@ -432,24 +436,25 @@ export class Onboarding extends Logger { reqProfileReq.end(); }); const newIdentity = (requestedProfile as any).identity; - const accountHash = runtime.web3.utils.soliditySha3(accountId); - const identityHash = runtime.web3.utils.soliditySha3(newIdentity); - const targetAccount = runtime.activeIdentity !== accountId ? newIdentity : accountId; - const targetAccountHash = runtime.activeIdentity !== accountId ? identityHash : accountHash; + const accountHash = creationRuntime.web3.utils.soliditySha3(accountId); + const identityHash = creationRuntime.web3.utils.soliditySha3(newIdentity); + const targetAccount = creationRuntime.activeIdentity !== accountId ? newIdentity : accountId; + const targetAccountHash = creationRuntime.activeIdentity !== accountId ? identityHash + : accountHash; // TODO: Use identity encryption key here! - const dataKey = runtime.keyProvider.keys[accountHash]; + const dataKey = creationRuntime.keyProvider.keys[accountHash]; - profile.ipld.originator = runtime.web3.utils.soliditySha3(targetAccount); + profile.ipld.originator = creationRuntime.web3.utils.soliditySha3(targetAccount); profile.activeAccount = targetAccount; profile.profileOwner = targetAccount; // eslint-disable-next-line - runtime.keyProvider.keys[targetAccountHash] = dataKey; + creationRuntime.keyProvider.keys[targetAccountHash] = dataKey; // eslint-disable-next-line - runtime.keyProvider.keys[runtime.web3.utils.soliditySha3(targetAccountHash, targetAccountHash)] = dataKey; + creationRuntime.keyProvider.keys[creationRuntime.web3.utils.soliditySha3(targetAccountHash, targetAccountHash)] = dataKey; - const dhKeys = runtime.keyExchange.getDiffieHellmanKeys(); + const dhKeys = creationRuntime.keyExchange.getDiffieHellmanKeys(); await profile.addContactKey( targetAccount, 'dataKey', dhKeys.privateKey.toString('hex'), ); @@ -457,37 +462,37 @@ export class Onboarding extends Logger { await profile.addPublicKey(dhKeys.publicKey.toString('hex')); // set initial structure by creating addressbook structure and saving it to ipfs - const cryptor = runtime.cryptoProvider.getCryptorByCryptoAlgo('aesEcb'); + const cryptor = creationRuntime.cryptoProvider.getCryptorByCryptoAlgo('aesEcb'); const fileHashes: any = {}; - const cryptorAes = runtime.cryptoProvider.getCryptorByCryptoAlgo( - runtime.dataContract.options.defaultCryptoAlgo, + const cryptorAes = creationRuntime.cryptoProvider.getCryptorByCryptoAlgo( + creationRuntime.dataContract.options.defaultCryptoAlgo, ); - const hashCryptor = runtime.cryptoProvider.getCryptorByCryptoAlgo( - runtime.dataContract.cryptoAlgorithHashes, + const hashCryptor = creationRuntime.cryptoProvider.getCryptorByCryptoAlgo( + creationRuntime.dataContract.cryptoAlgorithHashes, ); const [hashKey, blockNr] = await Promise.all([ hashCryptor.generateKey(), - runtime.web3.eth.getBlockNumber(), + creationRuntime.web3.eth.getBlockNumber(), ]); // setup sharings for new profile const sharings = {}; const profileKeys = Object.keys(profileData); // add hashKey - await runtime.sharing.extendSharings( + await creationRuntime.sharing.extendSharings( sharings, targetAccount, targetAccount, '*', 'hashKey', hashKey, ); // extend sharings for profile data const dataContentKeys = await Promise.all(profileKeys.map(() => cryptorAes.generateKey())); for (let i = 0; i < profileKeys.length; i += 1) { - await runtime.sharing.extendSharings( + await creationRuntime.sharing.extendSharings( sharings, targetAccount, targetAccount, profileKeys[i], blockNr, dataContentKeys[i], ); } // upload sharings - const sharingsHash = await runtime.dfs.add( - 'sharing', Buffer.from(JSON.stringify(sharings), runtime.dataContract.encodingUnencrypted), + const sharingsHash = await creationRuntime.dfs.add( + 'sharing', Buffer.from(JSON.stringify(sharings), creationRuntime.dataContract.encodingUnencrypted), ); // used to exclude encrypted hashes from fileHashes.ipfsHashes @@ -502,10 +507,10 @@ export class Onboarding extends Logger { const envelope = { private: encrypted.toString('hex'), cryptoInfo: cryptorAes.getCryptoInfo( - runtime.nameResolver.soliditySha3((requestedProfile as any).contractId), + creationRuntime.nameResolver.soliditySha3((requestedProfile as any).contractId), ), }; - const ipfsHash = await runtime.dfs.add(key, Buffer.from(JSON.stringify(envelope))); + const ipfsHash = await creationRuntime.dfs.add(key, Buffer.from(JSON.stringify(envelope))); profile.ipld.hashLog.push(`${ipfsHash.toString('hex')}`); fileHashes.properties.entries[key] = await cryptor.encrypt( @@ -549,7 +554,7 @@ export class Onboarding extends Logger { const data = { accountId, - identityId: runtime.activeIdentity !== accountId ? newIdentity : undefined, + identityId: creationRuntime.activeIdentity !== accountId ? newIdentity : undefined, signature, profileInfo: fileHashes, accessToken: (requestedProfile as any).accessToken, @@ -558,7 +563,7 @@ export class Onboarding extends Logger { // TODO if statement can be removed after account/identity switch is done if ((requestedProfile as any).identity) { - const didTransactionTuple = await this.createOfflineDidTransaction(runtime, + const didTransactionTuple = await this.createOfflineDidTransaction(creationRuntime, accountId, (requestedProfile as any).identity); const didTransaction = didTransactionTuple[0]; const documentHash = didTransactionTuple[1]; From 4ad697a99a0cdf90628f0c4436ae81b186593b69 Mon Sep 17 00:00:00 2001 From: wulfraem Date: Fri, 28 Feb 2020 11:01:34 +0100 Subject: [PATCH 088/104] update revocation to support anchored VCs - use `vcOwner` instead of document - [CORE-1052] --- src/vc/vc.spec.ts | 2 +- src/vc/vc.ts | 31 ++++++++++++++++++++----------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/vc/vc.spec.ts b/src/vc/vc.spec.ts index 5823ef37..961b36bb 100644 --- a/src/vc/vc.spec.ts +++ b/src/vc/vc.spec.ts @@ -319,7 +319,7 @@ describe('VC Resolver', function () { const nonExistingVcId = '0x2a838a6961be98f6a182f375bb9158848ee9760ca97a379939ccdf03fc442a23'; const vcRevokeStatus = runtime.vc.getRevokeVcStatus(nonExistingVcId); - await expect(vcRevokeStatus).to.be.rejectedWith(`VC for address ${nonExistingVcId} does not exist`); + await expect(vcRevokeStatus).to.be.rejectedWith(`Given "${nonExistingVcId}" is not a valid evan VC`); }); it('allows me to get revoke status of an existing VC using the vc api', async () => { diff --git a/src/vc/vc.ts b/src/vc/vc.ts index 8cfb162d..a43c7cc6 100644 --- a/src/vc/vc.ts +++ b/src/vc/vc.ts @@ -21,6 +21,7 @@ import * as didJWT from 'did-jwt'; import { cloneDeep } from 'lodash'; import { + nullAddress, nullBytes32, getEnvironment, } from '../common/utils'; @@ -262,20 +263,28 @@ export class Vc extends Logger { * @return {revokationStatus} A boolean value. False = not revoked, True = revoked */ public async getRevokeVcStatus(vcId: string): Promise { - let revokationStatus; - const vcDocument = await this.getVc(vcId); - if (!vcDocument) { - throw new Error(`Given "${vcId}" is not a valid evan VC`); - } else { - const environment = await this.getEnvironment(); - const vcIdHash = vcId.replace(`vc:evan:${environment}:`, ''); - revokationStatus = await this.options.executor.executeContractCall( + const environment = await this.getEnvironment(); + const vcIdHash = vcId.replace(`vc:evan:${environment}:`, ''); + // `vcId` usually come from a vc document, so non-existing vcs can be considered an edge case, + // running both requests in parallel to improve performance + const [revoked] = await Promise.all([ + this.options.executor.executeContractCall( await this.getRegistryContract(), 'vcRevoke', vcIdHash, - ); - } - return revokationStatus; + ), + (async () => { + const owned = await this.options.executor.executeContractCall( + await this.getRegistryContract(), + 'vcOwner', + vcIdHash, + ); + if (owned === nullAddress) { + throw new Error(`Given "${vcId}" is not a valid evan VC`); + } + })(), + ]); + return revoked; } /** From 9a91b1875b66327ff4b882db0f30a96c5f734d46 Mon Sep 17 00:00:00 2001 From: wulfraem Date: Fri, 28 Feb 2020 11:12:36 +0100 Subject: [PATCH 089/104] Update src/vc/vc.ts --- src/vc/vc.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vc/vc.ts b/src/vc/vc.ts index a43c7cc6..badfbadf 100644 --- a/src/vc/vc.ts +++ b/src/vc/vc.ts @@ -265,7 +265,7 @@ export class Vc extends Logger { public async getRevokeVcStatus(vcId: string): Promise { const environment = await this.getEnvironment(); const vcIdHash = vcId.replace(`vc:evan:${environment}:`, ''); - // `vcId` usually come from a vc document, so non-existing vcs can be considered an edge case, + // `vcId` usually comes from a VC document, so non-existing VCs can be considered an edge case, // running both requests in parallel to improve performance const [revoked] = await Promise.all([ this.options.executor.executeContractCall( From 6fe89eabe966b44414c4b27225316d9072f383cf Mon Sep 17 00:00:00 2001 From: wulfraem Date: Fri, 28 Feb 2020 16:05:03 +0100 Subject: [PATCH 090/104] fix pending contract members after unshare - [CORE-1011] --- src/contracts/digital-twin/container.spec.ts | 2 ++ src/contracts/digital-twin/container.ts | 22 +++++++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/contracts/digital-twin/container.spec.ts b/src/contracts/digital-twin/container.spec.ts index 85a49c54..e71ae331 100644 --- a/src/contracts/digital-twin/container.spec.ts +++ b/src/contracts/digital-twin/container.spec.ts @@ -940,6 +940,8 @@ describe('Container', function test() { ((consumerContainer as any).options as ContainerOptions).sharing.clearCache(); consumerSharing = ((consumerContainer as any).options as ContainerOptions).sharing; shareConfig = await container.getContainerShareConfigForAccount(consumer); + const members = await runtimes[owner].rightsAndRoles.getMembers((container as any).contract); + expect(members['1']).not.to.include(consumer); await expect(consumerContainer.getEntry('testField')) .to.be.rejectedWith('could not get entry; no hashKey key found'); expect(shareConfig).not.to.haveOwnProperty('read'); diff --git a/src/contracts/digital-twin/container.ts b/src/contracts/digital-twin/container.ts index 78479305..0de6e4b8 100644 --- a/src/contracts/digital-twin/container.ts +++ b/src/contracts/digital-twin/container.ts @@ -1463,8 +1463,8 @@ export class Container extends Logger { await Throttle.all(accessP); // /////////////////// check if only remaining property is 'type', cleanup if that's the case - const shareConfig = await this.getContainerShareConfigForAccount(accountId); - const remainingFields = Array.from(new Set([ + let shareConfig = await this.getContainerShareConfigForAccount(accountId); + let remainingFields = Array.from(new Set([ ...(shareConfig.read ? shareConfig.read : []), ...(shareConfig.readWrite ? shareConfig.readWrite : []), ])); @@ -1479,11 +1479,6 @@ export class Container extends Logger { // remove read if applicable readWrite.push('type'); - - // uninvite - this.options.dataContract.removeFromContract( - null, await this.getContractAddress(), this.config.accountId, accountId, - ); } // //////////////////////////////////////////////////// ensure encryption keys for properties @@ -1555,6 +1550,19 @@ export class Container extends Logger { } }); }); + + // ////////////////////////////////////////////// remove account from member role if required + shareConfig = await this.getContainerShareConfigForAccount(accountId); + remainingFields = Array.from(new Set([ + ...(shareConfig.read ? shareConfig.read : []), + ...(shareConfig.readWrite ? shareConfig.readWrite : []), + ])); + if (remainingFields.length === 0) { + // uninvite + await this.options.dataContract.removeFromContract( + null, await this.getContractAddress(), this.config.accountId, accountId, + ); + } })); } From c449bed37033dda28503eb44b6b2987dd19e9533 Mon Sep 17 00:00:00 2001 From: wulfraem Date: Fri, 28 Feb 2020 16:09:34 +0100 Subject: [PATCH 091/104] update versions info - [CORE-1011] --- VERSIONS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/VERSIONS.md b/VERSIONS.md index c2e98ab1..d4d3e2ee 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -17,6 +17,7 @@ - add `member-ordering` rule to eslint config - fix `container.getListEntry` to not throw an exception on call anymore - replaced deprecated property `owner` in DID publicKey fields with `controller` +- fix pending contract members after unshare ### Deprecations From 0cde03fed3577798c304ab009e65adf6aea49eed Mon Sep 17 00:00:00 2001 From: Tobias Winkler Date: Mon, 2 Mar 2020 07:31:24 +0100 Subject: [PATCH 092/104] update versions for wulf - [CORE-635] --- VERSIONS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/VERSIONS.md b/VERSIONS.md index bbe724b6..92b6c6cc 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -15,6 +15,8 @@ - add `member-ordering` rule to eslint config - fix `container.getListEntry` to not throw an exception on call anymore - replaced deprecated property `owner` in DID publicKey fields with `controller` +- update verification keys for onboarding, when `useIdentity` is enabled +- reset `activeIdentity` within `createOfflineProfile` ### Deprecations From c1f306ad3bbb3cbb4cce2d38beede863e750690b Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Mon, 2 Mar 2020 16:54:47 +0100 Subject: [PATCH 093/104] Refactoring [CORE-1082] --- src/contracts/digital-twin/digital-twin.ts | 9 +- src/did/did.spec.ts | 14 +- src/did/did.ts | 191 +++++++++------------ src/vc/vc.ts | 31 ++-- 4 files changed, 112 insertions(+), 133 deletions(-) diff --git a/src/contracts/digital-twin/digital-twin.ts b/src/contracts/digital-twin/digital-twin.ts index e6d0dbf2..94f1252c 100644 --- a/src/contracts/digital-twin/digital-twin.ts +++ b/src/contracts/digital-twin/digital-twin.ts @@ -778,9 +778,12 @@ export class DigitalTwin extends Logger { // Get the first authentication key of the controller, which is either their own public key // or their respective controller's authentication key - const authKeyIds = (await this.options.did.getDidDocument(controllerDid)) - .publicKey.map((key) => key.id).join(','); - const doc = await this.options.did.getDidDocumentTemplate(twinDid, controllerDid, authKeyIds); + const publicKeys = (await this.options.did.getDidDocument(controllerDid)).publicKey; + const authKeyIds = []; + for (const key of publicKeys) { + authKeyIds.push(key.id); + } + const doc = await this.options.did.getDidDocumentTemplate(twinDid, controllerDid, authKeyIds.join(',')); await this.options.did.setDidDocument(twinDid, doc); } diff --git a/src/did/did.spec.ts b/src/did/did.spec.ts index a87fbad7..6224c36a 100644 --- a/src/did/did.spec.ts +++ b/src/did/did.spec.ts @@ -381,14 +381,17 @@ describe('DID Resolver', function test() { const did = await runtimes[0].did.convertIdentityToDid(twinIdentity); const controllerDid = await runtimes[0].did.convertIdentityToDid(runtimes[0].activeIdentity); const controllerDidDoc = await runtimes[0].did.getDidDocument(controllerDid); - const authKeyIds = controllerDidDoc.publicKey.map((key) => key.id).join(','); + const authKeyIds = []; + for (const entry of controllerDidDoc.publicKey) { + authKeyIds.push(entry.id); + } const expectedDefaultDid = { '@context': 'https://w3id.org/did/v1', id: did, controller: controllerDidDoc.id, authentication: [ - authKeyIds, + authKeyIds.join(','), ], }; const defaultDidDoc = await runtimes[0].did.getDidDocument(did); @@ -573,14 +576,17 @@ describe('DID Resolver', function test() { const controllerDid = await runtimes[0].did.convertIdentityToDid(ownerIdentity); const controllerDidDoc = await runtimes[0].did.getDidDocument(controllerDid); const did = await runtimes[0].did.convertIdentityToDid(aliasIdentity); - const authKeyIds = controllerDidDoc.publicKey.map((key) => key.id).join(','); + const authKeyIds = []; + for (const entry of controllerDidDoc.publicKey) { + authKeyIds.push(entry.id); + } const expectedDefaultDid = { '@context': 'https://w3id.org/did/v1', id: did, controller: controllerDidDoc.id, authentication: [ - authKeyIds, + authKeyIds.join(','), ], }; diff --git a/src/did/did.ts b/src/did/did.ts index d9d2604f..3a8a43cb 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -50,19 +50,13 @@ export interface DidConfig { } /** - * template for a new DID document, can be used as a starting point for building own documents + * DID document structure */ -export interface DidDocumentTemplate { +export interface DidDocument { '@context': string; id: string; controller?: string; - authentication: { - type: string; - publicKey: string; - } | { - type: string; - publicKey: string; - }[]; + authentication: string[]; publicKey?: { id: string; type: string; @@ -73,29 +67,19 @@ export interface DidDocumentTemplate { controller: string; ethereumAddress: string; }[]; - updated?: { - time: string; - }; - created?: { - time: string; - }; + updated?: string; + created?: string; proof?: DidProof; - service?: { - id: string; - type: string; - serviceEndpoint: string; - }[]; + service?: DidServiceEntry[]; } /** * interface for services in DIDs */ export interface DidServiceEntry { - [id: string]: any; - type: any; - serviceEndpoint: any; - '@context'?: any; - id?: any; + id: string; + type: string; + serviceEndpoint: string; } /** @@ -216,11 +200,11 @@ export class Did extends Logger { * Get DID document for given DID. * * @param {string} did DID to fetch DID document for - * @return {Promise} a DID document that MAY resemble `DidDocumentTemplate` format. + * @return {Promise} a DID document. * For deactiated DIDs it returns a default DID document containing * no authentication material. */ - public async getDidDocument(did: string): Promise { + public async getDidDocument(did: string): Promise { let result = null; if (await this.didIsDeactivated(did)) { return this.getDeactivatedDidDocument(did); @@ -242,7 +226,6 @@ export class Did extends Logger { return this.getDefaultDidDocument(did); } result = JSON.parse(await this.options.dfs.get(documentHash) as any); - result = await this.removePublicKeyTypeArray(result); if (result.proof) { await this.validateProof(result); @@ -263,40 +246,40 @@ export class Did extends Logger { * @param {string} did (optional) contract DID * @param {string} controllerDid (optional) controller of contracts identity (DID) * @param {string} authenticationKey (optional) authentication key used for contract - * @return {Promise} a DID document template + * @return {Promise} a DID document template */ public async getDidDocumentTemplate( did?: string, controllerDid?: string, authenticationKey?: string, - ): Promise { + ): Promise { if (did && controllerDid && authenticationKey) { await this.validateDid(did); await this.validateDid(controllerDid); // use given key to create a contract DID document - return JSON.parse(`{ - "@context": "https://w3id.org/did/v1", - "id": "${did}", - "controller": "${controllerDid}", - "authentication": [ - "${authenticationKey}" - ] - }`); + return { + '@context': 'https://w3id.org/did/v1', + id: did, + controller: controllerDid, + authentication: [ + authenticationKey, + ], + }; } if (!(did || controllerDid || authenticationKey)) { const identity = this.options.signerIdentity.activeIdentity; const didAddress = await this.convertIdentityToDid(identity); - return JSON.parse(`{ - "@context": "https://w3id.org/did/v1", - "id": "${didAddress}", - "publicKey": [{ - "id": "${didAddress}#key-1", - "type": "Secp256k1VerificationKey2018", - "controller": "${didAddress}", - "ethereumAddress": "${this.options.signerIdentity.underlyingAccount.toLowerCase()}" + return { + '@context': 'https://w3id.org/did/v1', + id: didAddress, + publicKey: [{ + id: `${didAddress}#key-1`, + type: 'Secp256k1VerificationKey2018', + controller: `${didAddress}`, + ethereumAddress: `${this.options.signerIdentity.underlyingAccount.toLowerCase()}`, }], - "authentication": [ - "${didAddress}#key-1" - ] - }`); + authentication: [ + `${didAddress}#key-1`, + ], + }; } throw new Error('invalid config for template document'); } @@ -305,9 +288,9 @@ export class Did extends Logger { * Get service from DID document. * * @param {string} did DID name to get service for - * @return {Promise} service + * @return {Promise} service */ - public async getService(did: string): Promise { + public async getService(did: string): Promise { return (await this.getDidDocument(did)).service; } @@ -315,12 +298,12 @@ export class Did extends Logger { * Store given DID document for given DID. * * @param {string} did DID to store DID document for - * @param {any} document DID document to store + * @param {DidDocument} document DID document to store * @param {VerificationDelegationInfo} txInfo Optional. If given, the transaction * is executed on behalf of the tx signer. * @return {Promise} resolved when done */ - public async setDidDocument(did: string, document: any): Promise { + public async setDidDocument(did: string, document: DidDocument): Promise { if (await this.didIsDeactivated(did)) { throw Error('Cannot set document for deactivated DID'); } @@ -352,7 +335,7 @@ export class Did extends Logger { * @param document Document to store * @returns Tuple of the signed transaction and the document's ipfs hash */ - public async setDidDocumentOffline(did: string, document: any): + public async setDidDocumentOffline(did: string, document: DidDocument): Promise<[VerificationsDelegationInfo, string]> { const identity = this.padIdentity(did ? await this.convertDidToIdentity(did) @@ -378,17 +361,25 @@ export class Did extends Logger { /** - * Sets service in DID document. + * Sets service in DID document. Overrides the old service property. * * @param {string} did DID name to set service for - * @param {DidServiceEntry[] | DidServiceEntry} service service to set + * @param {DidServiceEntry[] | DidServiceEntry} service service(s) to set * @return {Promise} resolved when done */ public async setService( did: string, service: DidServiceEntry[] | DidServiceEntry, ): Promise { - await this.setDidDocument(did, { ...(await this.getDidDocument(did)), service }); + if (service instanceof Array) { + await this.setDidDocument(did, { ...(await this.getDidDocument(did)), service }); + } else { + const serviceToSet: DidServiceEntry[] = [service]; + await this.setDidDocument(did, { + ...(await this.getDidDocument(did)), + service: serviceToSet, + }); + } } /** @@ -425,12 +416,13 @@ export class Did extends Logger { /** * Create a JWT over a DID document * - * @param {didDocument} DID The DID document + * @param {DidDocument} DID The DID document * @param {string} proofIssuer The issuer (key owner) of the proof * @param {DidProofType} proofType The type of algorithm used for generating the JWT */ - private async createJwtForDid(didDocument: any, proofIssuer: string, proofType: DidProofType): - Promise { + private async createJwtForDid(didDocument: DidDocument, proofIssuer: string, + proofType: DidProofType): + Promise { const signer = didJWT.SimpleSigner( await this.options.accountStore.getPrivateKey(this.options.signerIdentity.underlyingAccount), ); @@ -512,14 +504,14 @@ export class Did extends Logger { /* * Returns the standard DID document for deactivated DIDs */ - private async getDeactivatedDidDocument(did: string): Promise { - return JSON.parse(`{ - "@context": "https://w3id.org/did/v1", - "id": "${did}", - "publicKey": [], - "authentication": [], - "services": [] - }`); + private async getDeactivatedDidDocument(did: string): Promise { + return { + '@context': 'https://w3id.org/did/v1', + id: `${did}`, + publicKey: [], + authentication: [], + service: [], + }; } /** @@ -527,7 +519,7 @@ export class Did extends Logger { * @param did DID to fetch a document for. * @returns Resolves to a DID document. */ - private async getDefaultDidDocument(did: string): Promise { + private async getDefaultDidDocument(did: string): Promise { const identity = await this.convertDidToIdentity(did); let controllerIdentity; try { @@ -539,28 +531,31 @@ export class Did extends Logger { if (identity.length === 42) { // Identity is account identity and therefore self-sovereign - return JSON.parse(`{ - "@context": "https://w3id.org/did/v1", - "id": "${did}", - "publicKey": [{ - "id": "${did}#key-1", - "type": "Secp256k1VerificationKey2018", - "controller": "${did}", - "ethereumAddress": "${controllerIdentity}" + return { + '@context': 'https://w3id.org/did/v1', + id: `${did}`, + publicKey: [{ + id: `${did}#key-1`, + type: 'Secp256k1VerificationKey2018', + controller: `${did}`, + ethereumAddress: `${controllerIdentity}`, }], - "authentication": [ - "${did}#key-1" - ] - }`); + authentication: [ + `${did}#key-1`, + ], + }; } // Identity is contract identity and therefore controlled by another identity const controllerDid = await this.convertIdentityToDid(controllerIdentity); const controllerDidDoc = await this.getDidDocument(controllerDid); - const authKeyIds = controllerDidDoc.publicKey.map((key) => key.id).join(','); + const authKeyIds = []; + for (const key of controllerDidDoc.publicKey) { + authKeyIds.push(key.id); + } return this.getDidDocumentTemplate(did, controllerDid, - authKeyIds); + authKeyIds.join(',')); } /** @@ -622,29 +617,7 @@ export class Did extends Logger { : identity; } - /** - * Method to ensure no public key array types are written into a retrieved did document. This is - * just a legacy method because we still have various faulty DID documents stored that have an - * array as the publicKey.type property. - * - * @param {any} result The cleaned and valid DID document - */ - private async removePublicKeyTypeArray(result: any): Promise { - // TODO: Method can be deleted as soon as there is a real DID validation in place - const cleanedResult = result; - let keyTypes = []; - - for (const pos in result.publicKey) { - // Discard ERC725ManagementKey type entry - if (result.publicKey[pos].type instanceof Array) { - keyTypes = result.publicKey[pos].type.filter((type) => !type.startsWith('ERC725')); - [cleanedResult.publicKey[pos].type] = keyTypes; - } - } - return cleanedResult; - } - - private async setAdditionalProperties(document: any): Promise { + private async setAdditionalProperties(document: DidDocument): Promise { const clone = _.cloneDeep(document); const now = (new Date(Date.now())).toISOString(); // Only set 'created' for new did documents @@ -677,10 +650,10 @@ export class Did extends Logger { /** * Validates the JWS of a DID Document proof * - * @param {any} document The DID Document + * @param {DidDocument} document The DID Document * @returns {Promise} Resolves when done */ - private async validateProof(document: any): Promise { + private async validateProof(document: DidDocument): Promise { // Mock the did-resolver package that did-jwt usually requires const getResolver = (didModule) => ({ async resolve(did) { diff --git a/src/vc/vc.ts b/src/vc/vc.ts index adabf85e..accdf743 100644 --- a/src/vc/vc.ts +++ b/src/vc/vc.ts @@ -497,10 +497,6 @@ export class Vc extends Logger { * the active identity is found. */ private async getPublicKeyUriFromDid(issuerDid: string): Promise { - const signaturePublicKey = (await this.options.signerIdentity.getPublicKey( - this.options.signerIdentity.underlyingAccount, - )).toLocaleLowerCase(); - const account = this.options.signerIdentity.underlyingAccount.toLocaleLowerCase(); const doc = await this.options.did.getDidDocument(issuerDid); if (!(doc.authentication || doc.publicKey || doc.publicKey.length === 0)) { @@ -508,23 +504,24 @@ export class Vc extends Logger { + 'does not provide authentication material. Cannot sign VC.'); } - const key = doc.publicKey.filter( - (entry) => { - if (entry.ethereumAddress) { - return entry.ethereumAddress.toLocaleLowerCase() === account; - } - if (entry.publicKeyHex) { - return entry.publicKeyHex.toLocaleLowerCase() === signaturePublicKey; - } - return false; - }, - )[0]; + let keyId; + const signaturePublicKey = (await this.options.signerIdentity.getPublicKey( + this.options.signerIdentity.underlyingAccount, + )).toLocaleLowerCase(); + const account = this.options.signerIdentity.underlyingAccount.toLocaleLowerCase(); + for (const entry of doc.publicKey) { + if (('ethereumAddress' in entry && entry.ethereumAddress.toLocaleLowerCase() === account) + || ('publicKeyHex' in entry && entry.publicKeyHex.toLocaleLowerCase() === signaturePublicKey)) { + keyId = entry.id; + break; + } + } - if (!key) { + if (!keyId) { throw Error('The signature key of the active account is not associated to its DID document. Cannot sign VC.'); } - return key.id; + return keyId; } /** From f680937f696aca95717c22be2e2183a82d75aa2c Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Mon, 2 Mar 2020 17:10:48 +0100 Subject: [PATCH 094/104] Update docs [CORE-1082] --- docs/profile/did.rst | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/profile/did.rst b/docs/profile/did.rst index 37de6da8..8d78ec35 100644 --- a/docs/profile/did.rst +++ b/docs/profile/did.rst @@ -86,7 +86,7 @@ deactivateDidDocument .. code-block:: typescript - did.deactivateDidDocument(did); + did.deactivateDidDocument(didToDeactivate); Unlinks the current DID document from the given DID @@ -123,7 +123,7 @@ didIsDeactivated .. code-block:: typescript - did.didIsDeactivated(did); + did.didIsDeactivated(didToCheck); Gets the deactivation status of a DID. @@ -162,7 +162,7 @@ getDidDocument .. code-block:: typescript - did.getDidDocument(did); + did.getDidDocument(myDid); Get DID document for given DID. If the DID has a proof property, `getDidDocument` will attempt to validate the proof and throw an error if the proof is invalid. @@ -177,7 +177,7 @@ Parameters Returns ------- -``Promise`` returns ``any``: a DID document that MAY resemble `DidDocumentTemplate` format +``Promise`` returns ``DidDOcument``: A DID document. For deactiated DIDs it returns a default DID document containing no authentication material. ------- Example @@ -201,7 +201,7 @@ getService .. code-block:: typescript - did.getService(did); + did.getService(myDid); Get the services from a DID document. @@ -215,7 +215,7 @@ Parameters Returns ------- -``Promise`` returns ``DidServiceEntry[] | DidServiceEntry``: Array of services, or a single service entry object. +``Promise`` returns ``DidServiceEntry[]``: Array of services. ------- Example @@ -244,7 +244,7 @@ setDidDocument .. code-block:: typescript - did.setDidDocument(did, document); + did.setDidDocument(myDid, document); Store given DID document for given DID. If the document misses the property `created`, it will automatically be appended. @@ -256,7 +256,7 @@ Parameters ---------- #. ``did`` - ``string``: DID to store DID document for -#. ``document`` - ``any``: DID document to store, ``getDidDocumentTemplate`` can be used as a starting point for DID documents +#. ``document`` - ``DidDOcument``: DID document to store, ``getDidDocumentTemplate`` can be used as a starting point for DID documents ------- Returns @@ -284,16 +284,16 @@ setService .. code-block:: typescript - did.setService(did, service); + did.setService(myDid, service); -Sets service in DID document. +Sets service in DID document. Overrides old services, so make sure to include current service if you only want to add a service. ---------- Parameters ---------- #. ``did`` - ``string``: DID name to set service for -#. ``service`` - ``DidServiceEntry[] | DidServiceEntry``: service or array of services to set +#. ``service`` - ``DidServiceEntry[]``: array of services to set ------- Returns @@ -332,7 +332,7 @@ convertDidToIdentity .. code-block:: typescript - did.convertDidToIdentity(did); + did.convertDidToIdentity(didToConvert); Converts given DID to a evan.network identity. @@ -371,7 +371,7 @@ convertIdentityToDid .. code-block:: typescript - did.convertIdentityToDid(identity); + did.convertIdentityToDid(identityToConvert); Converts given evan.network identity hash to DID. @@ -409,7 +409,7 @@ getDidDocumentTemplate .. code-block:: typescript - did.getDidDocumentTemplate([]); + did.getDidDocumentTemplate(); Gets a DID document for currently configured account/identity pair. Notice, that this document may a complete DID document for currently configured active identity, a part of it or not matching it at From 0e804c098a99096225fdbecce3c960cb71e069d6 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Wed, 4 Mar 2020 16:28:57 +0100 Subject: [PATCH 095/104] Update docs/profile/did.rst Co-Authored-By: wulfraem --- docs/profile/did.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/profile/did.rst b/docs/profile/did.rst index 8d78ec35..fc5d7fc5 100644 --- a/docs/profile/did.rst +++ b/docs/profile/did.rst @@ -177,7 +177,7 @@ Parameters Returns ------- -``Promise`` returns ``DidDOcument``: A DID document. For deactiated DIDs it returns a default DID document containing no authentication material. +``Promise`` returns ``DidDocument``: A DID document. For deactivated DIDs it returns a default DID document containing no authentication material. ------- Example From 530c8bc9113bf6f542c39430e1a6a6585e3bd3aa Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Wed, 4 Mar 2020 16:29:09 +0100 Subject: [PATCH 096/104] Update docs/profile/did.rst Co-Authored-By: wulfraem --- docs/profile/did.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/profile/did.rst b/docs/profile/did.rst index fc5d7fc5..01fc0ed5 100644 --- a/docs/profile/did.rst +++ b/docs/profile/did.rst @@ -256,7 +256,7 @@ Parameters ---------- #. ``did`` - ``string``: DID to store DID document for -#. ``document`` - ``DidDOcument``: DID document to store, ``getDidDocumentTemplate`` can be used as a starting point for DID documents +#. ``document`` - ``DidDocument``: DID document to store, ``getDidDocumentTemplate`` can be used as a starting point for DID documents ------- Returns From 24c63f5a4e1af955d2e626bb9e031d1b3efe487a Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Wed, 4 Mar 2020 17:14:00 +0100 Subject: [PATCH 097/104] Refactoring and bug fixing [CORE-1082] --- src/contracts/digital-twin/digital-twin.ts | 2 +- src/did/did.spec.ts | 32 ++++++++-------------- src/did/did.ts | 26 +++++++----------- 3 files changed, 22 insertions(+), 38 deletions(-) diff --git a/src/contracts/digital-twin/digital-twin.ts b/src/contracts/digital-twin/digital-twin.ts index 94f1252c..0d3e1d6f 100644 --- a/src/contracts/digital-twin/digital-twin.ts +++ b/src/contracts/digital-twin/digital-twin.ts @@ -783,7 +783,7 @@ export class DigitalTwin extends Logger { for (const key of publicKeys) { authKeyIds.push(key.id); } - const doc = await this.options.did.getDidDocumentTemplate(twinDid, controllerDid, authKeyIds.join(',')); + const doc = await this.options.did.getDidDocumentTemplate(twinDid, controllerDid, authKeyIds); await this.options.did.setDidDocument(twinDid, doc); } diff --git a/src/did/did.spec.ts b/src/did/did.spec.ts index 6224c36a..c7def9b3 100644 --- a/src/did/did.spec.ts +++ b/src/did/did.spec.ts @@ -257,7 +257,7 @@ describe('DID Resolver', function test() { ); const controllerDidDocument = await runtimes[0].did.getDidDocument(accounts0Did); const document = await runtimes[0].did.getDidDocumentTemplate( - twinDid, controllerDid, controllerDidDocument.authentication[0], + twinDid, controllerDid, [controllerDidDocument.authentication[0]], ); const promise = runtimes[0].did.setDidDocument(twinDid, document); await expect(promise).not.to.be.rejected; @@ -285,7 +285,7 @@ describe('DID Resolver', function test() { ); const controllerDidDocument = await runtimes[0].did.getDidDocument(accounts0Did); const document = await runtimes[0].did.getDidDocumentTemplate( - twinDid, controllerDid, controllerDidDocument.authentication[0], + twinDid, controllerDid, [controllerDidDocument.authentication[0]], ); await runtimes[0].did.setDidDocument(twinDid, document); const retrieved = await runtimes[0].did.getDidDocument(twinDid); @@ -318,7 +318,7 @@ describe('DID Resolver', function test() { ); const controllerDidDocument = await runtimes[0].did.getDidDocument(accounts0Did); const document = await runtimes[0].did.getDidDocumentTemplate( - twinDid, controllerDid, controllerDidDocument.authentication[0], + twinDid, controllerDid, [controllerDidDocument.authentication[0]], ); await runtimes[0].did.setDidDocument(twinDid, document); @@ -354,7 +354,7 @@ describe('DID Resolver', function test() { ); const controllerDidDocument = await runtimes[0].did.getDidDocument(accounts0Did); const document = await runtimes[0].did.getDidDocumentTemplate( - twinDid, controllerDid, controllerDidDocument.authentication[0], + twinDid, controllerDid, [controllerDidDocument.authentication[0]], ); const runtime1 = runtimes[1]; const promise = runtime1.did.setDidDocument(twinDid, document); @@ -381,18 +381,13 @@ describe('DID Resolver', function test() { const did = await runtimes[0].did.convertIdentityToDid(twinIdentity); const controllerDid = await runtimes[0].did.convertIdentityToDid(runtimes[0].activeIdentity); const controllerDidDoc = await runtimes[0].did.getDidDocument(controllerDid); - const authKeyIds = []; - for (const entry of controllerDidDoc.publicKey) { - authKeyIds.push(entry.id); - } + const authKeyIds = controllerDidDoc.publicKey.map((key) => key.id); const expectedDefaultDid = { '@context': 'https://w3id.org/did/v1', id: did, controller: controllerDidDoc.id, - authentication: [ - authKeyIds.join(','), - ], + authentication: authKeyIds, }; const defaultDidDoc = await runtimes[0].did.getDidDocument(did); await expect(defaultDidDoc).to.deep.eq(expectedDefaultDid); @@ -515,7 +510,7 @@ describe('DID Resolver', function test() { ); const controllerDidDocument = await runtimes[0].did.getDidDocument(accounts0Did); const document = await runtimes[0].did.getDidDocumentTemplate( - did, controllerDid, controllerDidDocument.authentication[0], + did, controllerDid, [controllerDidDocument.authentication[0]], ); const promise = runtimes[0].did.setDidDocument(did, document); @@ -534,7 +529,7 @@ describe('DID Resolver', function test() { ); const controllerDidDocument = await runtimes[0].did.getDidDocument(accounts0Did); const document = await runtimes[0].did.getDidDocumentTemplate( - did, controllerDid, controllerDidDocument.authentication[0], + did, controllerDid, [controllerDidDocument.authentication[0]], ); const promise = runtimes[0].did.setDidDocument(did, document); @@ -553,7 +548,7 @@ describe('DID Resolver', function test() { ); const controllerDidDocument = await runtimes[0].did.getDidDocument(accounts0Did); const documentTemplate = await runtimes[0].did.getDidDocumentTemplate( - did, controllerDid, controllerDidDocument.authentication[0], + did, controllerDid, [controllerDidDocument.authentication[0]], ); await runtimes[0].did.setDidDocument(did, documentTemplate); const actualDocument = await runtimes[0].did.getDidDocument(did); @@ -576,18 +571,13 @@ describe('DID Resolver', function test() { const controllerDid = await runtimes[0].did.convertIdentityToDid(ownerIdentity); const controllerDidDoc = await runtimes[0].did.getDidDocument(controllerDid); const did = await runtimes[0].did.convertIdentityToDid(aliasIdentity); - const authKeyIds = []; - for (const entry of controllerDidDoc.publicKey) { - authKeyIds.push(entry.id); - } + const authKeyIds = controllerDidDoc.publicKey.map((key) => key.id); const expectedDefaultDid = { '@context': 'https://w3id.org/did/v1', id: did, controller: controllerDidDoc.id, - authentication: [ - authKeyIds.join(','), - ], + authentication: authKeyIds, }; const aliasIdentityDid = await runtimes[0].did.getDidDocument(did); diff --git a/src/did/did.ts b/src/did/did.ts index 3a8a43cb..47a030c1 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -57,16 +57,16 @@ export interface DidDocument { id: string; controller?: string; authentication: string[]; - publicKey?: { + publicKey?: ({ id: string; type: string; publicKeyHex: string; - }[] | { + } | { id: string; type: string; controller: string; ethereumAddress: string; - }[]; + })[]; updated?: string; created?: string; proof?: DidProof; @@ -245,13 +245,13 @@ export class Did extends Logger { * * @param {string} did (optional) contract DID * @param {string} controllerDid (optional) controller of contracts identity (DID) - * @param {string} authenticationKey (optional) authentication key used for contract + * @param {string[]} authenticationKeys (optional) array of authentication keys * @return {Promise} a DID document template */ public async getDidDocumentTemplate( - did?: string, controllerDid?: string, authenticationKey?: string, + did?: string, controllerDid?: string, authenticationKeys?: string[], ): Promise { - if (did && controllerDid && authenticationKey) { + if (did && controllerDid && authenticationKeys) { await this.validateDid(did); await this.validateDid(controllerDid); // use given key to create a contract DID document @@ -259,11 +259,9 @@ export class Did extends Logger { '@context': 'https://w3id.org/did/v1', id: did, controller: controllerDid, - authentication: [ - authenticationKey, - ], + authentication: authenticationKeys, }; - } if (!(did || controllerDid || authenticationKey)) { + } if (!(did || controllerDid || authenticationKeys)) { const identity = this.options.signerIdentity.activeIdentity; const didAddress = await this.convertIdentityToDid(identity); @@ -548,14 +546,10 @@ export class Did extends Logger { // Identity is contract identity and therefore controlled by another identity const controllerDid = await this.convertIdentityToDid(controllerIdentity); const controllerDidDoc = await this.getDidDocument(controllerDid); - const authKeyIds = []; - for (const key of controllerDidDoc.publicKey) { - authKeyIds.push(key.id); - } - + const authKeyIds = controllerDidDoc.publicKey.map((key) => key.id); return this.getDidDocumentTemplate(did, controllerDid, - authKeyIds.join(',')); + authKeyIds); } /** From ff70297dfd15583e1f398fc26d78055ae77c11e6 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Thu, 5 Mar 2020 09:17:02 +0100 Subject: [PATCH 098/104] Update src/did/did.ts Co-Authored-By: Duc Anh Trinh --- src/did/did.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/did/did.ts b/src/did/did.ts index 47a030c1..ab911799 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -531,7 +531,7 @@ export class Did extends Logger { // Identity is account identity and therefore self-sovereign return { '@context': 'https://w3id.org/did/v1', - id: `${did}`, + id: did, publicKey: [{ id: `${did}#key-1`, type: 'Secp256k1VerificationKey2018', From 58d97a52c5c637f56fe138312143f8134d3d5782 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Thu, 5 Mar 2020 09:17:33 +0100 Subject: [PATCH 099/104] Update src/did/did.ts Co-Authored-By: Duc Anh Trinh --- src/did/did.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/did/did.ts b/src/did/did.ts index ab911799..80023ac7 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -505,7 +505,7 @@ export class Did extends Logger { private async getDeactivatedDidDocument(did: string): Promise { return { '@context': 'https://w3id.org/did/v1', - id: `${did}`, + id: did, publicKey: [], authentication: [], service: [], From adf47b8439b3a4e7cc07ca8dc8af51c157161043 Mon Sep 17 00:00:00 2001 From: Philip Kaiser Date: Thu, 5 Mar 2020 09:29:55 +0100 Subject: [PATCH 100/104] Refactoring [CORE-1082] --- VERSIONS.md | 1 + docs/profile/did.rst | 10 +++++----- src/contracts/digital-twin/digital-twin.ts | 7 ++----- src/did/did.ts | 2 +- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/VERSIONS.md b/VERSIONS.md index bbe724b6..55d29274 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -8,6 +8,7 @@ - add methods `deactivateDidDocument` and `didIsDeactivated` to check and handle DID deactivation status - added additional properties `updated`, `created`, and `proof` to DID documents - added proof validation to `getDidDocument` (only for documents that actually contain a proof) +- introduced interfaces for did documents `getDidDocument`, `setDidDocument`, and `setDidDocumentOffline` ### Fixes - fix `getListEntries` in the api docs diff --git a/docs/profile/did.rst b/docs/profile/did.rst index 01fc0ed5..5123f4ad 100644 --- a/docs/profile/did.rst +++ b/docs/profile/did.rst @@ -227,11 +227,11 @@ Example const identity = await runtime.verifications.getIdentityForAccount(account, true); const did = await runtime.did.convertIdentityToDid(identity); await runtime.did.setDidDocument(did, document); - const service = [{ + const service = { id: `${did}#randomService`, type: `randomService-${random}`, serviceEndpoint: `https://openid.example.com/${random}`, - }]; + }; await runtime.did.setService(did, service); const retrieved = await runtime.did.getService(did); @@ -293,7 +293,7 @@ Parameters ---------- #. ``did`` - ``string``: DID name to set service for -#. ``service`` - ``DidServiceEntry[]``: array of services to set +#. ``service`` - ``DidServiceEntry[] | DidServiceEntry``: service or array of services to set ------- Returns @@ -311,11 +311,11 @@ Example const identity = await runtime.verifications.getIdentityForAccount(account, true); const did = await runtime.did.convertIdentityToDid(identity); await runtime.did.setDidDocument(did, document); - const service = [{ + const service = { id: `${did}#randomService`, type: `randomService-${random}`, serviceEndpoint: `https://openid.example.com/${random}`, - }]; + }; await runtime.did.setService(did, service); diff --git a/src/contracts/digital-twin/digital-twin.ts b/src/contracts/digital-twin/digital-twin.ts index 0d3e1d6f..69c31732 100644 --- a/src/contracts/digital-twin/digital-twin.ts +++ b/src/contracts/digital-twin/digital-twin.ts @@ -778,11 +778,8 @@ export class DigitalTwin extends Logger { // Get the first authentication key of the controller, which is either their own public key // or their respective controller's authentication key - const publicKeys = (await this.options.did.getDidDocument(controllerDid)).publicKey; - const authKeyIds = []; - for (const key of publicKeys) { - authKeyIds.push(key.id); - } + const { publicKey } = await this.options.did.getDidDocument(controllerDid); + const authKeyIds = publicKey.map((key) => key.id); const doc = await this.options.did.getDidDocumentTemplate(twinDid, controllerDid, authKeyIds); await this.options.did.setDidDocument(twinDid, doc); } diff --git a/src/did/did.ts b/src/did/did.ts index 47a030c1..f7c2fb1a 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -286,7 +286,7 @@ export class Did extends Logger { * Get service from DID document. * * @param {string} did DID name to get service for - * @return {Promise} service + * @return {Promise} services */ public async getService(did: string): Promise { return (await this.getDidDocument(did)).service; From 437c5d81225417eb5a0c6c6860261356d30bea9a Mon Sep 17 00:00:00 2001 From: Tobias Winkler Date: Thu, 5 Mar 2020 10:59:07 +0100 Subject: [PATCH 101/104] export `config` and `runtimeConfig` by `createDefaultRuntime` - [CORE-1091] --- VERSIONS.md | 3 ++- src/runtime.ts | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/VERSIONS.md b/VERSIONS.md index 495c560b..4dd4d464 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -8,6 +8,7 @@ - add methods `deactivateDidDocument` and `didIsDeactivated` to check and handle DID deactivation status - added additional properties `updated`, `created`, and `proof` to DID documents - added proof validation to `getDidDocument` (only for documents that actually contain a proof) +- export `config` and `runtimeConfig` by `createDefaultRuntime` ### Fixes - fix `credentialStatus.id` uses short hand path for resolver links @@ -20,7 +21,7 @@ - replaced deprecated property `owner` in DID publicKey fields with `controller` - fix pending contract members after unshare - update verification keys for onboarding, when `useIdentity` is enabled -- reset `activeIdentity` within `createOfflineProfile` +- reset `activeIdentity` within `createOfflineProfile` when useIdentity is enabled ### Deprecations diff --git a/src/runtime.ts b/src/runtime.ts index e0b59d3e..6bc943ad 100644 --- a/src/runtime.ts +++ b/src/runtime.ts @@ -67,6 +67,7 @@ export interface Runtime { activeAccount?: string; activeIdentity?: string; baseContract?: BaseContract; + config?: any; contractLoader?: ContractLoader; contracts?: any; cryptoProvider?: CryptoProvider; @@ -88,6 +89,7 @@ export interface Runtime { payments?: Payments; profile?: Profile; rightsAndRoles?: RightsAndRoles; + runtimeConfig?: any; serviceContract?: ServiceContract; sharing?: Sharing; signer?: SignerInterface; @@ -565,6 +567,7 @@ export async function createDefaultRuntime( accountStore, activeAccount, baseContract, + config, contractLoader, contracts, cryptoProvider, @@ -585,6 +588,7 @@ export async function createDefaultRuntime( payments, profile, rightsAndRoles, + runtimeConfig, serviceContract, sharing, signer, From 953bd502a4ea014abde910542c54bb651a6c6172 Mon Sep 17 00:00:00 2001 From: Tobias Winkler Date: Thu, 5 Mar 2020 10:59:33 +0100 Subject: [PATCH 102/104] reset `activeIdentity` within `createOfflineProfile` when useIdentity is enabled - [CORE-1091] --- src/onboarding.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/onboarding.ts b/src/onboarding.ts index e74a7f08..2e5d477d 100644 --- a/src/onboarding.ts +++ b/src/onboarding.ts @@ -364,9 +364,12 @@ export class Onboarding extends Logger { recaptchaToken: string, network = 'testcore', ) { + // ensure to set activeIdentity to 0x0..., when use identity is enabled const creationRuntime = { ...runtime, - activeIdentity: '0x0000000000000000000000000000000000000000', + activeIdentity: runtime.runtimeConfig.useIdentity + ? '0x0000000000000000000000000000000000000000' + : runtime.activeIdentity, }; // check for correct profile data if (!profileData || !profileData.accountDetails || !profileData.accountDetails.accountName) { From 291a48975829927e81626cf9d374cc20d57b685d Mon Sep 17 00:00:00 2001 From: Tobias Winkler Date: Thu, 5 Mar 2020 13:16:04 +0100 Subject: [PATCH 103/104] use nullAddress const instead of 0x00000... - [CORE-1091] --- src/onboarding.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/onboarding.ts b/src/onboarding.ts index 2e5d477d..b3d2d517 100644 --- a/src/onboarding.ts +++ b/src/onboarding.ts @@ -25,6 +25,7 @@ import { SignerInternal, } from '@evan.network/dbcp'; +import { nullAddress } from './common/utils'; import { createDefaultRuntime, Runtime } from './runtime'; import { Mail, Mailbox } from './mailbox'; import { Profile } from './profile/profile'; @@ -367,9 +368,7 @@ export class Onboarding extends Logger { // ensure to set activeIdentity to 0x0..., when use identity is enabled const creationRuntime = { ...runtime, - activeIdentity: runtime.runtimeConfig.useIdentity - ? '0x0000000000000000000000000000000000000000' - : runtime.activeIdentity, + activeIdentity: runtime.runtimeConfig.useIdentity ? nullAddress : runtime.activeIdentity, }; // check for correct profile data if (!profileData || !profileData.accountDetails || !profileData.accountDetails.accountName) { From 6b9a6c4ab2ab5a64296fc0b23a484b455a3e78c2 Mon Sep 17 00:00:00 2001 From: wulfraem Date: Thu, 5 Mar 2020 15:27:57 +0100 Subject: [PATCH 104/104] release v2.18.0 - [CORE-1091] --- VERSIONS.md | 9 +++++++-- package.json | 6 +++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/VERSIONS.md b/VERSIONS.md index 2a6e356a..92d15fd2 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -1,7 +1,14 @@ # api-blockchain-core ## Next Version +### Features + +### Fixes + +### Deprecations + +## Version 2.18.0 ### Features - add methods `deactivateDidDocument` and `didIsDeactivated` to check and handle DID deactivation status - improve performance of `shareProperties`, `unshareProperties`, `setContainerShareConfigs` and related operations in `Container` @@ -24,8 +31,6 @@ - update verification keys for onboarding, when `useIdentity` is enabled - reset `activeIdentity` within `createOfflineProfile` when useIdentity is enabled -### Deprecations - ## Version 2.17.0 ### Features diff --git a/package.json b/package.json index 29d25a6a..ec1b0c39 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "author": "evan GmbH", "dependencies": { - "@evan.network/dbcp": "^1.11.0", - "@evan.network/smart-contracts-core": "^2.9.0", + "@evan.network/dbcp": "^1.11.1", + "@evan.network/smart-contracts-core": "^2.10.0", "@types/node": "^12.6.8", "ajv": "^6.10.2", "async-mutex": "^0.1.3", @@ -96,5 +96,5 @@ "testunitcoverage": "env-cmd --fallback -f ./.env.local npm run build && nyc -r lcov -e .ts -x \"**/*.spec.ts\" -x \"lib\" mocha --exit -r ts-node/register $TESTSPECS && nyc report --reporter=json > coverage/coverage.json" }, "types": "./dist/index.d.ts", - "version": "2.17.0" + "version": "2.18.0" }