From c213a8d3ca69fb0e35fdb5bb29d6600b3842a381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Guti=C3=A9rrez?= Date: Mon, 11 Mar 2019 22:52:53 +0100 Subject: [PATCH] Create Signature integration test and fixed/improved the generation of hashes. --- integration/ocean/Signature.test.ts | 99 +++++++++++++++++++ src/ddo/DDO.ts | 16 ++- src/ocean/OceanAgreements.ts | 40 +++++++- .../ServiceAgreements/ServiceAgreement.ts | 45 +++++---- 4 files changed, 166 insertions(+), 34 deletions(-) create mode 100644 integration/ocean/Signature.test.ts diff --git a/integration/ocean/Signature.test.ts b/integration/ocean/Signature.test.ts new file mode 100644 index 0000000..d64da85 --- /dev/null +++ b/integration/ocean/Signature.test.ts @@ -0,0 +1,99 @@ +import { assert } from "chai" + +import { config } from "../config" + +import { Ocean, Account, DDO, Keeper } from "../../src" // @oceanprotocol/squid + +import ServiceAgreement from "../../src/ocean/ServiceAgreements/ServiceAgreement" + +// WARN: not integration test. It has been done here because constant values +// depends on the first account on spree (only accessible from integration test) +describe("Signature", () => { + + let consumer: Account + + before(async () => { + await Ocean.getInstance(config) + + // Accounts + consumer = new Account("0x00bd138abd70e2f00903268f3db08f2d25677c9e") + consumer.setPassword("node0") + }) + + it("should generate the correct signature", async () => { + const templateId = `0x${"f".repeat(40)}` + const agreementId = `0x${"e".repeat(64)}` + + const accessId = `0x${"a".repeat(64)}` + const lockId = `0x${"b".repeat(64)}` + const escrowId = `0x${"c".repeat(64)}` + + const hash = await ServiceAgreement.hashServiceAgreement( + templateId, + agreementId, + [accessId, lockId, escrowId], + [0, 0, 0], + [0, 0, 0], + ) + + assert.equal(hash, "0x67901517c18a3d23e05806fff7f04235cc8ae3b1f82345b8bfb3e4b02b5800c7", "The signatuere is not correct.") + }) + + it("should generate the correct signature", async () => { + const templates = (await Keeper.getInstance()).templates + + const did = `did:op:${"c".repeat(64)}` + const templateId = `0x${"f".repeat(40)}` + const agreementId = `0x${"e".repeat(64)}` + const ddoOwner = `0x${"9".repeat(40)}` + const serviceDefinitionId = "0" + const amount = "10" + + const accessId = `0x${"a".repeat(64)}` + const lockId = `0x${"b".repeat(64)}` + + const serviceAgreementTemplate = await templates.escrowAccessSecretStoreTemplate.getServiceAgreementTemplate() + + const ddo = new DDO({ + id: did, + service: [ + { + type: "Access", + purchaseEndpoint: undefined, + serviceEndpoint: undefined, + serviceDefinitionId, + templateId, + serviceAgreementTemplate, + } as any, + ], + }) + + + const valuesMap = { + rewardAddress: ddoOwner, + amount, + documentId: ddo.shortId(), + grantee: consumer.getId(), + receiver: consumer.getId(), + sender: ddoOwner, + + lockCondition: lockId, + releaseCondition: accessId, + } + + const signature = await ServiceAgreement.signServiceAgreement( + ddo, + serviceDefinitionId, + agreementId, + valuesMap, + consumer, + ) + + assert.equal( + signature, + // tslint:disable-next-line + "0x6bd49301a4a98d4e2ca149d649cc22fa0c5bd69269716d91c5cc17576ec3caef12a0edf611bb318e684683eec77b202bbbe484ceb698aec0e1250b7d1cf874dd1c", + "The signatuere is not correct.", + ) + }) +}) diff --git a/src/ddo/DDO.ts b/src/ddo/DDO.ts index c2ef1aa..d5a3a17 100644 --- a/src/ddo/DDO.ts +++ b/src/ddo/DDO.ts @@ -38,19 +38,17 @@ export class DDO { * DID, descentralized ID. * @type {string} */ - public id: string + public id: string = null public created: string - public publicKey: PublicKey[] - public authentication: Authentication[] - public service: Service[] + public publicKey: PublicKey[] = [] + public authentication: Authentication[] = [] + public service: Service[] = [] public proof: Proof public constructor(ddo?: Partial) { - this.created = (ddo && ddo.created) || new Date().toISOString().replace(/\.[0-9]{3}/, "") - this.authentication = (ddo && ddo.authentication) || [] - this.id = (ddo && ddo.id) || null - this.publicKey = (ddo && ddo.publicKey) || [] - this.service = (ddo && ddo.service) || [] + Object.assign(this, ddo, { + created: ddo.created || new Date().toISOString().replace(/\.[0-9]{3}/, ""), + }) } public shortId(): string { diff --git a/src/ocean/OceanAgreements.ts b/src/ocean/OceanAgreements.ts index 7c8b811..9b927d5 100644 --- a/src/ocean/OceanAgreements.ts +++ b/src/ocean/OceanAgreements.ts @@ -54,16 +54,46 @@ export default class OceanAgreements { serviceDefinitionId: string, consumer: Account, ): Promise { - const d: DID = DID.parse(did as string) const ddo = await AquariusProvider.getAquarius().retrieveDDO(d) const agreementId: string = generateId() - const signature = await ServiceAgreement.signServiceAgreement(ddo, serviceDefinitionId, agreementId, consumer) + const valuesMap = await this.getValuesMapForPrepare(ddo, agreementId, consumer.getId()) + + const signature = await ServiceAgreement.signServiceAgreement(ddo, serviceDefinitionId, agreementId, valuesMap, consumer) return {agreementId, signature} } + // TODO: this map depends on the template, this generation should be reponsability of the template + private async getValuesMapForPrepare(ddo: DDO, agreementId: string, consumer: string): Promise<{[value: string]: string}> { + const keeper = await Keeper.getInstance() + const ddoOwner = ddo.proof && ddo.proof.creator + const amount = ddo.findServiceByType("Metadata").metadata.base.price + + let lockCondition + let releaseCondition + + try { + lockCondition = await keeper.conditions.lockRewardCondition.hashValues(ddoOwner, amount) + } catch(e) { } + + try { + releaseCondition = await keeper.conditions.accessSecretStoreCondition.hashValues(ddo.shortId(), consumer) + } catch(e) { } + + return { + rewardAddress: ddoOwner, + amount: amount.toString(), + documentId: ddo.shortId(), + grantee: consumer, + receiver: consumer, + sender: ddoOwner, + lockCondition, + releaseCondition, + } + } + /** * Submit a service agreement to the publisher to create the agreement on-chain. * @param {string} did Decentralized ID. @@ -82,10 +112,10 @@ export default class OceanAgreements { const result = await BrizoProvider .getBrizo() .initializeServiceAgreement( - did, - agreementId, + didPrefixed(did), + zeroX(agreementId), serviceDefinitionId, - signature, + zeroX(signature), consumer.getId(), ) diff --git a/src/ocean/ServiceAgreements/ServiceAgreement.ts b/src/ocean/ServiceAgreements/ServiceAgreement.ts index baed142..a9027e6 100644 --- a/src/ocean/ServiceAgreements/ServiceAgreement.ts +++ b/src/ocean/ServiceAgreements/ServiceAgreement.ts @@ -1,13 +1,11 @@ import { ServiceAgreementTemplateCondition } from "../../ddo/ServiceAgreementTemplate" import { DDO } from "../../ddo/DDO" import { ServiceAccess } from "../../ddo/Service" -import Keeper from "../../keeper/Keeper" import Web3Provider from "../../keeper/Web3Provider" import ValuePair from "../../models/ValuePair" import Logger from "../../utils/Logger" import Account from "../Account" -import DID from "../DID" -import { signText } from "../../utils" +import { signText, zeroX } from "../../utils" // TODO: move this class to helpers, it only contains pure functions @@ -24,12 +22,18 @@ export default class ServiceAgreement { const service = ddo.findServiceById<"Access">(serviceDefinitionId) const values: ValuePair[][] = ServiceAgreement.getValuesFromService(service, valuesMap) const valueHashes: string[] = ServiceAgreement.createValueHashes(values) - const timeoutValues: number[] = ServiceAgreement.getTimeoutValuesFromService(service) + const timelockValues: number[] = ServiceAgreement.getTimeValuesFromService(service, "timelock") + const timeoutValues: number[] = ServiceAgreement.getTimeValuesFromService(service, "timeout") - const serviceAgreementHashSignature = await ServiceAgreement.createSAHashSignature( - service, + if (!service.templateId) { + throw new Error("TemplateId not found in DDO.") + } + + const serviceAgreementHashSignature = await ServiceAgreement.createHashSignature( + service.templateId, serviceAgreementId, valueHashes, + timelockValues, timeoutValues, consumer, ) @@ -39,23 +43,22 @@ export default class ServiceAgreement { return serviceAgreementHashSignature } - private static async createSAHashSignature( - service: ServiceAccess, + public static async createHashSignature( + templateId: string, serviceAgreementId: string, valueHashes: string[], + timelockValues: number[], timeoutValues: number[], consumer: Account, ): Promise { - if (!service.templateId) { - throw new Error("TemplateId not found in ddo.") - } - const serviceAgreementHash = ServiceAgreement.hashServiceAgreement( - service.templateId, + templateId, serviceAgreementId, valueHashes, - timeoutValues) + timelockValues, + timeoutValues, + ) let serviceAgreementHashSignature = await signText(serviceAgreementHash, consumer.getId(), consumer.getPassword()) @@ -83,26 +86,28 @@ export default class ServiceAgreement { return hash } - private static hashServiceAgreement( + public static hashServiceAgreement( serviceAgreementTemplateId: string, serviceAgreementId: string, valueHashes: string[], + timelocks: number[], timeouts: number[], ): string { const args = [ - {type: "bytes32", value: serviceAgreementTemplateId}, - {type: "bytes32[]", value: valueHashes}, + {type: "address", value: zeroX(serviceAgreementTemplateId)}, + {type: "bytes32[]", value: valueHashes.map(zeroX)}, + {type: "uint256[]", value: timelocks}, {type: "uint256[]", value: timeouts}, - {type: "bytes32", value: "0x" + serviceAgreementId}, + {type: "bytes32", value: zeroX(serviceAgreementId)}, ] return (Web3Provider as any).getWeb3().utils.soliditySha3(...args).toString("hex") } - private static getTimeoutValuesFromService(service: ServiceAccess): number[] { + private static getTimeValuesFromService(service: ServiceAccess, type: "timeout" | "timelock"): number[] { const timeoutValues: number[] = service.serviceAgreementTemplate.conditions - .map((condition: ServiceAgreementTemplateCondition) => condition.timeout) + .map((condition: ServiceAgreementTemplateCondition) => condition[type]) return timeoutValues }