diff --git a/package.json b/package.json index 49b2f0314..f186591c8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kiltprotocol/sdk-js", - "version": "0.16.2", + "version": "0.17.0", "description": "", "main": "./build/index.js", "typings": "./build/index.d.ts", @@ -15,7 +15,7 @@ "style:fix": "yarn style --write", "build": "tsc --declaration", "build:docs": "typedoc --theme default --out docs/api && touch docs/.nojekyll", - "buildAndPublish": "yarn build && node ./npm-create-rc.js && npm publish" + "buildAndPublish": "yarn build && node ./npm-create-rc.js && npm publish --access public" }, "husky": { "hooks": { @@ -49,7 +49,6 @@ "@commitlint/cli": "^7.2.1", "@commitlint/config-conventional": "^7.1.2", "@types/jest": "^23.3.9", - "@types/lodash": "^4.14.138", "@types/uuid": "^3.4.4", "@typescript-eslint/eslint-plugin": "^2.8.0", "@typescript-eslint/parser": "^2.8.0", @@ -81,7 +80,6 @@ "ajv": "^6.6.1", "bn.js": "^4.11.8", "jsonabc": "^2.3.1", - "lodash": "^4.17.11", "typescript-logging": "^0.6.3", "uuid": "^3.3.2" }, diff --git a/src/attestation/Attestation.chain.ts b/src/attestation/Attestation.chain.ts index e90980833..c8e13d7cd 100644 --- a/src/attestation/Attestation.chain.ts +++ b/src/attestation/Attestation.chain.ts @@ -59,7 +59,7 @@ function decode(encoded: QueryResult, claimHash: string): Attestation | null { revoked: attestationTuple[3], } log.info(`Decoded attestation: ${JSON.stringify(attestation)}`) - return Attestation.fromObject(attestation) + return Attestation.fromAttestation(attestation) } } return null diff --git a/src/attestation/Attestation.spec.ts b/src/attestation/Attestation.spec.ts index ea62fbfac..beda029f2 100644 --- a/src/attestation/Attestation.spec.ts +++ b/src/attestation/Attestation.spec.ts @@ -1,11 +1,13 @@ import { Text } from '@polkadot/types' import Bool from '@polkadot/types/primitive/Bool' import { Tuple } from '@polkadot/types/codec' -import Crypto from '../crypto' import Identity from '../identity/Identity' import Attestation from './Attestation' +import CType from '../ctype/CType' +import IAttestation from '../types/Attestation' +import ICType from '../types/CType' import RequestForAttestation from '../requestforattestation/RequestForAttestation' -import IClaim from '../types/Claim' +import Claim from '../claim/Claim' jest.mock('../blockchainApiConnection/BlockchainApiConnection') @@ -15,28 +17,50 @@ describe('Attestation', () => { const Blockchain = require('../blockchain/Blockchain').default - const cTypeHash = Crypto.hashStr('testCtype') - const claim = { - cType: cTypeHash, - contents: {}, - owner: identityBob.address, - } as IClaim - const requestForAttestation: RequestForAttestation = new RequestForAttestation( - claim, + const rawCType: ICType['schema'] = { + $id: 'http://example.com/ctype-1', + $schema: 'http://kilt-protocol.org/draft-01/ctype#', + properties: { + name: { type: 'string' }, + }, + type: 'object', + } + + const fromRawCType: ICType = { + schema: rawCType, + owner: identityAlice.address, + hash: '', + } + + const testCType: CType = CType.fromCType(fromRawCType) + + const testcontents = {} + const testClaim = Claim.fromCTypeAndClaimContents( + testCType, + testcontents, + identityBob.address + ) + const requestForAttestation: RequestForAttestation = RequestForAttestation.fromClaimAndIdentity( + testClaim, + identityBob, [], - identityBob + null ) it('stores attestation', async () => { Blockchain.api.query.attestation.attestations = jest.fn(() => { const tuple = new Tuple( [Text, Text, Text, Bool], - [cTypeHash, identityAlice.address, undefined, false] + [testCType.hash, identityAlice.address, undefined, false] ) return Promise.resolve(tuple) }) - const attestation = new Attestation(requestForAttestation, identityAlice) + const attestation: Attestation = Attestation.fromRequestAndPublicIdentity( + requestForAttestation, + identityAlice, + null + ) expect(await attestation.verify()).toBeTruthy() }) @@ -45,11 +69,12 @@ describe('Attestation', () => { return Promise.resolve(new Tuple([], [])) }) - const attestation = new Attestation( - requestForAttestation, - identityAlice, - false - ) + const attestation: Attestation = Attestation.fromAttestation({ + claimHash: requestForAttestation.rootHash, + cTypeHash: testCType.hash, + owner: identityAlice.address, + revoked: false, + } as IAttestation) expect(await attestation.verify()).toBeFalsy() }) @@ -59,15 +84,15 @@ describe('Attestation', () => { new Tuple( // Attestations: claim-hash -> (ctype-hash, account, delegation-id?, revoked) [Text, Text, Text, Bool], - [cTypeHash, identityAlice, undefined, true] + [testCType.hash, identityAlice, undefined, true] ) ) }) - const attestation = new Attestation( + const attestation: Attestation = Attestation.fromRequestAndPublicIdentity( requestForAttestation, identityAlice, - true + null ) expect(await attestation.verify()).toBeFalsy() }) diff --git a/src/attestation/Attestation.ts b/src/attestation/Attestation.ts index 408f2f863..8cf288996 100644 --- a/src/attestation/Attestation.ts +++ b/src/attestation/Attestation.ts @@ -13,45 +13,18 @@ /** * Dummy comment needed for correct doc display, do not remove. */ +import IRequestForAttestation from '../types/RequestForAttestation' import TxStatus from '../blockchain/TxStatus' import { factory } from '../config/ConfigLog' import Identity from '../identity/Identity' import IAttestation from '../types/Attestation' -import IRequestForAttestation from '../types/RequestForAttestation' import { revoke, query, store } from './Attestation.chain' +import { IDelegationBaseNode } from '../types/Delegation' +import IPublicIdentity from '../types/PublicIdentity' const log = factory.getLogger('Attestation') export default class Attestation implements IAttestation { - public claimHash: IAttestation['claimHash'] - public cTypeHash: IAttestation['cTypeHash'] - public owner: IAttestation['owner'] - public revoked: IAttestation['revoked'] - public delegationId: IAttestation['delegationId'] | null - - /** - * Builds a new [[Attestation]] instance. - * - * @param requestForAttestation - A request for attestation, usually sent by a claimer. - * @param attester - The identity of the attester. - * @param revoked - Whether the attestation should be revoked. - * @example ```javascript - * // create an attestation, e.g. to store it on-chain - * new Attestation(requestForAttestation, attester); - * ``` - */ - public constructor( - requestForAttestation: IRequestForAttestation, - attester: Identity, - revoked = false - ) { - this.owner = attester.address - this.claimHash = requestForAttestation.hash - this.cTypeHash = requestForAttestation.claim.cType - this.delegationId = requestForAttestation.delegationId - this.revoked = revoked - } - /** * [STATIC] [ASYNC] Queries the chain for a given attestation, by `claimHash`. * @@ -90,16 +63,79 @@ export default class Attestation implements IAttestation { * [STATIC] Builds an instance of [[Attestation]], from a simple object with the same properties. * Used for deserialization. * - * @param obj - The base object from which to create the attestation. + * @param attestationInput - The base object from which to create the attestation. * @returns A new [[Attestation]] object. * @example ```javascript * // create an Attestation object, so we can call methods on it (`serialized` is a serialized Attestation object ) - * Attestation.fromObject(JSON.parse(serialized)); + * Attestation.fromAttestation(JSON.parse(serialized)); * ``` */ - public static fromObject(obj: IAttestation): Attestation { - const newAttestation: Attestation = Object.create(Attestation.prototype) - return Object.assign(newAttestation, obj) + public static fromAttestation(attestationInput: IAttestation): Attestation { + return new Attestation(attestationInput) + } + + /** + * [STATIC] Builds a new instance of an [[Attestation]], from a complete set of input required for an attestation. + * + * @param request - The base request for attestation. + * @param attesterPublicIdentity - The attesters public identity, used to attest the underlying claim. + * @param [delegationIdInput] - optional delegationId for which the attester attests the claim. + * @returns A new [[Attestation]] object. + * @example ```javascript + * // create a complete new attestation from the RequestForAttestation and all other needed properties + * Attestation.fromRequestAndPublicIdentity(request, attesterPublicIdentity, delegationId); + * ``` + */ + public static fromRequestAndPublicIdentity( + request: IRequestForAttestation, + attesterPublicIdentity: IPublicIdentity, + delegationIdInput: IDelegationBaseNode['id'] | null + ) { + return new Attestation({ + claimHash: request.rootHash, + cTypeHash: request.claim.cTypeHash, + owner: attesterPublicIdentity.address, + delegationId: delegationIdInput, + revoked: false, + }) + } + + public claimHash: IAttestation['claimHash'] + public cTypeHash: IAttestation['cTypeHash'] + public owner: IAttestation['owner'] + public revoked: IAttestation['revoked'] + public delegationId: IAttestation['delegationId'] | null + + /** + * Builds a new [[Attestation]] instance. + * + * @param attestationInput - The base object from which to create the attestation. + * @example ```javascript + * // create an attestation, e.g. to store it on-chain + * new Attestation(attestationInput); + * ``` + */ + public constructor(attestationInput: IAttestation) { + if ( + !attestationInput.cTypeHash || + !attestationInput.claimHash || + !attestationInput.owner + ) { + throw new Error( + `Property Not Provided while building Attestation!\n + attestationInput.cTypeHash:\n + ${attestationInput.cTypeHash}\n + attestationInput.claimHash:\n + ${attestationInput.claimHash}\n + attestationInput.owner:\n + ${attestationInput.owner}` + ) + } + this.owner = attestationInput.owner + this.claimHash = attestationInput.claimHash + this.cTypeHash = attestationInput.cTypeHash + this.delegationId = attestationInput.delegationId + this.revoked = attestationInput.revoked } /** @@ -110,7 +146,7 @@ export default class Attestation implements IAttestation { * @example ```javascript * // Use [[store]] to store an attestation on chain, and to create an [[AttestedClaim]] upon success: * attestation.store(attester).then(() => { - * // the attestation was successfully stored, so now we can for example create an AttestedClaim + * // the attestation was successfully stored, so now we can for example create an AttestedClaim * }); * ``` */ diff --git a/src/attestedclaim/AttestedClaim.spec.ts b/src/attestedclaim/AttestedClaim.spec.ts index d42e15c9c..6cfbec2a2 100644 --- a/src/attestedclaim/AttestedClaim.spec.ts +++ b/src/attestedclaim/AttestedClaim.spec.ts @@ -1,8 +1,10 @@ import Identity from '../identity/Identity' -import RequestForAttestation from '../requestforattestation/RequestForAttestation' import AttestedClaim from './AttestedClaim' import Attestation from '../attestation/Attestation' -import IClaim from '../types/Claim' +import CType from '../ctype/CType' +import ICType from '../types/CType' +import RequestForAttestation from '../requestforattestation/RequestForAttestation' +import Claim from '../claim/Claim' function buildAttestedClaim( claimer: Identity, @@ -12,35 +14,56 @@ function buildAttestedClaim( legitimations: AttestedClaim[] ): AttestedClaim { // create claim - const claim = { - cType: ctype, + const identityAlice = Identity.buildFromURI('//Alice') + + const rawCType: ICType['schema'] = { + $id: 'http://example.com/ctype-1', + $schema: 'http://kilt-protocol.org/draft-01/ctype#', + properties: { + name: { type: 'string' }, + }, + type: 'object', + } + + const fromRawCType: ICType = { + schema: rawCType, + owner: identityAlice.address, + hash: '', + } + + const testCType: CType = CType.fromCType(fromRawCType) + + const claim = Claim.fromCTypeAndClaimContents( + testCType, contents, - owner: claimer.address, - } as IClaim + claimer.address + ) // build request for attestation with legimitations - const requstForAttestation: RequestForAttestation = new RequestForAttestation( + const requestForAttestation = RequestForAttestation.fromClaimAndIdentity( claim, + claimer, legitimations, - claimer + null ) // build attestation - const attestation: Attestation = new Attestation( - requstForAttestation, - attester + const testAttestation: Attestation = Attestation.fromRequestAndPublicIdentity( + requestForAttestation, + attester, + null ) // combine to attested claim - const attestedClaim: AttestedClaim = new AttestedClaim( - requstForAttestation, - attestation + const attestedClaim: AttestedClaim = AttestedClaim.fromRequestAndAttestation( + requestForAttestation, + testAttestation ) return attestedClaim } describe('RequestForAttestation', () => { const identityAlice = Identity.buildFromURI('//Alice') + const identityBob = Identity.buildFromURI('//Bob') const identityCharlie = Identity.buildFromURI('//Charlie') - const identityDoria = Identity.buildFromURI('//Doria') const legitimation: AttestedClaim = buildAttestedClaim( identityAlice, @@ -53,7 +76,7 @@ describe('RequestForAttestation', () => { it('verify attested claims', async () => { const attestedClaim: AttestedClaim = buildAttestedClaim( identityCharlie, - identityDoria, + identityAlice, 'ctype', { a: 'a', diff --git a/src/attestedclaim/AttestedClaim.ts b/src/attestedclaim/AttestedClaim.ts index 1aacbc6e4..c5403f7c9 100644 --- a/src/attestedclaim/AttestedClaim.ts +++ b/src/attestedclaim/AttestedClaim.ts @@ -10,7 +10,6 @@ /** * Dummy comment needed for correct doc display, do not remove. */ -import cloneDeep from 'lodash/cloneDeep' import Attestation from '../attestation/Attestation' import RequestForAttestation from '../requestforattestation/RequestForAttestation' import IAttestedClaim from '../types/AttestedClaim' @@ -22,20 +21,38 @@ export default class AttestedClaim implements IAttestedClaim { * [STATIC] Builds an instance of [[AttestedClaim]], from a simple object with the same properties. * Used for deserialization. * - * @param obj - The base object from which to create the attested claim. - * @returns A new [[AttestedClaim]] object. + * @param attestedClaimInput - The base object from which to create the attested claim. + * @returns A new instantiated [[AttestedClaim]] object. * @example ```javascript * // create an AttestedClaim object, so we can call methods on it (`serialized` is a serialized AttestedClaim object) - * AttestedClaim.fromObject(JSON.parse(serialized)); + * AttestedClaim.fromAttestedClaim(JSON.parse(serialized)); * ``` */ - public static fromObject(obj: IAttestedClaim): AttestedClaim { - const newAttestedClaim: AttestedClaim = Object.create( - AttestedClaim.prototype - ) - newAttestedClaim.request = RequestForAttestation.fromObject(obj.request) - newAttestedClaim.attestation = Attestation.fromObject(obj.attestation) - return newAttestedClaim + public static fromAttestedClaim( + attestedClaimInput: IAttestedClaim + ): AttestedClaim { + return new AttestedClaim(attestedClaimInput) + } + + /** + * [STATIC] Builds a new instance of [[AttestedClaim]], from all requiered properties. + * + * @param request - The request for attestation for the claim that was attested. + * @param attestation - The attestation for the claim by the attester. + * @returns A new [[AttestedClaim]] object. + * @example ```javascript + * //create an AttestedClaim object after receiving the attestation from the attester + * AttestedClaim.fromRequestAndAttestation(request, attestation); + * ``` + */ + public static fromRequestAndAttestation( + request: IRequestForAttestation, + attestation: IAttestation + ): AttestedClaim { + return new AttestedClaim({ + request, + attestation, + }) } public request: RequestForAttestation @@ -44,20 +61,26 @@ export default class AttestedClaim implements IAttestedClaim { /** * Builds a new [[AttestedClaim]] instance. * - * @param request - A request for attestation, usually sent by a claimer. - * @param attestation - The attestation to base the [[AttestedClaim]] on. - * @example ```javascript + * @param attestedClaimInput - The base object with all required input, from which to create the attested claim. + * @example ```javascript * // Create an [[AttestedClaim]] upon successful [[Attestation]] creation: - * new AttestedClaim(requestForAttestation, attestation); + * new AttestedClaim(attestedClaimInput); * ``` */ - public constructor( - request: IRequestForAttestation, - attestation: IAttestation - ) { - // TODO: this should be instantiated w/o fromObject - this.request = RequestForAttestation.fromObject(request) - this.attestation = Attestation.fromObject(attestation) + public constructor(attestedClaimInput: IAttestedClaim) { + if (!attestedClaimInput.request || !attestedClaimInput.attestation) { + throw new Error( + `Property Not Provided while building AttestedClaim!\n + attestedClaimInput.request: \n + ${attestedClaimInput.request} \n + attestedClaimInput.attestation: \n + ${attestedClaimInput.attestation}` + ) + } + this.request = RequestForAttestation.fromRequest(attestedClaimInput.request) + this.attestation = Attestation.fromAttestation( + attestedClaimInput.attestation + ) } /** @@ -96,7 +119,7 @@ export default class AttestedClaim implements IAttestedClaim { public verifyData(): boolean { return ( this.request.verifyData() && - this.request.hash === this.attestation.claimHash + this.request.rootHash === this.attestation.claimHash ) } @@ -127,7 +150,7 @@ export default class AttestedClaim implements IAttestedClaim { excludedClaimProperties: string[], excludeIdentity = false ): AttestedClaim { - const result: AttestedClaim = AttestedClaim.fromObject(cloneDeep(this)) + const result: AttestedClaim = AttestedClaim.fromAttestedClaim(this) result.request.removeClaimProperties(excludedClaimProperties) if (excludeIdentity) { result.request.removeClaimOwner() diff --git a/src/claim/Claim.spec.ts b/src/claim/Claim.spec.ts index 4fcef8256..d571fba1e 100644 --- a/src/claim/Claim.spec.ts +++ b/src/claim/Claim.spec.ts @@ -4,33 +4,37 @@ import Identity from '../identity/Identity' import ICType from '../types/CType' describe('Claim', () => { - const ctype = new CType({ - schema: { - $id: 'http://example.com/ctype-1', - $schema: 'http://kilt-protocol.org/draft-01/ctype#', - properties: { - name: { type: 'string' }, - }, - type: 'object', - }, - metadata: { - title: { default: 'CType Title' }, - description: {}, - properties: { - name: { title: { default: 'Name' } }, - }, - }, - } as ICType) - const identity = Identity.buildFromURI('//Alice') + const identityAlice = Identity.buildFromURI('//Alice') const claimContents = { name: 'Bob', } - const claim = new Claim(ctype, claimContents, identity) + const rawCType: ICType['schema'] = { + $id: 'http://example.com/ctype-1', + $schema: 'http://kilt-protocol.org/draft-01/ctype#', + properties: { + name: { type: 'string' }, + }, + type: 'object', + } + + const fromRawCType: ICType = { + schema: rawCType, + owner: identityAlice.address, + hash: '', + } + + const testCType: CType = CType.fromCType(fromRawCType) + + const claim = Claim.fromCTypeAndClaimContents( + testCType, + claimContents, + identityAlice.address + ) it('can be made from object', () => { const claimObj = JSON.parse(JSON.stringify(claim)) - expect(Claim.fromObject(claimObj)).toEqual(claim) + expect(Claim.fromClaim(claimObj, testCType.schema)).toEqual(claim) }) }) diff --git a/src/claim/Claim.ts b/src/claim/Claim.ts index 032b38540..ca99caaa3 100644 --- a/src/claim/Claim.ts +++ b/src/claim/Claim.ts @@ -13,35 +13,66 @@ /** * Dummy comment needed for correct doc display, do not remove */ -import CType from '../ctype/CType' +import ICType from '../ctype/CType' import { verifyClaimStructure } from '../ctype/CTypeUtils' -import Identity from '../identity/Identity' import IClaim from '../types/Claim' +import IPublicIdentity from '../types/PublicIdentity' -function verifyClaim(claimContents: object, cType: CType): boolean { - return verifyClaimStructure(claimContents, cType.schema) +function verifyClaim( + claimContents: object, + cTypeSchema: ICType['schema'] +): boolean { + return verifyClaimStructure(claimContents, cTypeSchema) } export default class Claim implements IClaim { - public static fromObject(obj: IClaim): Claim { - const newClaim = Object.create(Claim.prototype) - return Object.assign(newClaim, obj) + public static fromClaim( + claimInput: IClaim, + cTypeSchema: ICType['schema'] + ): Claim { + if (cTypeSchema) { + if (!verifyClaim(claimInput.contents, cTypeSchema)) { + throw Error('Claim not valid') + } + } + return new Claim(claimInput) + } + + public static fromCTypeAndClaimContents( + ctypeInput: ICType, + claimContents: object, + claimOwner: IPublicIdentity['address'] + ): Claim { + if (ctypeInput.schema) { + if (!verifyClaim(claimContents, ctypeInput.schema)) { + throw Error('Claim not valid') + } + } + return new Claim({ + cTypeHash: ctypeInput.hash, + contents: claimContents, + owner: claimOwner, + }) } - public cType: IClaim['cType'] + public cTypeHash: IClaim['cTypeHash'] public contents: IClaim['contents'] public owner: IClaim['owner'] - public constructor( - cType: CType, - contents: IClaim['contents'], - identity: Identity - ) { - if (!verifyClaim(contents, cType)) { - throw Error('Claim not valid') + public constructor(claimInput: IClaim) { + if (!claimInput.cTypeHash || !claimInput.contents || !claimInput.owner) { + throw new Error( + `Property Not Provided while building Claim:\n + claimInput.cTypeHash:\n + ${claimInput.cTypeHash}\n + claimInput.contents:\n + ${claimInput.contents}\n + claimInput.owner:\n' + ${claimInput.owner}` + ) } - this.cType = cType.hash - this.contents = contents - this.owner = identity.address + this.cTypeHash = claimInput.cTypeHash + this.contents = claimInput.contents + this.owner = claimInput.owner } } diff --git a/src/ctype/CType.chain.ts b/src/ctype/CType.chain.ts index 15ff85bd1..f0caadc7d 100644 --- a/src/ctype/CType.chain.ts +++ b/src/ctype/CType.chain.ts @@ -35,7 +35,9 @@ export async function store( } function decode(encoded: QueryResult): IPublicIdentity['address'] | null { - return encoded && encoded.encodedLength ? encoded.toString() : null + return encoded && encoded.encodedLength && !encoded.isEmpty + ? encoded.toString() + : null } export async function getOwner( diff --git a/src/ctype/CType.spec.ts b/src/ctype/CType.spec.ts index 384342691..662e3c640 100644 --- a/src/ctype/CType.spec.ts +++ b/src/ctype/CType.spec.ts @@ -8,58 +8,54 @@ import Claim from '../claim/Claim' jest.mock('../blockchainApiConnection/BlockchainApiConnection') describe('CType', () => { - const ctypeModel = { - schema: { - $id: 'http://example.com/ctype-1', - $schema: 'http://kilt-protocol.org/draft-01/ctype#', - properties: { - 'first-property': { type: 'integer' }, - 'second-property': { type: 'string' }, - }, - type: 'object', + const ctypeModel: ICType['schema'] = { + $id: 'http://example.com/ctype-1', + $schema: 'http://kilt-protocol.org/draft-01/ctype#', + properties: { + 'first-property': { type: 'integer' }, + 'second-property': { type: 'string' }, }, - metadata: { - title: { default: 'CType Title' }, - description: {}, - properties: { - 'first-property': { title: { default: 'First Property' } }, - 'second-property': { title: { default: 'Second Property' } }, - }, - }, - } as ICType + type: 'object', + } - const rawCtype = { - schema: { - $id: 'http://example.com/ctype-1', - $schema: 'http://kilt-protocol.org/draft-01/ctype#', - properties: { - name: { type: 'string' }, - }, - type: 'object', - }, - metadata: { - title: { default: 'CType Title' }, - description: {}, - properties: { - name: { title: { default: 'Name' } }, - }, + const rawCType: ICType['schema'] = { + $id: 'http://example.com/ctype-1', + $schema: 'http://kilt-protocol.org/draft-01/ctype#', + properties: { + name: { type: 'string' }, }, - } as ICType - - const claimCtype = new CType(rawCtype) + type: 'object', + } const identityAlice = Identity.buildFromURI('//Alice') + const fromRawCType: ICType = { + schema: rawCType, + owner: identityAlice.address, + hash: '', + } + + const fromCTypeModel: ICType = { + schema: ctypeModel, + owner: identityAlice.address, + hash: '', + } + const claimCtype = CType.fromCType(fromRawCType) + const claimContents = { name: 'Bob', } - const claim = new Claim(claimCtype, claimContents, identityAlice) + const claim = Claim.fromCTypeAndClaimContents( + claimCtype, + claimContents, + identityAlice.address + ) it('stores ctypes', async () => { const testHash = Crypto.hashStr('1234') - const ctype = new CType(ctypeModel) + const ctype = CType.fromCType(fromCTypeModel) ctype.hash = testHash const resultCtype = { ...ctype, @@ -75,15 +71,17 @@ describe('CType', () => { }) it('verifies the claim structure', () => { expect(claimCtype.verifyClaimStructure(claim)).toBeTruthy() - expect(claimCtype.verifyClaimStructure(!claim)).toBeFalsy() + // @ts-ignore + claim.contents.name = 123 + expect(claimCtype.verifyClaimStructure(claim)).toBeFalsy() }) it('throws error on wrong ctype hash', () => { const wrongRawCtype = { - ...rawCtype, + ...fromRawCType, hash: '0x1234', } expect(() => { - return new CType(wrongRawCtype) + return CType.fromCType(wrongRawCtype) }).toThrow() }) }) diff --git a/src/ctype/CType.ts b/src/ctype/CType.ts index 32191a791..35505fb7c 100644 --- a/src/ctype/CType.ts +++ b/src/ctype/CType.ts @@ -17,30 +17,33 @@ import ICType from '../types/CType' import Identity from '../identity/Identity' import { getOwner, store } from './CType.chain' import TxStatus from '../blockchain/TxStatus' +import IClaim from '../types/Claim' export default class CType implements ICType { - public static fromObject(obj: ICType): CType { - const newObject = Object.create(CType.prototype) - return Object.assign(newObject, obj) + public static fromCType(cTypeInput: ICType): CType { + if (!CTypeUtils.verifySchema(cTypeInput, CTypeWrapperModel)) { + throw new Error('CType does not correspond to schema') + } + if (cTypeInput.hash) { + if (CTypeUtils.getHashForSchema(cTypeInput.schema) !== cTypeInput.hash) { + throw Error('provided and generated cType hash are not matching') + } + } + return new CType(cTypeInput) } public hash: ICType['hash'] - public owner?: ICType['owner'] + public owner: ICType['owner'] | null public schema: ICType['schema'] - public metadata: ICType['metadata'] - - public constructor(ctype: ICType) { - if (!CTypeUtils.verifySchema(ctype, CTypeWrapperModel)) { - throw new Error('CType does not correspond to schema') - } - this.schema = ctype.schema - this.metadata = ctype.metadata - this.owner = ctype.owner - this.hash = CTypeUtils.getHashForSchema(this.schema) + public constructor(cTypeInput: ICType) { + this.schema = cTypeInput.schema + this.owner = cTypeInput.owner - if (ctype.hash && this.hash !== ctype.hash) { - throw Error('provided and generated cType hash are not the same') + if (!cTypeInput.hash) { + this.hash = CTypeUtils.getHashForSchema(this.schema) + } else { + this.hash = cTypeInput.hash } } @@ -48,8 +51,8 @@ export default class CType implements ICType { return store(this, identity) } - public verifyClaimStructure(claim: any): boolean { - return CTypeUtils.verifySchema(claim, this.schema) + public verifyClaimStructure(claim: IClaim): boolean { + return CTypeUtils.verifySchema(claim.contents, this.schema) } public async verifyStored(): Promise { diff --git a/src/ctype/CTypeMetadata.spec.ts b/src/ctype/CTypeMetadata.spec.ts new file mode 100644 index 000000000..743dc7153 --- /dev/null +++ b/src/ctype/CTypeMetadata.spec.ts @@ -0,0 +1,51 @@ +import CType from './CType' +import ICType from '../types/CType' +import CTypeMetadata from './CTypeMetadata' +import CTypeUtils from './CTypeUtils' +import { MetadataModel } from './CTypeSchema' +import ICTypeMetadata from '../types/CTypeMetadata' +import Identity from '../identity/Identity' + +describe('CType', () => { + const identityAlice = Identity.buildFromURI('//Alice') + + const rawCType: ICType['schema'] = { + $id: 'http://example.com/ctype-1', + $schema: 'http://kilt-protocol.org/draft-01/ctype#', + properties: { + 'first-property': { type: 'integer' }, + 'second-property': { type: 'string' }, + }, + type: 'object', + } + + const fromRawCType: ICType = { + schema: rawCType, + owner: identityAlice.address, + hash: '', + } + const ctype = CType.fromCType(fromRawCType) + + const ctypeMetadata: ICTypeMetadata['metadata'] = { + title: { default: 'Title' }, + description: { default: 'Description' }, + properties: { + 'first-property': { title: { default: 'First Property' } }, + 'second-property': { title: { default: 'Second Property' } }, + }, + } + + const metadata = new CTypeMetadata({ + metadata: ctypeMetadata, + ctypeHash: ctype.hash, + }) + + it('verifies the metadata of a ctype', async () => { + expect(metadata.ctypeHash).not.toHaveLength(0) + expect(CTypeUtils.verifySchema(metadata, MetadataModel)).toBeTruthy() + expect(CTypeUtils.verifySchema(ctypeMetadata, MetadataModel)).toBeFalsy() + }) + it('checks if the metadata matches corresponding ctype hash', async () => { + expect(metadata.ctypeHash).toEqual(ctype.hash) + }) +}) diff --git a/src/ctype/CTypeMetadata.ts b/src/ctype/CTypeMetadata.ts new file mode 100644 index 000000000..1f27d916b --- /dev/null +++ b/src/ctype/CTypeMetadata.ts @@ -0,0 +1,16 @@ +import ICTypeMetadata from '../types/CTypeMetadata' +import { MetadataModel } from './CTypeSchema' +import CTypeUtils from './CTypeUtils' + +export default class CTypeMetadata implements ICTypeMetadata { + public ctypeHash: ICTypeMetadata['ctypeHash'] + public metadata: ICTypeMetadata['metadata'] + + public constructor(metdata: ICTypeMetadata) { + if (!CTypeUtils.verifySchema(metdata, MetadataModel)) { + throw new Error('CTypeMetadata does not correspond to schema') + } + this.metadata = metdata.metadata + this.ctypeHash = metdata.ctypeHash + } +} diff --git a/src/ctype/CTypeSchema.ts b/src/ctype/CTypeSchema.ts index bfff5faf0..8345ca3ab 100644 --- a/src/ctype/CTypeSchema.ts +++ b/src/ctype/CTypeSchema.ts @@ -8,8 +8,8 @@ // TODO: Generate from actual CTypeModel // TODO: The SDK is not really responsible for this, since it is editor specific export const CTypeInputModel = { - $schema: 'http://json-schema.org/draft-07/schema#', $id: 'http://kilt-protocol.org/draft-01/ctype-input#', + $schema: 'http://json-schema.org/draft-07/schema#', title: 'CTYPE', type: 'object', properties: { @@ -79,8 +79,8 @@ export const CTypeInputModel = { } export const CTypeModel = { - $schema: 'http://json-schema.org/draft-07/schema#', $id: 'http://kilt-protocol.org/draft-01/ctype#', + $schema: 'http://json-schema.org/draft-07/schema#', type: 'object', properties: { $id: { @@ -121,28 +121,91 @@ export const CTypeModel = { } export const CTypeWrapperModel = { - $schema: 'http://json-schema.org/draft-07/schema#', $id: 'http://kilt-protocol.org/draft-01/ctype-wrapper#', + $schema: 'http://json-schema.org/draft-07/schema#', type: 'object', properties: { schema: { type: 'object', properties: CTypeModel.properties, }, + owner: { type: 'string' }, hash: { type: 'string', - minLength: 1, }, - metamodel: { + }, + required: ['schema'], +} + +export const MetadataModel = { + $id: 'http://kilt-protocol.org/draft-01/ctype-metadata', + $schema: 'http://json-schema.org/draft-07/schema#', + type: 'object', + properties: { + metadata: { type: 'object', - properties: {}, - patternProperties: { - '^.*$': { + properties: { + title: { + type: 'object', + properties: { + default: { + type: 'string', + }, + }, + patternProperties: { + '^.*$': { + type: 'string', + }, + }, + required: ['default'], + }, + description: { + type: 'object', + properties: { + default: { + type: 'string', + }, + }, + patternProperties: { + '^.*$': { + type: 'string', + }, + }, + required: ['default'], + }, + properties: { type: 'object', properties: {}, + patternProperties: { + '^.*$': { + type: 'object', + properties: { + title: { + type: 'object', + properties: { + default: { + type: 'string', + }, + }, + patternProperties: { + '^.*$': { + type: 'string', + }, + }, + required: ['default'], + }, + }, + required: ['title'], + additionalProperties: false, + }, + }, }, }, + required: ['title', 'description'], + additionalProperties: false, }, + ctypeHash: { type: 'string', minLength: 1 }, }, - required: ['schema'], + required: ['metadata', 'ctypeHash'], + additionalProperties: false, } diff --git a/src/ctype/CTypeUtils.spec.ts b/src/ctype/CTypeUtils.spec.ts index 986a774b8..14f314146 100644 --- a/src/ctype/CTypeUtils.spec.ts +++ b/src/ctype/CTypeUtils.spec.ts @@ -28,25 +28,15 @@ const ctypeInput = { required: ['first-property', 'second-property'], } -const ctypeWrapperModel = { - schema: { - $id: 'http://example.com/ctype-1', - $schema: 'http://kilt-protocol.org/draft-01/ctype#', - properties: { - 'first-property': { type: 'integer' }, - 'second-property': { type: 'string' }, - }, - type: 'object', - }, - metadata: { - title: { default: 'CType Title' }, - description: {}, - properties: { - 'first-property': { title: { default: 'First Property' } }, - 'second-property': { title: { default: 'Second Property' } }, - }, +const ctypeWrapperModel: ICType['schema'] = { + $id: 'http://example.com/ctype-1', + $schema: 'http://kilt-protocol.org/draft-01/ctype#', + properties: { + 'first-property': { type: 'integer' }, + 'second-property': { type: 'string' }, }, -} as ICType + type: 'object', +} const goodClaim = { 'first-property': 10, @@ -61,10 +51,8 @@ const badClaim = { describe('CTypeUtils', () => { it('verifies claims', () => { - expect( - verifyClaimStructure(goodClaim, ctypeWrapperModel.schema) - ).toBeTruthy() - expect(verifyClaimStructure(badClaim, ctypeWrapperModel.schema)).toBeFalsy() + expect(verifyClaimStructure(goodClaim, ctypeWrapperModel)).toBeTruthy() + expect(verifyClaimStructure(badClaim, ctypeWrapperModel)).toBeFalsy() expect(verifySchemaWithErrors(badClaim, CTypeWrapperModel, [])).toBeFalsy() expect(() => { verifyClaimStructure(badClaim, ctypeInput) @@ -72,9 +60,9 @@ describe('CTypeUtils', () => { }) it('verifies ctypes', () => { expect(verifySchema(ctypeInput, CTypeInputModel)).toBeTruthy() - expect(verifySchema(ctypeWrapperModel.schema, CTypeModel)).toBeTruthy() + expect(verifySchema(ctypeWrapperModel, CTypeModel)).toBeTruthy() + expect(verifySchema(ctypeWrapperModel, CTypeInputModel)).toBeFalsy() expect(verifySchema(ctypeWrapperModel, CTypeInputModel)).toBeFalsy() - expect(verifySchema(ctypeWrapperModel.schema, CTypeInputModel)).toBeFalsy() expect( verifySchema( { diff --git a/src/ctype/CTypeUtils.ts b/src/ctype/CTypeUtils.ts index cc5bf075a..bb0b77489 100644 --- a/src/ctype/CTypeUtils.ts +++ b/src/ctype/CTypeUtils.ts @@ -11,13 +11,13 @@ import ICType from '../types/CType' import Crypto from '../crypto' export function verifySchemaWithErrors( - model: any, - metaModel: any, + object: any, + schema: any, messages?: string[] ): boolean { const ajv = new Ajv() ajv.addMetaSchema(CTypeModel) - const result = ajv.validate(metaModel, model) + const result = ajv.validate(schema, object) if (!result && ajv.errors) { if (messages) { ajv.errors.forEach((error: any) => { @@ -28,8 +28,8 @@ export function verifySchemaWithErrors( return !!result } -export function verifySchema(model: any, metaModel: any): boolean { - return verifySchemaWithErrors(model, metaModel) +export function verifySchema(object: any, schema: any): boolean { + return verifySchemaWithErrors(object, schema) } export function verifyClaimStructure(claim: any, schema: any): boolean { @@ -42,3 +42,10 @@ export function verifyClaimStructure(claim: any, schema: any): boolean { export function getHashForSchema(schema: ICType['schema']): string { return Crypto.hashObjectAsStr(schema) } + +export default { + verifySchema, + verifySchemaWithErrors, + verifyClaimStructure, + getHashForSchema, +} diff --git a/src/did/Did.spec.ts b/src/did/Did.spec.ts index 83b902e30..6a70c6d4f 100644 --- a/src/did/Did.spec.ts +++ b/src/did/Did.spec.ts @@ -2,6 +2,7 @@ import { Text, Tuple, Option, U8a } from '@polkadot/types' import { Did } from '..' import { IDid } from './Did' import Identity from '../identity/Identity' +import { getIdentifierFromAddress } from './Did.utils' jest.mock('../blockchainApiConnection/BlockchainApiConnection') @@ -73,12 +74,14 @@ describe('DID', () => { expect(await did.store(alice)).toEqual({ status: 'ok' }) }) - it('get default did document', async () => { + it('creates default did document', async () => { const did = Did.fromIdentity( Identity.buildFromURI('//Alice'), 'http://myDID.kilt.io' ) - expect(did.getDefaultDocument('http://myDID.kilt.io/service')).toEqual({ + expect( + did.createDefaultDidDocument('http://myDID.kilt.io/service') + ).toEqual({ '@context': 'https://w3id.org/did/v1', authentication: { publicKey: [ @@ -114,6 +117,110 @@ describe('DID', () => { }) }) + it('creates default did document (static)', async () => { + const alice = Identity.buildFromURI('//Alice') + expect( + Did.createDefaultDidDocument( + Did.getIdentifierFromAddress(alice.address), + alice.boxPublicKeyAsHex, + alice.signPublicKeyAsHex, + 'http://myDID.kilt.io/service' + ) + ).toEqual({ + '@context': 'https://w3id.org/did/v1', + authentication: { + publicKey: [ + 'did:kilt:5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TTpu#key-1', + ], + type: 'Ed25519SignatureAuthentication2018', + }, + id: 'did:kilt:5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TTpu', + publicKey: [ + { + controller: + 'did:kilt:5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TTpu', + id: 'did:kilt:5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TTpu#key-1', + publicKeyHex: + '0x88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee', + type: 'Ed25519VerificationKey2018', + }, + { + controller: + 'did:kilt:5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TTpu', + id: 'did:kilt:5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TTpu#key-2', + publicKeyHex: + '0xe54bdd5e4f0929471fb333b17c0d865fc4f2cbc45364602bd1b85550328c3c62', + type: 'X25519Salsa20Poly1305Key2018', + }, + ], + service: [ + { + serviceEndpoint: 'http://myDID.kilt.io/service', + type: 'KiltMessagingService', + }, + ], + }) + }) + + it('verifies the did document signature (untampered data)', () => { + const identity = Identity.buildFromURI('//Alice') + const did = Did.fromIdentity( + Identity.buildFromURI('//Alice'), + 'http://myDID.kilt.io' + ) + const didDocument = did.createDefaultDidDocument( + 'http://myDID.kilt.io/service' + ) + const signedDidDocument = Did.signDidDocument(didDocument, identity) + expect( + Did.verifyDidDocumentSignature( + signedDidDocument, + getIdentifierFromAddress(identity.address) + ) + ).toBeTruthy() + }) + + it('verifies the did document signature (tampered data)', () => { + const identity = Identity.buildFromURI('//Alice') + const did = Did.fromIdentity(identity, 'http://myDID.kilt.io') + const didDocument = did.createDefaultDidDocument( + 'http://myDID.kilt.io/service' + ) + const signedDidDocument = Did.signDidDocument(didDocument, identity) + const tamperedSignedDidDocument = { + ...signedDidDocument, + authentication: { + type: 'Ed25519SignatureAuthentication2018', + publicKey: ['did:kilt:123'], + }, + } + expect( + Did.verifyDidDocumentSignature( + tamperedSignedDidDocument, + getIdentifierFromAddress(identity.address) + ) + ).toBeFalsy() + }) + + it("throws when verifying the did document signature if identitifiers don't match", () => { + const identityAlice = Identity.buildFromURI('//Alice') + const did = Did.fromIdentity(identityAlice, 'http://myDID.kilt.io') + const didDocument = did.createDefaultDidDocument( + 'http://myDID.kilt.io/service' + ) + const signedDidDocument = Did.signDidDocument(didDocument, identityAlice) + const identityBob = Identity.buildFromURI('//Bob') + const id = getIdentifierFromAddress(identityBob.address) + + expect(() => { + Did.verifyDidDocumentSignature(signedDidDocument, id) + }).toThrowError( + new Error( + `This identifier (${id}) doesn't match the DID Document's identifier (${signedDidDocument.id})` + ) + ) + }) + it('gets identifier from address', () => { expect( Did.getIdentifierFromAddress( diff --git a/src/did/Did.ts b/src/did/Did.ts index 8e1166acc..e96375f82 100644 --- a/src/did/Did.ts +++ b/src/did/Did.ts @@ -15,7 +15,13 @@ import Identity from '../identity/Identity' import { factory } from '../config/ConfigLog' import TxStatus from '../blockchain/TxStatus' import IPublicIdentity from '../types/PublicIdentity' -import { getIdentifierFromAddress, getAddressFromIdentifier } from './Did.utils' +import { + getIdentifierFromAddress, + getAddressFromIdentifier, + createDefaultDidDocument, + verifyDidDocumentSignature, + signDidDocument, +} from './Did.utils' import { store, queryByAddress, queryByIdentifier, remove } from './Did.chain' const log = factory.getLogger('DID') @@ -24,10 +30,12 @@ export const IDENTIFIER_PREFIX = 'did:kilt:' export const SERVICE_KILT_MESSAGING = 'KiltMessagingService' export const KEY_TYPE_SIGNATURE = 'Ed25519VerificationKey2018' export const KEY_TYPE_ENCRYPTION = 'X25519Salsa20Poly1305Key2018' +export const KEY_TYPE_AUTHENTICATION = 'Ed25519SignatureAuthentication2018' +export const CONTEXT = 'https://w3id.org/did/v1' export interface IDid { /** - * The DID identifier under which it is store on chain. + * The DID identifier under which this DID object is stored on-chain. */ identifier: string /** @@ -39,61 +47,109 @@ export interface IDid { */ publicSigningKey: string /** - * The document store reference. + * The document store reference, usually a URL. */ documentStore: string | null } +export interface IDidDocumentCore { + // id and context are the only mandatory ppties, described as "MUST"s in the w3c spec https://w3c.github.io/did-core/ + id: string + '@context': string +} + +export interface IDidDocumentPublicKey { + id: string + type: string + controller: string + publicKeyHex: string +} + +export interface IDidDocumentPpties { + authentication: object + publicKey: IDidDocumentPublicKey[] + service: any +} + +export interface IDidDocument + extends IDidDocumentCore, + Partial {} + +export interface IDidDocumentSigned extends IDidDocument { + signature: string +} + export default class Did implements IDid { + public readonly identifier: string + public readonly publicBoxKey: string + public readonly publicSigningKey: string + public readonly documentStore: string | null + + private constructor( + identifier: string, + publicBoxKey: string, + publicSigningKey: string, + documentStore: string | null = null + ) { + this.identifier = identifier + this.publicBoxKey = publicBoxKey + this.publicSigningKey = publicSigningKey + this.documentStore = documentStore + } + /** - * @description Queries the [Did] from chain using the [identifier] + * Builds a [[Did]] object from the given [[Identity]]. * - * @param identifier the DID identifier - * @returns promise containing the [[Did]] or [null] + * @param identity The identity used to build the [[Did]] object. + * @param documentStore The storage location of the associated DID Document; usally a URL. + * @returns The [[Did]] object. */ - public static queryByIdentifier(identifier: string): Promise { - return queryByIdentifier(identifier) + public static fromIdentity(identity: Identity, documentStore?: string): Did { + const identifier = getIdentifierFromAddress(identity.address) + return new Did( + identifier, + identity.boxPublicKeyAsHex, + identity.signPublicKeyAsHex, + documentStore + ) } /** - * @description Gets the complete KILT DID from an [address] (in KILT, the method-specific ID is an address). Reverse of [[getAddressFromIdentifier]]. + * Stores the [[Did]] object on-chain. * - * @param address An address, e.g. "5CtPYoDuQQF...". - * @returns The associated KILT DID, e.g. "did:kilt:5CtPYoDuQQF...". + * @param identity The identity used to store the [[Did]] object on-chain. + * @returns A promise containing the [[TxStatus]] (transaction status). */ - public static getIdentifierFromAddress( - address: IPublicIdentity['address'] - ): IDid['identifier'] { - return getIdentifierFromAddress(address) + public async store(identity: Identity): Promise { + log.debug(`Create tx for 'did.add'`) + return store(this, identity) } /** - * @description Gets the [address] from a complete KILT DID (in KILT, the method-specific ID is an address). Reverse of [[getIdentifierFromAddress]]. + * Queries the [[Did]] object from the chain using the [identifier]. * - * @param identifier A KILT DID, e.g. "did:kilt:5CtPYoDuQQF...". - * @returns The associated address, e.g. "5CtPYoDuQQF...". + * @param identifier A KILT DID identifier, e.g. "did:kilt:5CtPYoDuQQF...". + * @returns A promise containing the [[Did]] or [null]. */ - public static getAddressFromIdentifier( - identifier: IDid['identifier'] - ): IPublicIdentity['address'] { - return getAddressFromIdentifier(identifier) + public static queryByIdentifier(identifier: string): Promise { + return queryByIdentifier(identifier) } /** - * @description Queries the [Did] from chain using the [address] + * Queries the [[Did]] object from the chain using the [address]. * - * @param address the DIDs address - * @returns promise containing the [[Did]] or [null] + * @param address The address associated to this [[Did]]. + * @returns A promise containing the [[Did]] or [null]. */ public static queryByAddress(address: string): Promise { return queryByAddress(address) } /** - * @description Removes the [[Did]] attached to [identity] from chain + * Removes the [[Did]] object attached to a given [[Identity]] from the chain. * - * @param identity the identity for which to delete the [[Did]] - * @returns promise containing the [[TxStatus]] + * @param identity The identity for which to delete the [[Did]]. + * @returns A promise containing the [[TxStatus]] (transaction status). */ public static async remove(identity: Identity): Promise { log.debug(`Create tx for 'did.remove'`) @@ -101,87 +157,92 @@ export default class Did implements IDid { } /** - * @description Builds a [[Did]] from the given [identity]. + * Gets the complete KILT DID from an [address] (in KILT, the method-specific ID is an address). Reverse of [[getAddressFromIdentifier]]. + * + * @param address An address, e.g. "5CtPYoDuQQF...". + * @returns The associated KILT DID identifier, e.g. "did:kilt:5CtPYoDuQQF...". + */ + public static getIdentifierFromAddress( + address: IPublicIdentity['address'] + ): IDid['identifier'] { + return getIdentifierFromAddress(address) + } + + /** + * Gets the [address] from a complete KILT DID (in KILT, the method-specific ID is an address). Reverse of [[getIdentifierFromAddress]]. * - * @param identity the identity used to build the [[Did]] - * @param documentStore optional document store reference - * @returns the [[Did]] + * @param identifier A KILT DID identifier, e.g. "did:kilt:5CtPYoDuQQF...". + * @returns The associated address, e.g. "5CtPYoDuQQF...". */ - public static fromIdentity(identity: Identity, documentStore?: string): Did { - const identifier = getIdentifierFromAddress(identity.address) - return new Did( - identifier, - identity.boxPublicKeyAsHex, - identity.signPublicKeyAsHex, - documentStore - ) + public static getAddressFromIdentifier( + identifier: IDid['identifier'] + ): IPublicIdentity['address'] { + return getAddressFromIdentifier(identifier) } - public readonly identifier: string - public readonly publicBoxKey: string - public readonly publicSigningKey: string - public readonly documentStore: string | null + /** + * Signs (the hash of) a DID Document. + * + * @param didDocument A DID Document, e.g. created via [[createDefaultDidDocument]]. + * @param identity [[Identity]] representing the DID subject for this DID Document, and used for signature. + * @returns The signed DID Document. + */ + public static signDidDocument( + didDocument: IDidDocument, + identity: Identity + ): IDidDocumentSigned { + return signDidDocument(didDocument, identity) + } - private constructor( - identifier: string, - publicBoxKey: string, - publicSigningKey: string, - documentStore: string | null = null - ) { - this.identifier = identifier - this.publicBoxKey = publicBoxKey - this.publicSigningKey = publicSigningKey - this.documentStore = documentStore + /** + * Verifies the signature of a DID Document, to check whether the data has been tampered with. + * + * @param didDocument A signed DID Document. + * @param identifier A KILT DID identifier, e.g. "did:kilt:5CtPYoDuQQF...". + * @returns Whether the DID Document's signature is valid. + */ + public static verifyDidDocumentSignature( + didDocument: IDidDocumentSigned, + identifier: string + ): boolean { + return verifyDidDocumentSignature(didDocument, identifier) } /** - * @description Stores the [[Did]] on chain + * Builds the default DID Document from this [[Did]] object. * - * @param identity the identity used to store the [[Did]] on chain - * @returns promise containing the [[TxStatus]] + * @param kiltServiceEndpoint A URI pointing to the service endpoint. + * @returns The default DID Document. */ - public async store(identity: Identity): Promise { - log.debug(`Create tx for 'did.add'`) - return store(this, identity) + public createDefaultDidDocument(kiltServiceEndpoint?: string): IDidDocument { + return createDefaultDidDocument( + this.identifier, + this.publicBoxKey, + this.publicSigningKey, + kiltServiceEndpoint + ) } /** - * @description Builds the default DID document from this [[Did]] + * [STATIC] Builds a default DID Document. * - * @param kiltServiceEndpoint URI pointing to the service endpoint - * @returns the default DID document + * @param identifier A KILT DID identifier, e.g. "did:kilt:5CtPYoDuQQF...". + * @param publicBoxKey The public encryption key of the DID subject of this KILT DID identifier. + * @param publicSigningKey The public signing key of the DID subject of this KILT DID identifier. + * @param kiltServiceEndpoint A URI pointing to the service endpoint. + * @returns The default DID Document. */ - public getDefaultDocument(kiltServiceEndpoint?: string): object { - const result = { - id: this.identifier, - authentication: { - type: 'Ed25519SignatureAuthentication2018', - publicKey: [`${this.identifier}#key-1`], - }, - publicKey: [ - { - id: `${this.identifier}#key-1`, - type: KEY_TYPE_SIGNATURE, - controller: this.identifier, - publicKeyHex: this.publicSigningKey, - }, - { - id: `${this.identifier}#key-2`, - type: KEY_TYPE_ENCRYPTION, - controller: this.identifier, - publicKeyHex: this.publicBoxKey, - }, - ], - '@context': 'https://w3id.org/did/v1', - service: kiltServiceEndpoint - ? [ - { - type: SERVICE_KILT_MESSAGING, - serviceEndpoint: kiltServiceEndpoint, - }, - ] - : [], - } - return result + public static createDefaultDidDocument( + identifier: string, + publicBoxKey: string, + publicSigningKey: string, + kiltServiceEndpoint?: string + ): IDidDocument { + return createDefaultDidDocument( + identifier, + publicBoxKey, + publicSigningKey, + kiltServiceEndpoint + ) } } diff --git a/src/did/Did.utils.ts b/src/did/Did.utils.ts index b378e393f..3a3e855a7 100644 --- a/src/did/Did.utils.ts +++ b/src/did/Did.utils.ts @@ -8,8 +8,20 @@ import { isHex, hexToString } from '@polkadot/util' import IPublicIdentity from '../types/PublicIdentity' +import Crypto from '../crypto' +import Identity from '../identity/Identity' import { QueryResult } from '../blockchain/Blockchain' -import Did, { IDid, IDENTIFIER_PREFIX } from './Did' +import Did, { + IDid, + IDidDocument, + IDidDocumentSigned, + IDENTIFIER_PREFIX, + CONTEXT, + KEY_TYPE_AUTHENTICATION, + KEY_TYPE_SIGNATURE, + KEY_TYPE_ENCRYPTION, + SERVICE_KILT_MESSAGING, +} from './Did' export function decodeDid( identifier: string, @@ -45,3 +57,83 @@ export function getAddressFromIdentifier( } return identifier.substr(IDENTIFIER_PREFIX.length) } + +export function createDefaultDidDocument( + identifier: string, + publicBoxKey: string, + publicSigningKey: string, + kiltServiceEndpoint?: string +): IDidDocument { + return { + id: identifier, + '@context': CONTEXT, + authentication: { + type: KEY_TYPE_AUTHENTICATION, + publicKey: [`${identifier}#key-1`], + }, + publicKey: [ + { + id: `${identifier}#key-1`, + type: KEY_TYPE_SIGNATURE, + controller: identifier, + publicKeyHex: publicSigningKey, + }, + { + id: `${identifier}#key-2`, + type: KEY_TYPE_ENCRYPTION, + controller: identifier, + publicKeyHex: publicBoxKey, + }, + ], + service: kiltServiceEndpoint + ? [ + { + type: SERVICE_KILT_MESSAGING, + serviceEndpoint: kiltServiceEndpoint, + }, + ] + : [], + } +} + +export function verifyDidDocumentSignature( + didDocument: IDidDocumentSigned, + identifier: string +): boolean { + if (!didDocument || !didDocument.signature || !identifier) { + throw new Error( + `Missing data for verification (either didDocument, didDocumentHash, signature, or address is missing):\n + didDocument:\n + ${didDocument}\n + signature:\n + ${didDocument.signature}\n + address:\n + ${identifier}\n + ` + ) + } + const { id } = didDocument + if (identifier !== id) { + throw new Error( + `This identifier (${identifier}) doesn't match the DID Document's identifier (${id})` + ) + } + const unsignedDidDocument = { ...didDocument } + delete unsignedDidDocument.signature + return Crypto.verify( + Crypto.hashObjectAsStr(unsignedDidDocument), + didDocument.signature, + getAddressFromIdentifier(identifier) + ) +} + +export function signDidDocument( + didDocument: IDidDocument, + identity: Identity +): IDidDocumentSigned { + const didDocumentHash = Crypto.hashObjectAsStr(didDocument) + return { + ...didDocument, + signature: identity.signStr(didDocumentHash), + } +} diff --git a/src/identity/PublicIdentity.ts b/src/identity/PublicIdentity.ts index 8c1c05c13..230e4c5fd 100644 --- a/src/identity/PublicIdentity.ts +++ b/src/identity/PublicIdentity.ts @@ -86,7 +86,9 @@ export default class PublicIdentity implements IPublicIdentity { * ``` */ public static fromDidDocument(didDocument: object): IPublicIdentity | null { - if (!isDIDDocument(didDocument)) return null + if (!isDIDDocument(didDocument)) { + return null + } try { return new PublicIdentity( @@ -202,7 +204,9 @@ export default class PublicIdentity implements IPublicIdentity { return object[filterKey] && object[filterKey] === filterValue }) - if (correctObj && correctObj[property]) return correctObj[property] + if (correctObj && correctObj[property]) { + return correctObj[property] + } throw new Error() } diff --git a/src/index.ts b/src/index.ts index 8b18933db..1ed4fad26 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,6 +12,7 @@ import * as Balance from './balance/Balance.chain' import Identity from './identity/Identity' import PublicIdentity, { IURLResolver } from './identity/PublicIdentity' import CType from './ctype/CType' +import CTypeMetadata from './ctype/CTypeMetadata' import * as CTypeUtils from './ctype/CTypeUtils' import Claim from './claim/Claim' import RequestForAttestation from './requestforattestation/RequestForAttestation' @@ -32,6 +33,7 @@ export * from './errorhandling/ExtrinsicError' // ---- Types, which define the most basic KILT objects ---- export { default as IPublicIdentity } from './types/PublicIdentity' export { default as ICType } from './types/CType' +export { default as ICTypeMetadata } from './types/CTypeMetadata' export { default as IClaim } from './types/Claim' export { default as IAttestedClaim } from './types/AttestedClaim' export { default as IAttestation } from './types/Attestation' @@ -52,6 +54,7 @@ export { PublicIdentity, IURLResolver, CType, + CTypeMetadata, CTypeUtils, Claim, RequestForAttestation, @@ -79,6 +82,7 @@ export default { Identity, PublicIdentity, CType, + CTypeMetadata, Claim, RequestForAttestation, Attestation, diff --git a/src/messaging/Message.spec.ts b/src/messaging/Message.spec.ts index 517455896..398cf61ef 100644 --- a/src/messaging/Message.spec.ts +++ b/src/messaging/Message.spec.ts @@ -10,6 +10,7 @@ import Message, { } from './Message' import { EncryptedAsymmetricString } from '../crypto/Crypto' import Crypto from '../crypto' +import IRequestForAttestation from '../types/RequestForAttestation' describe('Messaging', () => { const identityAlice = Identity.buildFromURI('//Alice') @@ -102,20 +103,23 @@ describe('Messaging', () => { }) it('verify message sender is owner', () => { - const requestAttestationBody: IRequestAttestationForClaim = { - content: { - delegationId: null, - ctypeHash: { nonce: '0x12345678', hash: '0x12345678' }, - claim: { - cType: '0x12345678', - owner: identityAlice.getPublicIdentity().address, - contents: {}, - }, - claimHashTree: {}, - legitimations: [], - hash: '0x12345678', - claimerSignature: '0x12345678', + const content = { + claim: { + cTypeHash: '0x12345678', + owner: identityAlice.getPublicIdentity().address, + contents: {}, }, + delegationId: null, + legitimations: [], + claimOwner: { nonce: '0x12345678', hash: '0x12345678' }, + claimHashTree: {}, + cTypeHash: { nonce: '0x12345678', hash: '0x12345678' }, + rootHash: '0x12345678', + claimerSignature: '0x12345678', + } as IRequestForAttestation + + const requestAttestationBody: IRequestAttestationForClaim = { + content, type: MessageBodyType.REQUEST_ATTESTATION_FOR_CLAIM, } @@ -140,10 +144,10 @@ describe('Messaging', () => { content: { request: requestAttestationBody.content, attestation: { - delegationId: null, - claimHash: requestAttestationBody.content.hash, + claimHash: requestAttestationBody.content.rootHash, cTypeHash: '0x12345678', owner: identityBob.getPublicIdentity().address, + delegationId: null, revoked: false, }, }, diff --git a/src/messaging/Message.ts b/src/messaging/Message.ts index 3ae08daf0..8d3affee8 100644 --- a/src/messaging/Message.ts +++ b/src/messaging/Message.ts @@ -310,7 +310,7 @@ export interface IInformCreateDelegation extends IMessageBodyBase { } export interface IPartialClaim extends Partial { - cType: Claim['cType'] + cTypeHash: Claim['cTypeHash'] } export type MessageBody = diff --git a/src/requestforattestation/RequestForAttestation.spec.ts b/src/requestforattestation/RequestForAttestation.spec.ts index 6a739ad20..862b80dac 100644 --- a/src/requestforattestation/RequestForAttestation.spec.ts +++ b/src/requestforattestation/RequestForAttestation.spec.ts @@ -2,6 +2,8 @@ import Identity from '../identity/Identity' import RequestForAttestation from './RequestForAttestation' import AttestedClaim from '../attestedclaim/AttestedClaim' import Attestation from '../attestation/Attestation' +import CType from '../ctype/CType' +import ICType from '../types/CType' import IClaim from '../types/Claim' function buildRequestForAttestation( @@ -11,13 +13,39 @@ function buildRequestForAttestation( legitimations: AttestedClaim[] ): RequestForAttestation { // create claim - const claim = { - cType: ctype, - contents, + const contentsCopy = contents + + const identityAlice = Identity.buildFromURI('//Alice') + + const rawCType: ICType['schema'] = { + $id: 'http://example.com/ctype-1', + $schema: 'http://kilt-protocol.org/draft-01/ctype#', + properties: { + name: { type: 'string' }, + }, + type: 'object', + } + + const fromRawCType: ICType = { + schema: rawCType, + owner: identityAlice.address, + hash: '', + } + + const testCType: CType = CType.fromCType(fromRawCType) + + const claim: IClaim = { + cTypeHash: testCType.hash, + contents: contentsCopy, owner: claimer.address, - } as IClaim + } // build request for attestation with legimitations - return new RequestForAttestation(claim, legitimations, claimer) + return RequestForAttestation.fromClaimAndIdentity( + claim, + claimer, + legitimations, + null + ) } describe('RequestForAttestation', () => { @@ -32,12 +60,13 @@ describe('RequestForAttestation', () => { [] ) // build attestation - const legitimationAttestation: Attestation = new Attestation( + const legitimationAttestation: Attestation = Attestation.fromRequestAndPublicIdentity( legitimationRequest, - identityCharlie + identityCharlie, + null ) // combine to attested claim - const legitimation: AttestedClaim = new AttestedClaim( + const legitimation: AttestedClaim = AttestedClaim.fromRequestAndAttestation( legitimationRequest, legitimationAttestation ) diff --git a/src/requestforattestation/RequestForAttestation.ts b/src/requestforattestation/RequestForAttestation.ts index d1797b6e2..aadac6e9c 100644 --- a/src/requestforattestation/RequestForAttestation.ts +++ b/src/requestforattestation/RequestForAttestation.ts @@ -2,7 +2,9 @@ * Requests for attestation are a core building block of the KILT SDK. * A RequestForAttestation represents a [[Claim]] which needs to be validated. In practice, the RequestForAttestation is sent from a claimer to an attester. * - * A RequestForAttestation object contains the [[Claim]] and its hash, and legitimations/delegationId of the attester. It's signed by the claimer, to make it tamper proof (`claimerSignature` is a property of [[Claim]]). A RequestForAttestation also supports hiding of claim data during a credential presentation. + * A RequestForAttestation object contains the [[Claim]] and its hash, and legitimations/delegationId of the attester. + * It's signed by the claimer, to make it tamperproof (`claimerSignature` is a property of [[Claim]]). + * A RequestForAttestation also supports hiding of claim data during a credential presentation. * * @module RequestForAttestation */ @@ -11,7 +13,6 @@ * Dummy comment needed for correct doc display, do not remove. */ import { v4 as uuid } from 'uuid' -import { IDelegationBaseNode } from '..' import { verify, hash, @@ -28,6 +29,7 @@ import IRequestForAttestation, { Hash, NonceHash, } from '../types/RequestForAttestation' +import { IDelegationBaseNode } from '../types/Delegation' function hashNonceValue(nonce: string, value: any): string { return hashObjectAsStr(value, nonce) @@ -52,7 +54,7 @@ function generateHashTree(contents: object): object { } function verifyClaimerSignature(rfa: RequestForAttestation): boolean { - return verify(rfa.hash, rfa.claimerSignature, rfa.claim.owner) + return verify(rfa.rootHash, rfa.claimerSignature, rfa.claim.owner) } function getHashRoot(leaves: Uint8Array[]): Uint8Array { @@ -65,65 +67,137 @@ export default class RequestForAttestation implements IRequestForAttestation { * [STATIC] Builds an instance of [[RequestForAttestation]], from a simple object with the same properties. * Used for deserialization. * - * @param obj - An object built from simple [[Claim]], [[Identity]] and legitimation objects. + * @param requestForAttestationInput - An object built from simple [[Claim]], [[Identity]] and legitimation objects. * @returns A new [[RequestForAttestation]] `object`. * @example ```javascript - * const serialized = - * '{ "claim": { "cType": "0x981...", "contents": { "name": "Alice", "age": 29 }, owner: "5Gf..." }, ... }, ... }'; - * const parsed = JSON.parse(serialized); + * const serializedRequest = '{ "claim": { "cType": "0x981...", "contents": { "name": "Alice", "age": 29 }, owner: "5Gf..." }, ... }, ... }'; + * const parsedRequest = JSON.parse(serializedRequest); + * RequestForAttestation.fromRequest(parsedRequest); + * ``` + */ + public static fromRequest( + requestForAttestationInput: IRequestForAttestation + ): RequestForAttestation { + return new RequestForAttestation(requestForAttestationInput) + } + + /** + * [STATIC] Builds a new instance of [[RequestForAttestation]], from a complete set of requiered parameters. * - * RequestForAttestation.fromObject(parsed); + * @param claimInput - An `IClaim` object the request for attestation is built for. + * @param identity - The Claimer's [Identity]. + * @param [legitimationsInput] - Array of [AttestedClaim] objects of the Attester which the Claimer requests to include into the attestation as legitimations. + * @param [delegationInput] - The id of the DelegationNode of the Attester, which should be used in the attestation + * @returns A new [[RequestForAttestation]] object. + * @example ```javascript + * const requestForAttestation = RequestForAttestation.fromClaimAndIdentity(claim,alice,[],null) * ``` */ - public static fromObject(obj: IRequestForAttestation): RequestForAttestation { - const newClaim = Object.create(RequestForAttestation.prototype) - const object = Object.assign(newClaim, obj) - object.legitimations = object.legitimations.map( - (legitimation: AttestedClaim) => AttestedClaim.fromObject(legitimation) + public static fromClaimAndIdentity( + claimInput: IClaim, + identity: Identity, + legitimationsInput: AttestedClaim[], + delegationIdInput: IDelegationBaseNode['id'] | null + ): RequestForAttestation { + if (claimInput.owner !== identity.address) { + throw Error('Claim owner is not Identity') + } + const claimOwnerGenerated = generateHash(claimInput.owner) + const cTypeHashGenerated = generateHash(claimInput.cTypeHash) + const claimHashTreeGenerated = generateHashTree(claimInput.contents) + const calculatedRootHash = RequestForAttestation.calculateRootHash( + claimOwnerGenerated, + cTypeHashGenerated, + claimHashTreeGenerated, + legitimationsInput, + delegationIdInput ) - return object + let legitimations: AttestedClaim[] = [] + if (Array.isArray(legitimationsInput)) { + legitimations = legitimationsInput + } + return new RequestForAttestation({ + claim: claimInput, + legitimations, + claimOwner: claimOwnerGenerated, + claimHashTree: claimHashTreeGenerated, + cTypeHash: cTypeHashGenerated, + rootHash: calculatedRootHash, + claimerSignature: RequestForAttestation.sign( + identity, + calculatedRootHash + ), + delegationId: delegationIdInput, + }) } public claim: IClaim + public legitimations: AttestedClaim[] public claimOwner: NonceHash public claimerSignature: string public claimHashTree: object - public ctypeHash: NonceHash - public hash: Hash - public legitimations: AttestedClaim[] + public cTypeHash: NonceHash + public rootHash: Hash public delegationId: IDelegationBaseNode['id'] | null /** * Builds a new [[RequestForAttestation]] instance. * - * @param claim - A claim, usually sent by a claimer. - * @param legitimations - Attested claims used as legitimations. - * @param identity - Identity of the claimer. - * @param delegationId - A delegation tree's root node id. + * @param requestInput - The base object from which to create the requestForAttestation. * @example ```javascript * // create a new request for attestation - * new RequestForAttestation(claim, [], alice); + * new RequestForAttestation(requestForAttestationInput); * ``` */ - public constructor( - claim: IClaim, - legitimations: AttestedClaim[], - identity: Identity, - delegationId: IDelegationBaseNode['id'] | null = null - ) { - if (claim.owner !== identity.address) { - throw Error('Claim owner is not identity') + public constructor(requestForAttestationInput: IRequestForAttestation) { + if ( + !requestForAttestationInput.claim || + !requestForAttestationInput.legitimations || + !requestForAttestationInput.claimOwner || + !requestForAttestationInput.claimerSignature || + !requestForAttestationInput.claimHashTree || + !requestForAttestationInput.cTypeHash || + !requestForAttestationInput.rootHash + ) { + throw new Error( + `Property Not Provided while building RequestForAttestation:\n + requestInput.claim:\n + ${requestForAttestationInput.claim}\n + requestInput.legitimations:\n + ${requestForAttestationInput.legitimations}\n + requestInput.claimOwner:\n + ${requestForAttestationInput.claimOwner}\n + requestInput.claimerSignature:\n + ${requestForAttestationInput.claimerSignature} + requestInput.claimHashTree:\n + ${requestForAttestationInput.claimHashTree}\n + requestInput.rootHash:\n + ${requestForAttestationInput.rootHash}\n + requestInput.cTypeHash:\n + ${requestForAttestationInput.cTypeHash}\n` + ) } - this.claim = claim - this.claimOwner = generateHash(this.claim.owner) - this.ctypeHash = generateHash(this.claim.cType) - this.legitimations = legitimations - this.delegationId = delegationId - - this.claimHashTree = generateHashTree(claim.contents) - this.hash = this.calculateRootHash() - this.claimerSignature = this.sign(identity) + this.claim = requestForAttestationInput.claim + this.claimOwner = requestForAttestationInput.claimOwner + this.cTypeHash = requestForAttestationInput.cTypeHash + if ( + typeof requestForAttestationInput.legitimations !== 'undefined' && + Array.isArray(requestForAttestationInput.legitimations) && + requestForAttestationInput.legitimations.length + ) { + this.legitimations = requestForAttestationInput.legitimations.map( + legitimation => AttestedClaim.fromAttestedClaim(legitimation) + ) + } else { + this.legitimations = [] + } + this.delegationId = requestForAttestationInput.delegationId + this.claimHashTree = requestForAttestationInput.claimHashTree + this.rootHash = requestForAttestationInput.rootHash + this.claimerSignature = requestForAttestationInput.claimerSignature + this.verifySignature() + this.verifyData() } /** @@ -136,8 +210,8 @@ export default class RequestForAttestation implements IRequestForAttestation { * name: 'Alice', * age: 29, * }; - * const claim = new Claim(ctype, rawClaim, alice); - * const reqForAtt = new RequestForAttestation(claim, [], alice); + * const claim = Claim.fromCTypeAndClaimContents(ctype, rawClaim, alice); + * const reqForAtt = RequestForAttestation.fromClaimAndIdentity(claim,alice,[],null); * reqForAtt.removeClaimProperties(['name']); * // reqForAtt does not contain `name` in its claimHashTree and its claim contents anymore. * ``` @@ -156,7 +230,7 @@ export default class RequestForAttestation implements IRequestForAttestation { * Removes the [[Claim]] owner from the [[RequestForAttestation]] object. * * @example ```javascript - * const reqForAtt = new Kilt.RequestForAttestation(claim, [], alice); + * const reqForAtt = RequestForAttestation.fromClaimAndIdentity(claim,alice,[],null); * reqForAtt.removeClaimOwner(); * // `requestForAttestation` does not contain the claim `owner` or the `claimOwner`'s nonce anymore. * ``` @@ -171,13 +245,22 @@ export default class RequestForAttestation implements IRequestForAttestation { * * @returns Whether the data is valid. * @example ```javascript - * const reqForAtt = new RequestForAttestation(claim, [], alice); + * const reqForAtt = RequestForAttestation.fromClaimAndIdentity(claim,alice,[],null); * reqForAtt.verifyData(); // returns true if the data is correct * ``` */ public verifyData(): boolean { // check claim hash - if (this.hash !== this.calculateRootHash()) { + if ( + this.rootHash !== + RequestForAttestation.calculateRootHash( + this.claimOwner, + this.cTypeHash, + this.claimHashTree, + this.legitimations, + this.delegationId + ) + ) { return false } // check claim owner hash @@ -192,8 +275,8 @@ export default class RequestForAttestation implements IRequestForAttestation { // check cType hash if ( - this.ctypeHash.hash !== - hashNonceValue(this.ctypeHash.nonce, this.claim.cType) + this.cTypeHash.hash !== + hashNonceValue(this.cTypeHash.nonce, this.claim.cTypeHash) ) { throw Error('Invalid hash for CTYPE') } @@ -230,7 +313,7 @@ export default class RequestForAttestation implements IRequestForAttestation { * * @returns Whether the signature is correct. * @example ```javascript - * const reqForAtt = new RequestForAttestation(claim, [], alice); + * const reqForAtt = RequestForAttestation.fromClaimAndIdentity(claim,alice,[],null); * reqForAtt.verifySignature(); // returns `true` if the signature is correct * ``` */ @@ -238,33 +321,51 @@ export default class RequestForAttestation implements IRequestForAttestation { return verifyClaimerSignature(this) } - private getHashLeaves(): Uint8Array[] { + private static sign(identity: Identity, rootHash: Hash): string { + return identity.signStr(rootHash) + } + + private static getHashLeaves( + claimOwner: NonceHash, + cTypeHash: NonceHash, + claimHashTree: object, + legitimations: AttestedClaim[], + delegationId: IDelegationBaseNode['id'] | null + ): Uint8Array[] { const result: Uint8Array[] = [] - result.push(coToUInt8(this.claimOwner.hash)) - result.push(coToUInt8(this.ctypeHash.hash)) - Object.keys(this.claimHashTree).forEach(key => { - result.push(coToUInt8(this.claimHashTree[key].hash)) + result.push(coToUInt8(claimOwner.hash)) + result.push(coToUInt8(cTypeHash.hash)) + Object.keys(claimHashTree).forEach(key => { + result.push(coToUInt8(claimHashTree[key].hash)) }) - if (this.legitimations) { - this.legitimations.forEach(legitimation => { + if (legitimations) { + legitimations.forEach(legitimation => { result.push(coToUInt8(legitimation.getHash())) }) } - if (this.delegationId) { - result.push(coToUInt8(this.delegationId)) + if (delegationId) { + result.push(coToUInt8(delegationId)) } return result } - private calculateRootHash(): Hash { - const hashes: Uint8Array[] = this.getHashLeaves() + private static calculateRootHash( + claimOwner: NonceHash, + cTypeHash: NonceHash, + claimHashTree: object, + legitimations: AttestedClaim[], + delegationId: IDelegationBaseNode['id'] | null + ): Hash { + const hashes: Uint8Array[] = RequestForAttestation.getHashLeaves( + claimOwner, + cTypeHash, + claimHashTree, + legitimations, + delegationId + ) const root: Uint8Array = hashes.length === 1 ? hashes[0] : getHashRoot(hashes) return u8aToHex(root) } - - private sign(identity: Identity): string { - return identity.signStr(this.hash) - } } diff --git a/src/types/CType.ts b/src/types/CType.ts index 1fa183272..fa55fae37 100644 --- a/src/types/CType.ts +++ b/src/types/CType.ts @@ -13,19 +13,8 @@ export interface ICTypeSchema { type: 'object' } -export interface ICtypeMetadata { - title: { - default: string - } - description: { - default: string - } - properties: any -} - export default interface ICType { hash: string - owner?: IPublicIdentity['address'] + owner: IPublicIdentity['address'] | null schema: ICTypeSchema - metadata: ICtypeMetadata } diff --git a/src/types/CTypeMetadata.ts b/src/types/CTypeMetadata.ts new file mode 100644 index 000000000..f4809139e --- /dev/null +++ b/src/types/CTypeMetadata.ts @@ -0,0 +1,20 @@ +/** + * @module TypeInterfaces/CType + */ +/** + * Dummy comment needed for correct doc display, do not remove. + */ +export default interface ICTypeMetadata { + metadata: IMetadata + ctypeHash: string | null +} + +export interface IMetadata { + title: { + default: string + } + description?: { + default: string + } + properties: object +} diff --git a/src/types/Claim.ts b/src/types/Claim.ts index 7eab0e773..c0ad4b5a0 100644 --- a/src/types/Claim.ts +++ b/src/types/Claim.ts @@ -8,7 +8,7 @@ import ICType from './CType' import IPublicIdentity from './PublicIdentity' export default interface IClaim { - cType: ICType['hash'] + cTypeHash: ICType['hash'] contents: object owner: IPublicIdentity['address'] } diff --git a/src/types/RequestForAttestation.ts b/src/types/RequestForAttestation.ts index 3491d8f5d..9ef5cfaac 100644 --- a/src/types/RequestForAttestation.ts +++ b/src/types/RequestForAttestation.ts @@ -6,7 +6,7 @@ */ import IClaim from './Claim' import { IDelegationBaseNode } from './Delegation' -import IAttestedClaim from './AttestedClaim' +import AttestedClaim from '../attestedclaim/AttestedClaim' export type Hash = string @@ -17,11 +17,11 @@ export type NonceHash = { export default interface IRequestForAttestation { claim: IClaim - claimerSignature: string + legitimations: AttestedClaim[] + claimOwner: NonceHash claimHashTree: object - ctypeHash: NonceHash - hash: Hash - legitimations: IAttestedClaim[] - + cTypeHash: NonceHash + rootHash: Hash + claimerSignature: string delegationId: IDelegationBaseNode['id'] | null } diff --git a/yarn.lock b/yarn.lock index 56e11f690..a7be48ec6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -350,11 +350,6 @@ resolved "https://registry.yarnpkg.com/@types/jsonabc/-/jsonabc-2.3.1.tgz#3aa5d3f938331ef2472d3ea56c6b4c805bef6198" integrity sha512-SOR8DMQQVUmk3JnNp7FU75U+vp/VcFcghvS68mUQEuq4FKDANIQ9dxDA5qEQMDahBCd5Vyhk81ywn0JbcLpZZw== -"@types/lodash@^4.14.138": - version "4.14.138" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.138.tgz#34f52640d7358230308344e579c15b378d91989e" - integrity sha512-A4uJgHz4hakwNBdHNPdxOTkYmXNgmUAKLbXZ7PKGslgeV0Mb8P3BlbYfPovExek1qnod4pDfRbxuzcVs3dlFLg== - "@types/memoizee@^0.4.3": version "0.4.3" resolved "https://registry.yarnpkg.com/@types/memoizee/-/memoizee-0.4.3.tgz#f48270d19327c1709620132cf54d598650f98492"