From 250f0863893f6568598d4221f3dfdeb7749d266e Mon Sep 17 00:00:00 2001 From: Bill Barman Date: Tue, 20 Nov 2018 11:54:50 +0800 Subject: [PATCH] init new DDO lib objects --- src/libDDO/Authentication.ts | 29 +++ src/libDDO/DDO.ts | 235 +++++++++++++++++++++++++ src/libDDO/Proof.ts | 29 +++ src/libDDO/PublicKey.ts | 36 ++++ src/libDDO/Service.ts | 38 ++++ test/ddo_lib/DDO.test.ts | 112 ++++++++++++ test/testdata/ddoSample1.json | 134 ++++++++++++++ test/testdata/ddoSamplePrivateKey1.pem | 15 ++ 8 files changed, 628 insertions(+) create mode 100644 src/libDDO/Authentication.ts create mode 100644 src/libDDO/DDO.ts create mode 100644 src/libDDO/Proof.ts create mode 100644 src/libDDO/PublicKey.ts create mode 100644 src/libDDO/Service.ts create mode 100644 test/ddo_lib/DDO.test.ts create mode 100644 test/testdata/ddoSample1.json create mode 100644 test/testdata/ddoSamplePrivateKey1.pem diff --git a/src/libDDO/Authentication.ts b/src/libDDO/Authentication.ts new file mode 100644 index 0000000..32adb12 --- /dev/null +++ b/src/libDDO/Authentication.ts @@ -0,0 +1,29 @@ +// import PublicKey from "./PublicKey" + + +export default class Authentication { + + +// private publicKey?: PublicKey + public publicKeyId: string + public type: string + public value: string + + public constructor(data?: any) { + this.publicKeyId = data['publicKey'] + this.type = data['type'] + this.value = '' + } + + public toData(): object { + return { + 'publicKey': this.publicKeyId, + 'type': this.type + } + } + public isValid(): boolean { + return this.publicKeyId != '' && this.type != '' + } + +} + diff --git a/src/libDDO/DDO.ts b/src/libDDO/DDO.ts new file mode 100644 index 0000000..f56dfd3 --- /dev/null +++ b/src/libDDO/DDO.ts @@ -0,0 +1,235 @@ + +import Authentication from "./Authentication" +import PublicKey from "./PublicKey" +import Service from "./Service" +import Proof from "./Proof" + + +import * as Web3 from "web3" + +export default class DDO { + + public static CONTEXT: string = "https://w3id.org/future-method/v1" + +/* + public static serialize(ddo: DDO): string { + return JSON.stringify(ddo, null, 2) + } + + public static deserialize(ddoString: string): DDO { + const ddo = JSON.parse(ddoString) + + return ddo as DDO + } +*/ + + public context: string = DDO.CONTEXT + public did: string + public created: string + public publicKeys: PublicKey[] + public authentications: Authentication[] + public services: Service[] + public proof: Proof + + public constructor(did?: any) { + if (typeof did == 'string') { + this.did = did + } + else if (typeof did == 'object') { + this.readFromData(did) + } + } + + public readFromData(data: object) { + this.did = data['id'] + var date = new Date() + this.created = date.toISOString() + if (data.hasOwnProperty('created')) { + this.created = data['created'] + } + + this.context = DDO.CONTEXT + if ( data.hasOwnProperty('@context') ) { + this.context = data['@context'] + } + + this.publicKeys = [] + + if ( data.hasOwnProperty('publicKey') ) { + data['publicKey'].forEach(function(value) { + this.publicKeys.push(new PublicKey(value)) + }, this) + } + + this.authentications = [] + if ( data.hasOwnProperty('authentication') ) { + data['authentication'].forEach(function(value) { + this.authentications.push(new Authentication(value)) + }, this) + } + + this.services = [] + if ( data.hasOwnProperty('service') ) { + data['service'].forEach(function(value) { + this.services.push(new Service(value)) + }, this) + } + + if ( data.hasOwnProperty('proof') ) { + this.proof = new Proof(data['proof']) + } + } + + public toData(): object { + var data = { + '@context': this.context, + 'id': this.did, + 'created': this.created + } + if ( this.publicKeys.length > 0 ) { + data['publicKey'] = [] + this.publicKeys.forEach(function(publicKey) { + this.push(publicKey.toData()) + }, data['publicKey']) + } + + if ( this.authentications.length > 0 ) { + data['authentication'] = [] + this.authentications.forEach(function(authentication) { + this.push(authentication.toData()) + }, data['authentication'] ) + } + + if ( this.services.length > 0 ) { + data['service'] = [] + this.services.forEach(function(service) { + this.push(service.toData()) + }, data['service']) + } + + if ( this.isProofDefined() ) { + data['proof'] = this.proof.toData() + } + return data + } + + public toJSON(): string { + return JSON.stringify(this.toData(), null, 2) + } + + public isProofDefined(): boolean { + return this.proof != null + } + public validate(): boolean { + + if (this.context.length == 0 || this.did.length == 0 || this.created.length == 0) { + return false + } + + if ( this.publicKeys.length == 0 ) { + return false + } + if ( this.authentications.length == 0 ) { + return false + } + if ( this.services.length == 0 ) { + return false + } + + var result = { 'isValid': true } + this.publicKeys.forEach(function(publicKey) { + if ( !publicKey.isValid() ) { + this.isValid = false + } + }, result) + if ( ! result.isValid ) { + return false + } + + this.authentications.forEach(function(authentication) { + if ( !authentication.isValid() ) { + this.isValid = false + } + }, result) + if ( ! result.isValid ) { + return false + } + + this.services.forEach(function(service) { + if ( !service.isValid() ) { + this.isValid = false + } + }, result) + if ( ! result.isValid ) { + return false + } + + if ( this.isProofDefined() ) { + if ( !this.proof.isValid() ) { + return false + } + } + return true + } + // return a service based on the service type value + public getService(serviceType: string): Service { + var result = { 'service': null } + this.services.forEach(function(service) { + if (service.type == serviceType ) { + this.service = service + } + }, result) + return result.service + } + + public findServiceKeyValue(key: string, value: string): Service { + var result = { 'service': null } + this.services.forEach(function(service) { + if (service.values[key] == value) { + this.service = service + } + }, result) + return result.service + } + + // return a string list of fields used for hashing + public hashTextList(): string[] { + var values = [] + + if (this.created) { + values.push(this.created) + } + + this.publicKeys.forEach(function(publicKey) { + this.push(publicKey.type) + this.push(publicKey.value) + }, values) + + this.authentications.forEach(function(authentication) { + this.push(authentication.type) + this.push(authentication.value) + }, values) + + this.services.forEach(function(service) { + this.push(service.type) + this.push(service.endpoint) + }, values) + return values + } + + public calculateHash(): string { + var values = this.hashTextList() + return Web3.utils.sha3(values.join()) + } + + public isEmpty(): boolean { + return this.did && this.did.length == 0 + && this.publicKeys.length == 0 + && this.authentications.length == 0 + && this.services.length == 0 + } + + public isDIDAssigned(): boolean { + return this.did && this.did.length > 0 + } +} diff --git a/src/libDDO/Proof.ts b/src/libDDO/Proof.ts new file mode 100644 index 0000000..79a6ae8 --- /dev/null +++ b/src/libDDO/Proof.ts @@ -0,0 +1,29 @@ +export default class Proof { + + + public created: string + public creator: string + public type: string + public signatureValue: string + + public constructor(data?: any) { + this.created = data['created'] + this.creator = data['creator'] + this.type = data['type'] + this.signatureValue = data['signatureValue'] + } + + public toData(): object { + return { + 'created': this.created, + 'creator': this.creator, + 'type': this.type, + 'signatureValue': this.signatureValue + } + } + public isValid(): boolean { + return this.created != '' && this.creator != '' && this.type != '' + && this.signatureValue != '' + } +} + diff --git a/src/libDDO/PublicKey.ts b/src/libDDO/PublicKey.ts new file mode 100644 index 0000000..a6c8bd1 --- /dev/null +++ b/src/libDDO/PublicKey.ts @@ -0,0 +1,36 @@ +export default class PublicKey { + + public static TYPE_RSA: string = 'RsaSignatureAuthentication2018' + public static PEM: string = 'publicKeyPem' + public static JWK: string = 'publicKeyJwk' + public static HEX: string = 'publicKeyHex' + public static BASE64: string = 'publicKeyBase64' + public static BASE85: string = 'publicKeyBase85' + + public did: string + public owner: string + public type: string + public value: string + + public constructor(data?: any) { + this.did = data['id'] + this.owner = data['owner'] + this.type = data['type'] + this.value = data[PublicKey.PEM] + } + + public toData(): object { + return { + 'id': this.did, + 'owner': this.owner, + 'type': this.type, + [PublicKey.PEM]: this.value + } + } + + public isValid(): boolean { + return this.did != '' && this.owner != '' && this.type != '' && this.value != '' + } + +} + diff --git a/src/libDDO/Service.ts b/src/libDDO/Service.ts new file mode 100644 index 0000000..07cb3bb --- /dev/null +++ b/src/libDDO/Service.ts @@ -0,0 +1,38 @@ +export default class Service { + + + public did: string + public endpoint: string + public type: string + public values: object + + public constructor(data?: any) { + this.did = data['id'] + this.endpoint = data['serviceEndpoint'] + this.type = data['type'] + this.values = Object.assign({}, data) + delete this.values['id'] + delete this.values['serviceEndpoint'] + delete this.values['type'] + } + + public toData(): object { + var data = { + 'id': this.did, + 'serviceEndpoint': this.endpoint, + 'type': this.type + } + if (Object.keys(this.values).length > 0) { + data = Object.assign(data, this.values) + } + return data + } + + public isValid(): boolean { + return this.did && this.did.length > 0 + && this.endpoint && this.endpoint.length > 0 + && this.type && this.type.length > 0 + } + +} + diff --git a/test/ddo_lib/DDO.test.ts b/test/ddo_lib/DDO.test.ts new file mode 100644 index 0000000..70bd827 --- /dev/null +++ b/test/ddo_lib/DDO.test.ts @@ -0,0 +1,112 @@ +import {assert} from "chai" +import DDO from "../../src/libDDO/DDO" + +import * as jsonDDO from "../testdata/ddoSample1.json" + + +describe("DDO", () => { + + describe("#constructor()", () => { + + it("should create an empty ddo", async () => { + + const ddo = new DDO() + assert(ddo) + assert(ddo.did == null) + + }) + }) + describe('JSON serialization unserialization', () => { + it("should create ddo with the sample JSON", async () => { + + assert(jsonDDO) + var ddo = new DDO(jsonDDO) + assert(ddo) + + assert(ddo.validate()) + + var jsonText = ddo.toJSON() + assert(jsonText) + }) + + }) + describe('validation', () => { + it("should test ddo core validation", async () => { + + // core ddo values + assert(jsonDDO) + var ddo = new DDO(jsonDDO) + assert(ddo) + + assert(ddo.validate()) + ddo.did = '' + assert(!ddo.validate()) + }) + it("should test ddo public key validation", async () => { + + // public key + var ddo = new DDO(jsonDDO) + assert(ddo) + assert(ddo.validate()) + + ddo.publicKeys[0].did = '' + assert(!ddo.validate()) + + }) + it("should test ddo authentication validation", async () => { + + // authentication + var ddo = new DDO(jsonDDO) + assert(ddo) + assert(ddo.validate()) + + ddo.authentications[0].type = '' + assert(!ddo.validate()) + }) + it("should test ddo service validation", async () => { + // service + var ddo = new DDO(jsonDDO) + assert(ddo) + assert(ddo.validate()) + + ddo.services[0].endpoint = '' + assert(!ddo.validate()) + }) + it("should test ddo proof validation", async () => { + // proof + var ddo = new DDO(jsonDDO) + assert(ddo) + assert(ddo.validate()) + + ddo.proof.signatureValue = '' + assert(!ddo.validate()) + }) + }) + describe('DDO access data', () => { + it("should find a service in the ddo", async () => { + var ddo = new DDO(jsonDDO) + assert(ddo) + assert(ddo.validate()) + + var service = ddo.getService('Metadata') + assert(service) + + var service = ddo.getService('MetadataCannotFind') + assert(service == null) +// var item = ddo.findServiceKeyValue('serviceDefinitionId', 'test') + }) + }) + describe('DDO hashing', () => { + it("should hash a valid ddo", async () => { + var ddo = new DDO(jsonDDO) + assert(ddo) + assert(ddo.validate()) + + var hash = ddo.calculateHash() + assert(hash) + console.log(hash) + + }) + }) + +}) diff --git a/test/testdata/ddoSample1.json b/test/testdata/ddoSample1.json new file mode 100644 index 0000000..253b3d2 --- /dev/null +++ b/test/testdata/ddoSample1.json @@ -0,0 +1,134 @@ +{ + "@context": "https://w3id.org/did/v1", + "id": "did:op:da1a0831df3f6f4a4790aa68297b1710eb7568f0b763b642779c3ce39a92952a", + "created": "2018-11-16 18:00:09.968273", + "publicKey": [ + { + "id": "did:op:da1a0831df3f6f4a4790aa68297b1710eb7568f0b763b642779c3ce39a92952a#keys=1", + "type": "RsaSignatureAuthentication2018", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCz+BpLriIZU/4UwmlOYNgf0YOA\noOah0C/mKMSDBYj6XAq2JM3oAWWN05+iW85Wi9Owa19BAOEIl90gMMdZpWOrwhCy\ndY1WdhmvpOFL+8YFYUhRlaMexBZxSUdA4NLXRciKWzz0gJSQS7a9JDAd6xFupzv6\nGmrYL3WK+lnaEqHiuwIDAQAB\n-----END PUBLIC KEY-----", + "owner": "did:op:da1a0831df3f6f4a4790aa68297b1710eb7568f0b763b642779c3ce39a92952a#keys=1" + } + ], + "authentication": [ + { + "type": "RsaVerificationKey2018", + "publicKey": "did:op:da1a0831df3f6f4a4790aa68297b1710eb7568f0b763b642779c3ce39a92952a#keys=1" + } + ], + "service": [ + { + "id": "did:op:da1a0831df3f6f4a4790aa68297b1710eb7568f0b763b642779c3ce39a92952a", + "type": "Metadata", + "serviceEndpoint": "http://myaquarius.org/api/v1/provider/assets/metadata/{did}", + "metadata": { + "base": { + "name": "UK Weather information 2011", + "type": "dataset", + "description": "Weather information of UK including temperature and humidity", + "size": "3.1gb", + "dateCreated": "2012-10-10T17:00:000Z", + "author": "Met Office", + "license": "CC-BY", + "copyrightHolder": "Met Office", + "encoding": "UTF-8", + "compression": "zip", + "contentType": "text/csv", + "workExample": "423432fsd,51.509865,-0.118092,2011-01-01T10:55:11+00:00,7.2,68", + "contentUrls": [ + "https://testocnfiles.blob.core.windows.net/testfiles/testzkp.zip" + ], + "links": [ + { + "name": "Sample of Asset Data", + "type": "sample", + "url": "https://foo.com/sample.csv" + }, + { + "name": "Data Format Definition", + "type": "format", + "AssetID": "4d517500da0acb0d65a716f61330969334630363ce4a6a9d39691026ac7908ea" + } + ], + "inLanguage": "en", + "tags": "weather, uk, 2011, temperature, humidity", + "price": 10 + }, + "curation": { + "rating": 0.93, + "numVotes": 123, + "schema": "Binary Voting" + }, + "additionalInformation": { + "updateFrequency": "yearly", + "structuredMarkup": [ + { + "uri": "http://skos.um.es/unescothes/C01194/jsonld", + "mediaType": "application/ld+json" + }, + { + "uri": "http://skos.um.es/unescothes/C01194/turtle", + "mediaType": "text/turtle" + } + ] + } + } + }, + { + "id": "did:op:da1a0831df3f6f4a4790aa68297b1710eb7568f0b763b642779c3ce39a92952a", + "type": "OpenIdConnectVersion1.0Service", + "serviceEndpoint": "https://openid.example.com/" + }, + { + "id": "did:op:da1a0831df3f6f4a4790aa68297b1710eb7568f0b763b642779c3ce39a92952a", + "type": "CredentialRepositoryService", + "serviceEndpoint": "https://repository.example.com/service/8377464" + }, + { + "id": "did:op:da1a0831df3f6f4a4790aa68297b1710eb7568f0b763b642779c3ce39a92952a", + "type": "XdiService", + "serviceEndpoint": "https://xdi.example.com/8377464" + }, + { + "id": "did:op:da1a0831df3f6f4a4790aa68297b1710eb7568f0b763b642779c3ce39a92952a", + "type": "HubService", + "serviceEndpoint": "https://hub.example.com/.identity/did:op:0123456789abcdef/" + }, + { + "id": "did:op:da1a0831df3f6f4a4790aa68297b1710eb7568f0b763b642779c3ce39a92952a", + "type": "MessagingService", + "serviceEndpoint": "https://example.com/messages/8377464" + }, + { + "id": "did:op:da1a0831df3f6f4a4790aa68297b1710eb7568f0b763b642779c3ce39a92952a", + "type": "SocialWebInboxService", + "serviceEndpoint": "https://social.example.com/83hfh37dj", + "description": "My public social inbox", + "spamCost": { + "amount": "0.50", + "currency": "USD" + } + }, + { + "id": "did:op:da1a0831df3f6f4a4790aa68297b1710eb7568f0b763b642779c3ce39a92952a", + "type": "BopsService", + "serviceEndpoint": "https://bops.example.com/enterprise/" + }, + { + "id": "did:op:da1a0831df3f6f4a4790aa68297b1710eb7568f0b763b642779c3ce39a92952a", + "type": "Consume", + "serviceEndpoint": "http://mybrizo.org/api/v1/brizo/services/consume?pubKey=${pubKey}&serviceId={serviceId}&url={url}" + }, + { + "id": "did:op:da1a0831df3f6f4a4790aa68297b1710eb7568f0b763b642779c3ce39a92952a", + "type": "Compute", + "serviceEndpoint": "http://mybrizo.org/api/v1/brizo/services/compute?pubKey=${pubKey}&serviceId={serviceId}&algo={algo}&container={container}" + } + ], + "proof": { + "type": "RsaSignatureAuthentication2018", + "created": "2018-11-16 18:00:10.187507", + "creator": "did:op:da1a0831df3f6f4a4790aa68297b1710eb7568f0b763b642779c3ce39a92952a#keys=1", + "signatureValue": "lRwJvK0VouYxBNNQySxG2SrVUYWCss8WV6gzTP3tm+MkuSDUeUpsDWHTASzR5IityL+6pBsgwUPIpAUI/nh0YQXixMZWKF9aMVImgg0G85eP44zQC2u9iOFvpP4H0IdAG1kCSRkd7LmXVEzSoEx2ejdpk2Vbg/dNUnV0YOebxZ8=" + } +} \ No newline at end of file diff --git a/test/testdata/ddoSamplePrivateKey1.pem b/test/testdata/ddoSamplePrivateKey1.pem new file mode 100644 index 0000000..e09f1fb --- /dev/null +++ b/test/testdata/ddoSamplePrivateKey1.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCz+BpLriIZU/4UwmlOYNgf0YOAoOah0C/mKMSDBYj6XAq2JM3o +AWWN05+iW85Wi9Owa19BAOEIl90gMMdZpWOrwhCydY1WdhmvpOFL+8YFYUhRlaMe +xBZxSUdA4NLXRciKWzz0gJSQS7a9JDAd6xFupzv6GmrYL3WK+lnaEqHiuwIDAQAB +AoGAQfNzK3W6QD7j9xaRgawCt5JPVwVfzz+cNgONlBgkrN6q/Dm7jUBpx1Ich2KO +WG/wWQ/X/dnFHaGNYr0NaOAviEWtv3SRY3yjPe7rONlPJ9IN1HJ7iy7JI2SyrFYY +RFkrMYwNwOB58THKQBNOUKdNocZXBdlumky3j7vahtekdKkCQQDImygpTHhEqmt2 +xUevXfsnqkmSEQSEce9RodLxKgg/ULr2c9xavMnca72pg46+E7xMRqCojXGfZwmE +aXfqfh7DAkEA5aofDmTB4bHseovAHZyjFqG65qWN1x0cYVqqGvOCi5IauzPxvATV +7lIlgB3n4anrNmeDo+fWx+RKODacQlDcqQJBAJIsTKVbLT+Llmai9csZBgsvEBC5 +Cbugcavf6J8F66CHKNSwM96CNezBLSA51mc2ZjyGMkbfWe223L55Q4HAiJUCQHgA +wGGA/F213l7aDvRqGD2HHGXQM6EnMOEdwqx6eMf8+8K9jZ402KPCgJ3FApjDdIfk +5sAKMAwamT1uK4/rOKECQHo5HRTsHUmigl+mAibCrOQVYl3/cBCzgFMM4xtqt8HT +hqIqoWkW8GM5cLqhREku6LwTZ0o4sAXXZhLWiryyuao= +-----END RSA PRIVATE KEY----- \ No newline at end of file