diff --git a/.deploy/SSL2.yaml b/.deploy/SSL2.yaml new file mode 100644 index 00000000..7c48333d --- /dev/null +++ b/.deploy/SSL2.yaml @@ -0,0 +1,11 @@ +# secret.yaml +apiVersion: v1 +kind: Secret + +metadata: + name: entity-ssl-test + namespace: hypermine-development +type: kubernetes.io/tls +stringData: + tls.key: "" + tls.crt: "" diff --git a/.deploy/cert2.yaml b/.deploy/cert2.yaml new file mode 100644 index 00000000..c9472a05 --- /dev/null +++ b/.deploy/cert2.yaml @@ -0,0 +1,13 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: entity-ssl-test + namespace: hypermine-development +spec: + dnsNames: + - api.entity-test.hypersign.id + - "*.api.entity-test.hypersign.id" + issuerRef: + kind: Issuer + name: letsencrypt-production + secretName: entity-ssl-test diff --git a/.deploy/deployment.yaml b/.deploy/deployment.yaml index 9f43920d..13cdfc7b 100644 --- a/.deploy/deployment.yaml +++ b/.deploy/deployment.yaml @@ -28,8 +28,8 @@ spec: resources: limits: - memory: "1Gi" - cpu: "500m" + memory: "2Gi" + cpu: "1000m" ports: - containerPort: __PORT__ env: @@ -57,8 +57,14 @@ spec: value: __MNEMONIC__ - name: JWT_SECRET value: __JWT_SECRET__ + - name: GLOBAL_TXN_CONTROLLER_QUEUE + value: __GLOBAL_TXN_CONTROLLER_QUEUE__ + - name: RABBIT_MQ_URI + value: __RABBIT_MQ_URI__ + - name: VAULT_PREFIX + value: __VAULT_PREFIX__ - name: WHITELISTED_CORS - value: "['https://entity.hypersign.id','https://api.entity.hypersign.id','https://wallet-prajna.hypersign.id']" + value: "['https://entity.hypersign.id','https://api.entity.hypersign.id','https://api.entity-test.hypersign.id','https://wallet-prajna.hypersign.id']" volumeMounts: - name: mongo mountPath: "/data" diff --git a/.deploy/ingress2.yaml b/.deploy/ingress2.yaml new file mode 100644 index 00000000..5f4a9c38 --- /dev/null +++ b/.deploy/ingress2.yaml @@ -0,0 +1,70 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: entity-ingress-test + namespace: hypermine-development + annotations: + kubernetes.io/ingress.allow-http: "false" + kubernetes.io/force-ssl-redirect: redirect + ingressClassName: "gce" + kubernetes.io/ingress.global-static-ip-name: entity-ip-test + cert-manager.io/issuer: letsencrypt-production + labels: + name: ingress + +spec: + tls: + - secretName: entity-ssl-test + hosts: + - "api.entity-test.hypersign.id" + - "*.api.entity-test.hypersign.id" + + rules: + - host: "api.entity-test.hypersign.id" + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: entity-api-test-service + port: + number: 3001 + - path: /ssi/ + pathType: Prefix + backend: + service: + name: entity-api-test-service + port: + number: 3001 + - path: /api/ + pathType: Prefix + backend: + service: + name: entity-api-test-service + port: + number: 3001 + - host: "*.api.entity-test.hypersign.id" + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: entity-api-test-service + port: + number: 3001 + - path: /ssi/ + pathType: Prefix + backend: + service: + name: entity-api-test-service + port: + number: 3001 + - path: /api/ + pathType: Prefix + backend: + service: + name: entity-api-test-service + port: + number: 3001 diff --git a/.github/workflows/CI-CD.yaml b/.github/workflows/CI-CD.yaml index a0c3ce29..d5650ee6 100644 --- a/.github/workflows/CI-CD.yaml +++ b/.github/workflows/CI-CD.yaml @@ -82,36 +82,42 @@ jobs: - name: "Configure kubectl" run: gcloud container clusters get-credentials hypermine-gke --region=asia-south1 - name: Replace tags - run: find .deploy/deployment.yaml -type f -exec sed -i -e "s#__LATEST_RELEASE_TAG__#${{ env.LATEST_RELEASE_TAG }}#" {} \; + run: find .deploy/deployment2.yaml -type f -exec sed -i -e "s#__LATEST_RELEASE_TAG__#${{ env.LATEST_RELEASE_TAG }}#" {} \; - name: "Replace secrets" - run: find .deploy/deployment.yaml -type f -exec sed -i ''s/__PORT__/${{ secrets.PORT }}/g'' {} \; + run: find .deploy/deployment2.yaml -type f -exec sed -i ''s/__PORT__/${{ secrets.PORT }}/g'' {} \; - name: "Replace secrets" - run: find .deploy/deployment.yaml -type f -exec sed -i -e "s#__BASE_DB_PATH__#${{ secrets.BASE_DB_PATH }}#" {} \; + run: find .deploy/deployment2.yaml -type f -exec sed -i -e "s#__BASE_DB_PATH__#${{ secrets.BASE_DB_PATH }}#" {} \; - name: "Replace secrets" - run: find .deploy/deployment.yaml -type f -exec sed -i -e "s#__DB_CONFIG__#${{ secrets.DB_CONFIG }}#" {} \; + run: find .deploy/deployment2.yaml -type f -exec sed -i -e "s#__DB_CONFIG__#${{ secrets.DB_CONFIG }}#" {} \; - name: "Replace secrets" - run: find .deploy/deployment.yaml -type f -exec sed -i -e "s#__HID_NETWORK_RPC__#${{ secrets.HID_NETWORK_RPC }}#" {} \; + run: find .deploy/deployment2.yaml -type f -exec sed -i -e "s#__HID_NETWORK_RPC__#${{ secrets.HID_NETWORK_RPC }}#" {} \; - name: "Replace secrets" - run: find .deploy/deployment.yaml -type f -exec sed -i -e "s#__HID_NETWORK_API__#${{ secrets.HID_NETWORK_API }}#" {} \; + run: find .deploy/deployment2.yaml -type f -exec sed -i -e "s#__HID_NETWORK_API__#${{ secrets.HID_NETWORK_API }}#" {} \; - name: "Replace secrets" - run: find .deploy/deployment.yaml -type f -exec sed -i -e "s#__HID_NETWORK_NAMESPACE__#${{ secrets.HID_NETWORK_NAMESPACE }}#" {} \; + run: find .deploy/deployment2.yaml -type f -exec sed -i -e "s#__HID_NETWORK_NAMESPACE__#${{ secrets.HID_NETWORK_NAMESPACE }}#" {} \; - name: "Replace secrets" - run: find .deploy/deployment.yaml -type f -exec sed -i -e "s#__EDV_BASE_URL__#${{ secrets.EDV_BASE_URL }}#" {} \; + run: find .deploy/deployment2.yaml -type f -exec sed -i -e "s#__EDV_BASE_URL__#${{ secrets.EDV_BASE_URL }}#" {} \; - name: "Replace secrets" - run: find .deploy/deployment.yaml -type f -exec sed -i -e "s#__EDV_CONFIG_DIR__#${{ secrets.EDV_CONFIG_DIR }}#" {} \; + run: find .deploy/deployment2.yaml -type f -exec sed -i -e "s#__EDV_CONFIG_DIR__#${{ secrets.EDV_CONFIG_DIR }}#" {} \; - name: "Replace secrets" - run: find .deploy/deployment.yaml -type f -exec sed -i -e "s#__EDV_DID_FILE_PATH__#${{ secrets.EDV_DID_FILE_PATH }}#" {} \; + run: find .deploy/deployment2.yaml -type f -exec sed -i -e "s#__EDV_DID_FILE_PATH__#${{ secrets.EDV_DID_FILE_PATH }}#" {} \; - name: "Replace secrets" - run: find .deploy/deployment.yaml -type f -exec sed -i -e "s#__EDV_KEY_FILE_PATH__#${{ secrets.EDV_KEY_FILE_PATH }}#" {} \; + run: find .deploy/deployment2.yaml -type f -exec sed -i -e "s#__EDV_KEY_FILE_PATH__#${{ secrets.EDV_KEY_FILE_PATH }}#" {} \; - name: "Replace secrets" - run: find .deploy/deployment.yaml -type f -exec sed -i -e "s#__MNEMONIC__#${{ secrets.MNEMONIC }}#" {} \; + run: find .deploy/deployment2.yaml -type f -exec sed -i -e "s#__GLOBAL_TXN_CONTROLLER_QUEUE__#${{ secrets.GLOBAL_TXN_CONTROLLER_QUEUE }}#" {} \; - name: "Replace secrets" - run: find .deploy/deployment.yaml -type f -exec sed -i -e "s#__JWT_SECRET__#${{ secrets.JWT_SECRET }}#" {} \; + run: find .deploy/deployment2.yaml -type f -exec sed -i -e "s#__RABBIT_MQ_URI__#${{ secrets.RABBIT_MQ_URI }}#" {} \; - name: "Replace secrets" - run: find .deploy/deployment.yaml -type f -exec sed -i ''s/__GOOGLE_ARTIFACT_URL__/${{ secrets.GOOGLE_ARTIFACT_URL }}/g'' {} \; + run: find .deploy/deployment2.yaml -type f -exec sed -i -e "s#__VAULT_PREFIX__#${{ secrets.VAULT_PREFIX }}#" {} \; - name: "Replace secrets" - run: find .deploy/deployment.yaml -type f -exec sed -i ''s/__GOOGLE_ARTIFACT_REPO__/${{ secrets.GOOGLE_ARTIFACT_REPO }}/g'' {} \; + run: find .deploy/deployment2.yaml -type f -exec sed -i -e "s#__MNEMONIC__#${{ secrets.MNEMONIC }}#" {} \; - name: "Replace secrets" - run: find .deploy/deployment.yaml -type f -exec sed -i ''s/__GOOGLE_PROJECT_ID__/${{ secrets.GOOGLE_PROJECT_ID }}/g'' {} \; + run: find .deploy/deployment2.yaml -type f -exec sed -i -e "s#__JWT_SECRET__#${{ secrets.JWT_SECRET }}#" {} \; + - name: "Replace secrets" + run: find .deploy/deployment2.yaml -type f -exec sed -i ''s/__GOOGLE_ARTIFACT_URL__/${{ secrets.GOOGLE_ARTIFACT_URL }}/g'' {} \; + - name: "Replace secrets" + run: find .deploy/deployment2.yaml -type f -exec sed -i ''s/__GOOGLE_ARTIFACT_REPO__/${{ secrets.GOOGLE_ARTIFACT_REPO }}/g'' {} \; + - name: "Replace secrets" + run: find .deploy/deployment2.yaml -type f -exec sed -i ''s/__GOOGLE_PROJECT_ID__/${{ secrets.GOOGLE_PROJECT_ID }}/g'' {} \; - name: "Deploy to GKE" run: kubectl apply -f .deploy/deployment.yaml diff --git a/package.json b/package.json index f7da6645..1778a7d8 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,8 @@ "@nestjs/passport": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "@nestjs/swagger": "^6.1.4", + "amqp-connection-manager": "^4.1.14", + "amqplib": "^0.10.4", "argon2": "^0.30.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", @@ -42,7 +44,7 @@ "fs": "^0.0.1-security", "hid-hd-wallet": "git+https://github.com/hypersign-protocol/hid-hd-wallet.git#main", "hs-ssi-sdk": "github:hypersign-protocol/hid-ssi-js-sdk#testcase/bjj", - "hypersign-edv-client": "github:hypersign-protocol/hypersign-edv-client#develop", + "hypersign-edv-client": "github:hypersign-protocol/hypersign-edv-client#deleteByDocumentId", "idb-keyval": "^6.2.1", "mongoose": "^6.8.3", "passport": "^0.6.0", diff --git a/src/app.module.ts b/src/app.module.ts index e996bd16..bb649722 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -7,6 +7,7 @@ import { DidModule } from './did/did.module'; import { SchemaModule } from './schema/schema.module'; import { CredentialModule } from './credential/credential.module'; import { PresentationModule } from './presentation/presentation.module'; +import { TxSendModuleModule } from './tx-send-module/tx-send-module.module'; @Module({ imports: [ ConfigModule.forRoot({ @@ -18,6 +19,7 @@ import { PresentationModule } from './presentation/presentation.module'; SchemaModule, CredentialModule, PresentationModule, + TxSendModuleModule, ], controllers: [], providers: [{ provide: APP_FILTER, useClass: AllExceptionsFilter }], diff --git a/src/credential/credential.module.ts b/src/credential/credential.module.ts index 5cc59bfc..3f2641b3 100644 --- a/src/credential/credential.module.ts +++ b/src/credential/credential.module.ts @@ -16,9 +16,10 @@ import { WhitelistSSICorsMiddleware } from 'src/utils/middleware/cors.middleware import { TrimMiddleware } from 'src/utils/middleware/trim.middleware'; import { credentialProviders } from './providers/credential.provider'; import { databaseProviders } from '../mongoose/tenant-mongoose-connections'; +import { TxSendModuleModule } from 'src/tx-send-module/tx-send-module.module'; @Module({ - imports: [EdvModule, HidWalletModule, DidModule], + imports: [EdvModule, HidWalletModule, DidModule, TxSendModuleModule], controllers: [CredentialController], providers: [ CredentialService, diff --git a/src/credential/services/credential.service.ts b/src/credential/services/credential.service.ts index 529385e4..aa351241 100644 --- a/src/credential/services/credential.service.ts +++ b/src/credential/services/credential.service.ts @@ -15,6 +15,7 @@ import { HypersignDID, HypersignVerifiableCredential } from 'hs-ssi-sdk'; import { VerifyCredentialDto } from '../dto/verify-credential.dto'; import { RegisterCredentialStatusDto } from '../dto/register-credential.dto'; import { getAppVault, getAppMenemonic } from '../../utils/app-vault-service'; +import { TxSendModuleService } from 'src/tx-send-module/tx-send-module.service'; @Injectable() export class CredentialService { @@ -24,8 +25,28 @@ export class CredentialService { private readonly hidWallet: HidWalletService, private credentialRepository: CredentialRepository, private readonly didRepositiory: DidRepository, + private readonly txnService: TxSendModuleService, ) {} + async checkAllowence(address) { + const url = + this.config.get('HID_NETWORK_API') + + '/cosmos/feegrant/v1beta1/allowances/' + + address; + + const resp = await fetch(url); + + const res = await resp.json(); + if (resp.status === 200) { + if (res.allowances.length > 0) { + return true; + } else { + return false; + } + } + return false; + } + async create(createCredentialDto: CreateCredentialDto, appDetail) { Logger.log('create() method: starts....', 'CredentialService'); const { @@ -137,8 +158,20 @@ export class CredentialService { issuerDid, verificationMethodId, privateKeyMultibase, - registerCredential: registerCredentialStatus, + registerCredential: false, }); + + const credStatusTemp = {}; + Object.assign(credStatusTemp, credentialStatus); + + const credStatus = { + credentialStatus, + namespace: nameSpace, + } as RegisterCredentialStatusDto; + if (registerCredentialStatus) { + await this.registerCredentialStatus(credStatus, appDetail); + } + let edvData = undefined; if (persist) { const creedential = { @@ -180,7 +213,7 @@ export class CredentialService { return { credentialDocument: signedCredential, - credentialStatus, + credentialStatus: credStatusTemp, persist, }; } catch (e) { @@ -311,7 +344,7 @@ export class CredentialService { ? namespace : this.config.get('NETWORK') ? this.config.get('NETWORK') - : 'testnet'; + : namespace; const hypersignVC = await this.credentialSSIService.initateHypersignVC( appMenemonic, nameSpace, @@ -327,17 +360,46 @@ export class CredentialService { 'update() method: before calling hypersignVC.updateCredentialStatus to update cred status on chain', 'CredentialService', ); - const updatedCredResult = await hypersignVC.updateCredentialStatus({ - credentialStatus, - issuerDid, - verificationMethodId, - privateKeyMultibase, - status: statusChange, - statusReason, - }); + + const { wallet, address } = await this.hidWallet.generateWallet( + appMenemonic, + ); + let updatedCredResult; + if (await this.checkAllowence(address)) { + const updateCredenital: any = await hypersignVC.updateCredentialStatus({ + credentialStatus, + issuerDid, + verificationMethodId, + privateKeyMultibase, + status: statusChange, + statusReason, + readonly: true, + }); + + await this.txnService.sendUpdateVC( + updateCredenital?.credentialStatus, + updateCredenital?.proofValue, + appMenemonic, + ); + } else { + updatedCredResult = await hypersignVC.updateCredentialStatus({ + credentialStatus, + issuerDid, + verificationMethodId, + privateKeyMultibase, + status: statusChange, + statusReason, + readonly: false, + }); + } + await this.credentialRepository.findOneAndUpdate( { appId: appDetail.appId, credentialId: id }, - { transactionHash: updatedCredResult.transactionHash }, + { + transactionHash: updatedCredResult?.transactionHash + ? updatedCredResult?.transactionHash + : '', + }, ); Logger.log('update() method: ends....', 'CredentialService'); @@ -419,6 +481,7 @@ export class CredentialService { ); const { credentialStatus, namespace } = registerCredentialDto; + const credentialId = credentialStatus.id; const { kmsId } = appDetail; Logger.log( 'registerCredentialStatus() method: initialising edv service', @@ -427,20 +490,32 @@ export class CredentialService { let registeredVC: { transactionHash: string }; try { const appMenemonic = await getAppMenemonic(kmsId); - const hypersignVC = await this.credentialSSIService.initateHypersignVC( - appMenemonic, - namespace, - ); Logger.log( 'registerCredentialStatus() method: before calling hypersignVC.registerCredentialStatus to register credential status on chain', 'CredentialService', ); + const { proof } = credentialStatus; + delete credentialStatus['proof']; - registeredVC = await hypersignVC.registerCredentialStatus({ - credentialStatus, - credentialStatusProof: proof, - }); + + const { wallet, address } = await this.hidWallet.generateWallet( + appMenemonic, + ); + + const hypersignVC = await this.credentialSSIService.initateHypersignVC( + appMenemonic, + namespace, + ); + + if (await this.checkAllowence(address)) { + await this.txnService.sendVCTxn(credentialStatus, proof, appMenemonic); + } else { + registeredVC = await hypersignVC.registerCredentialStatus({ + credentialStatus, + credentialStatusProof: proof, + }); + } } catch (e) { Logger.error( `registerCredentialStatus() method: Error ${e.message}`, @@ -452,6 +527,6 @@ export class CredentialService { 'registerCredentialStatus() method: ends....', 'CredentialService', ); - return { transactionHash: registeredVC.transactionHash }; + return { transactionHash: registeredVC?.transactionHash }; } } diff --git a/src/credential/services/credential.ssi.service.ts b/src/credential/services/credential.ssi.service.ts index 91b68e85..742043bb 100644 --- a/src/credential/services/credential.ssi.service.ts +++ b/src/credential/services/credential.ssi.service.ts @@ -10,7 +10,7 @@ export class CredentialSSIService { private readonly config: ConfigService, private readonly hidWallet: HidWalletService, ) {} - async initateHypersignVC(mnemonic: string, namespace: string) { + async initateHypersignVC(mnemonic: string,namespace: string): Promise { Logger.log('InitateHypersignVC(): starts....', 'CredentialSSIService'); const nodeRpcEndpoint = this.config.get('HID_NETWORK_RPC'); const nodeRestEndpoint = this.config.get('HID_NETWORK_API'); diff --git a/src/did/did.module.ts b/src/did/did.module.ts index f029853b..588b5d41 100644 --- a/src/did/did.module.ts +++ b/src/did/did.module.ts @@ -23,8 +23,9 @@ import { TrimMiddleware } from 'src/utils/middleware/trim.middleware'; import { databaseProviders } from '../mongoose/tenant-mongoose-connections'; import { didProviders } from './providers/did.provider'; import { JwtStrategy } from '../utils/jwt.strategy'; +import { TxSendModuleModule } from 'src/tx-send-module/tx-send-module.module'; @Module({ - imports: [EdvModule, HidWalletModule], + imports: [EdvModule, HidWalletModule, TxSendModuleModule], controllers: [DidController], providers: [ JwtStrategy, diff --git a/src/did/repository/did.repository.ts b/src/did/repository/did.repository.ts index 645cc471..1d98a919 100644 --- a/src/did/repository/did.repository.ts +++ b/src/did/repository/did.repository.ts @@ -5,7 +5,6 @@ import { DidMetaData, } from '../schemas/did.schema'; import { FilterQuery, Model } from 'mongoose'; -import { InjectConnection, InjectModel } from '@nestjs/mongoose'; import { Inject, Injectable, Logger } from '@nestjs/common'; @Injectable() diff --git a/src/did/services/did.service.ts b/src/did/services/did.service.ts index 15f4d06f..0c6cf606 100644 --- a/src/did/services/did.service.ts +++ b/src/did/services/did.service.ts @@ -30,6 +30,7 @@ import { Did as IDidDto } from '../schemas/did.schema'; import { AddVerificationMethodDto } from '../dto/addVm.dto'; import { getAppVault, getAppMenemonic } from '../../utils/app-vault-service'; import { ConfigService } from '@nestjs/config'; +import { TxSendModuleService } from 'src/tx-send-module/tx-send-module.service'; @Injectable({ scope: Scope.REQUEST }) export class DidService { @@ -39,8 +40,28 @@ export class DidService { private readonly hidWallet: HidWalletService, private readonly didSSIService: DidSSIService, private readonly config: ConfigService, + private readonly txnService: TxSendModuleService, ) {} + async checkAllowence(address) { + const url = + this.config.get('HID_NETWORK_API') + + '/cosmos/feegrant/v1beta1/allowances/' + + address; + + const resp = await fetch(url); + + const res = await resp.json(); + if (resp.status === 200) { + if (res.allowances.length > 0) { + return true; + } else { + return false; + } + } + return false; + } + // TODO: need to fix this once ed25519 is finished. async createByClientSpec(createDidDto: CreateDidDto, appDetail) { Logger.log('createByClientSpec() method: starts....', 'DidService'); @@ -367,16 +388,38 @@ export class DidService { seed: seed, }); const regDidDocument = registerDidDto.didDocument as Did; + + Logger.log( + 'register() method: before calling hypersignDid.register ', + 'DidService', + ); + const didDocPreserved = {}; + Object.assign(didDocPreserved, didDocument); + const signInfos = await hypersignDid.createSignInfos({ + didDocument, + privateKeyMultibase, + verificationMethodId: verificationMethodId, + }); const params = { - didDocument: regDidDocument, + didDocument: didDocPreserved, privateKeyMultibase, verificationMethodId: verificationMethodId, }; - Logger.log( - 'register() method: before calling hypersignDid.register ', - 'DidService', + + const { wallet, address } = await this.hidWallet.generateWallet( + appMenemonic, ); - registerDidDoc = await hypersignDid.register(params); + if (await this.checkAllowence(address)) { + await this.txnService.sendDIDTxn( + didDocument, + signInfos, + verificationMethodId, + appMenemonic, + ); + } else { + registerDidDoc = await hypersignDid.register(params); + } + data = await this.didRepositiory.findOneAndUpdate( { did: didDocument['id'] }, { @@ -506,7 +549,7 @@ export class DidService { const mnemonic = await getAppMenemonic(kmsId); const hypersignDid = await this.didSSIService.initiateHypersignDid( mnemonic, - 'testnet', + this.config.get('NETWORK') ? this.config.get('NETWORK') : 'testnet', ); const didInfo = await this.didRepositiory.findOne({ @@ -514,8 +557,13 @@ export class DidService { did, }); const { signInfos } = updateDidDto; + console.log({ + signInfos, + didInfo, + }); + // If signature is passed then no need to check if it is present in db or not - if (!signInfos && (!didInfo || didInfo == null)) { + if (!signInfos && (!didInfo || didInfo == null || didInfo == undefined)) { throw new NotFoundException([ `${did} not found`, `${did} is not owned by the appId ${appDetail.appId}`, @@ -613,30 +661,67 @@ export class DidService { }); try { + const { wallet, address } = await this.hidWallet.generateWallet( + appMenemonic, + ); + if (!updateDidDto.deactivate) { Logger.debug( 'updateDid() method: before calling hypersignDid.update to update did', 'DidService', ); - updatedDid = await hypersignDid.update({ - didDocument: updateDidDto.didDocument as Did, - privateKeyMultibase, - verificationMethodId: resolvedDid['verificationMethod'][0].id, - versionId: updatedDidDocMetaData.versionId, - }); + if ((await this.checkAllowence(address)) == false) { + updatedDid = await hypersignDid.update({ + didDocument: updateDidDto.didDocument as Did, + privateKeyMultibase, + verificationMethodId: resolvedDid['verificationMethod'][0].id, + versionId: updatedDidDocMetaData.versionId, + readonly: false, + }); + } else { + updatedDid = await hypersignDid.update({ + didDocument: updateDidDto.didDocument as Did, + privateKeyMultibase, + verificationMethodId: resolvedDid['verificationMethod'][0].id, + versionId: updatedDidDocMetaData.versionId, + readonly: true, + }); + await this.txnService.sendDIDUpdate( + updatedDid.didDocument, + updatedDid.signInfos, + updatedDid.versionId, + appMenemonic, + ); + } } else { Logger.debug( 'updateDid() method: before calling hypersignDid.deactivate to deactivate did', 'DidService', ); - updatedDid = await hypersignDid.deactivate({ - didDocument: updateDidDto.didDocument as Did, - privateKeyMultibase, - verificationMethodId: resolvedDid['verificationMethod'][0].id, - versionId: updatedDidDocMetaData.versionId, - }); + if ((await this.checkAllowence(address)) == false) { + updatedDid = await hypersignDid.deactivate({ + didDocument: updateDidDto.didDocument as Did, + privateKeyMultibase, + verificationMethodId: resolvedDid['verificationMethod'][0].id, + versionId: updatedDidDocMetaData.versionId, + }); + } else { + updatedDid = await hypersignDid.update({ + didDocument: updateDidDto.didDocument as Did, + privateKeyMultibase, + verificationMethodId: resolvedDid['verificationMethod'][0].id, + versionId: updatedDidDocMetaData.versionId, + readonly: true, + }); + await this.txnService.sendDIDDeactivate( + updatedDid.didDocument, + updatedDid.signInfos, + updatedDid.versionId, + appMenemonic, + ); + } } } catch (error) { Logger.error( diff --git a/src/hid-wallet/hid-wallet.module.ts b/src/hid-wallet/hid-wallet.module.ts index 17903d22..f0afa284 100644 --- a/src/hid-wallet/hid-wallet.module.ts +++ b/src/hid-wallet/hid-wallet.module.ts @@ -3,5 +3,6 @@ import { HidWalletService } from './services/hid-wallet.service'; @Module({ controllers: [], providers: [HidWalletService], + exports: [HidWalletService], }) export class HidWalletModule {} diff --git a/src/hid-wallet/services/hid-wallet.service.ts b/src/hid-wallet/services/hid-wallet.service.ts index e5a74e64..33b1d024 100644 --- a/src/hid-wallet/services/hid-wallet.service.ts +++ b/src/hid-wallet/services/hid-wallet.service.ts @@ -11,6 +11,7 @@ export class HidWalletService { async generateWallet(mnemonic?: string): Promise<{ mnemonic: string; + wallet: DirectSecp256k1HdWallet; address: string; }> { Logger.log('generateWallet() method: starts....', 'generateWallet'); @@ -41,6 +42,7 @@ export class HidWalletService { this.mnemonic = generatedMnemonice; return { mnemonic: generatedMnemonice, + wallet: wallet, address: hidWalletAddress[0].address, }; } diff --git a/src/presentation/repository/presentation-template.repository.ts b/src/presentation/repository/presentation-template.repository.ts index 51ebd567..ce9bd0ba 100644 --- a/src/presentation/repository/presentation-template.repository.ts +++ b/src/presentation/repository/presentation-template.repository.ts @@ -4,7 +4,6 @@ import { } from '../schemas/presentation-template.schema'; import { FilterQuery, Model } from 'mongoose'; import { Inject, Injectable, Logger } from '@nestjs/common'; -import { InjectModel } from '@nestjs/mongoose'; @Injectable() export class PresentationTemplateRepository { diff --git a/src/schema/repository/schema.repository.ts b/src/schema/repository/schema.repository.ts index 2daa12ee..281438d1 100644 --- a/src/schema/repository/schema.repository.ts +++ b/src/schema/repository/schema.repository.ts @@ -1,6 +1,5 @@ import { Schemas, SchemaDocument } from '../schemas/schemas.schema'; import { FilterQuery, Model } from 'mongoose'; -import { InjectModel } from '@nestjs/mongoose'; import { Inject, Injectable, Logger } from '@nestjs/common'; @Injectable() diff --git a/src/schema/schema.module.ts b/src/schema/schema.module.ts index abaecc11..65e2bef2 100644 --- a/src/schema/schema.module.ts +++ b/src/schema/schema.module.ts @@ -15,9 +15,10 @@ import { WhitelistSSICorsMiddleware } from 'src/utils/middleware/cors.middleware import { TrimMiddleware } from 'src/utils/middleware/trim.middleware'; import { schemaProviders } from './providers/schema.provider'; import { databaseProviders } from '../mongoose/tenant-mongoose-connections'; +import { TxSendModuleModule } from 'src/tx-send-module/tx-send-module.module'; @Module({ - imports: [DidModule], + imports: [DidModule, TxSendModuleModule], controllers: [SchemaController], providers: [ SchemaService, diff --git a/src/schema/services/schema.service.ts b/src/schema/services/schema.service.ts index dddb138d..6a04b78c 100644 --- a/src/schema/services/schema.service.ts +++ b/src/schema/services/schema.service.ts @@ -20,6 +20,7 @@ import { Schemas } from '../schemas/schemas.schema'; import { RegisterSchemaDto } from '../dto/register-schema.dto'; import { Namespace } from 'src/did/dto/create-did.dto'; import { getAppVault, getAppMenemonic } from '../../utils/app-vault-service'; +import { TxSendModuleService } from 'src/tx-send-module/tx-send-module.service'; @Injectable({ scope: Scope.REQUEST }) export class SchemaService { @@ -29,7 +30,27 @@ export class SchemaService { private readonly schemaSSIservice: SchemaSSIService, private readonly hidWallet: HidWalletService, private readonly didRepositiory: DidRepository, + private readonly txnService: TxSendModuleService, ) {} + + async checkAllowence(address) { + const url = + this.config.get('HID_NETWORK_API') + + '/cosmos/feegrant/v1beta1/allowances/' + + address; + + const resp = await fetch(url); + + const res = await resp.json(); + if (resp.status === 200) { + if (res.allowances.length > 0) { + return true; + } else { + return false; + } + } + return false; + } async create( createSchemaDto: CreateSchemaDto, appDetail, @@ -92,9 +113,23 @@ export class SchemaService { 'SchemaService', ); - const registeredSchema = await hypersignSchema.register({ - schema: signedSchema, - }); + const { wallet, address } = await this.hidWallet.generateWallet( + appMenemonic, + ); + let registeredSchema; + + if (await this.checkAllowence(address)) { + await this.txnService.sendSchemaTxn( + generatedSchema, + signedSchema.proof, + appMenemonic, + ); + } else { + registeredSchema = await hypersignSchema.register({ + schema: signedSchema, + }); + } + Logger.log( 'create() method: storing schema information to DB', 'SchemaService', @@ -103,13 +138,17 @@ export class SchemaService { schemaId: signedSchema.id, appId: appDetail.appId, authorDid: author, - transactionHash: registeredSchema['transactionHash'], + transactionHash: registeredSchema['transactionHash'] + ? registeredSchema['transactionHash'] + : '', }); Logger.log('create() method: ends', 'SchemaService'); return { schemaId: signedSchema.id, - transactionHash: registeredSchema['transactionHash'], + transactionHash: registeredSchema['transactionHash'] + ? registeredSchema['transactionHash'] + : '', }; } catch (error) { Logger.error( @@ -191,7 +230,7 @@ export class SchemaService { async registerSchema( registerSchemaDto: RegisterSchemaDto, appDetail, - ): Promise<{ transactionHash: string }> { + ): Promise { Logger.log('registerSchema() method: starts....', 'SchemaService'); const { edvId, kmsId } = appDetail; @@ -219,19 +258,30 @@ export class SchemaService { appMenemonic, namespace, ); - let registeredSchema = {} as { transactionHash: string }; schemaDocument['proof'] = schemaProof; Logger.log('registerSchema() method: registering schema on the blockchain'); + let registeredSchema; try { - registeredSchema = await hypersignSchema.register({ - schema: schemaDocument, - }); + const { wallet, address } = await this.hidWallet.generateWallet( + appMenemonic, + ); + if (await this.checkAllowence(address)) { + await this.txnService.sendSchemaTxn( + registerSchemaDto.schemaDocument, + registerSchemaDto.schemaProof, + appMenemonic, + ); + } else { + registeredSchema = await hypersignSchema.register({ + schema: schemaDocument, + }); + } } catch (e) { Logger.error('registerSchema() method: Error while registering schema'); throw new BadRequestException([e.message]); } Logger.log('registerSchema() method: ends....', 'SchemaService'); - return { transactionHash: registeredSchema.transactionHash }; + return { transactionHash: registeredSchema?.transactionHash }; } } diff --git a/src/tx-send-module/tx-send-module.module.ts b/src/tx-send-module/tx-send-module.module.ts new file mode 100644 index 00000000..eda3628a --- /dev/null +++ b/src/tx-send-module/tx-send-module.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { TxSendModuleService } from './tx-send-module.service'; +import { ConfigModule } from '@nestjs/config'; +import { HidWalletModule } from 'src/hid-wallet/hid-wallet.module'; +import { DidSSIService } from 'src/did/services/did.ssi.service'; + +@Module({ + imports: [ConfigModule, HidWalletModule], + controllers: [], + providers: [TxSendModuleService, DidSSIService], + exports: [TxSendModuleService], +}) +export class TxSendModuleModule {} diff --git a/src/tx-send-module/tx-send-module.service.spec.ts b/src/tx-send-module/tx-send-module.service.spec.ts new file mode 100644 index 00000000..d2bca434 --- /dev/null +++ b/src/tx-send-module/tx-send-module.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { TxSendModuleService } from './tx-send-module.service'; + +describe('TxSendModuleService', () => { + let service: TxSendModuleService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [TxSendModuleService], + }).compile(); + + service = module.get(TxSendModuleService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/tx-send-module/tx-send-module.service.ts b/src/tx-send-module/tx-send-module.service.ts new file mode 100644 index 00000000..308bd773 --- /dev/null +++ b/src/tx-send-module/tx-send-module.service.ts @@ -0,0 +1,508 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import amqp, { ChannelWrapper } from 'amqp-connection-manager'; +import { + MsgRegisterDID, + MsgRegisterCredentialStatus, + MsgUpdateCredentialStatus, + MsgUpdateDID, + MsgDeactivateDID, + MsgRegisterCredentialSchema, +} from 'hs-ssi-sdk/build/libs/generated/ssi/tx'; +import { DidSSIService } from 'src/did/services/did.ssi.service'; +import { HidWalletService } from 'src/hid-wallet/services/hid-wallet.service'; +import { StdFee } from '@cosmjs/stargate'; +import { + MsgExec, + MsgGrant, + MsgRevoke, +} from 'cosmjs-types/cosmos/authz/v1beta1/tx'; + +@Injectable() +export class TxSendModuleService { + private channel: ChannelWrapper; + private granterAddress; + constructor( + private readonly configService: ConfigService, + private readonly hidWalletService: HidWalletService, + private readonly didSSIService: DidSSIService, + ) { + this.connect(); + } + + async invokeTxnController(address, granteeMnemonic) { + const podENV = { + RMQ_URL: this.configService.get('RABBIT_MQ_URI'), + QUEUE_NAME: 'TXN_QUEUE_' + address, + NODE_RPC_URL: this.configService.get('HID_NETWORK_RPC'), + GRANTEE_MNEMONIC: granteeMnemonic, + GRANTER_ADDRESS: this.granterAddress, + DID_REGISTER_FIXED_FEE: '50', + CRED_REGISTER_FIXED_FEE: '50', + SCHEMA_CREATE_FIXED_FEE: '50', + ESTIMATE_GAS_PRICE: '155303', + podName: 'txn-dynamic', + granteeWalletAddress: address, + }; + + await this.channel.assertQueue('GLOBAL_TXN_CONTROLLER_QUEUE', { + durable: false, + }); + const sendToQueue2 = await this.channel.sendToQueue( + this.configService.get('GLOBAL_TXN_CONTROLLER_QUEUE'), + Buffer.from(JSON.stringify(podENV)), + ); + } + + async prepareMsgCreateDID( + didDocument, + didDocumentSigned, + verificationMethodId, + txAuthor, + ): Promise { + const proof = didDocumentSigned?.find((e) => { + return e.verification_method_id === verificationMethodId; + }); + + const vm = didDocument.verificationMethod?.find((e) => { + return e.id == verificationMethodId; + }); + let signatureType = ''; + let proofPurpose = ''; + switch (vm.type) { + case 'Ed25519VerificationKey2020': { + signatureType = 'Ed25519Signature2020'; + proofPurpose = 'assertionMethod'; + + break; + } + default: { + throw Error('Type is not matched'); + } + } + + return MsgRegisterDID.fromPartial({ + didDocument: didDocument, + didDocumentProofs: [ + { + verificationMethod: verificationMethodId, + type: signatureType, + proofPurpose: proofPurpose, + created: proof.created, + proofValue: proof.signature, + }, + ], + txAuthor: txAuthor, + }); + } + + async connect() { + Logger.log('Connecting Rabbit'); + const connection = await amqp.connect( + this.configService.get('RABBIT_MQ_URI'), + ); + this.channel = await connection.createChannel(); + const { address: granterAddress } = + await this.hidWalletService.generateWallet( + this.configService.get('MNEMONIC'), + ); + this.granterAddress = granterAddress; + Logger.log('Connected Rabbit'); + } + + async prepareRegisterCredentialStatus( + credentialStatus, + credentialStatusProof, + txAuthor, + ) { + return MsgRegisterCredentialStatus.fromPartial({ + credentialStatusDocument: credentialStatus, + credentialStatusProof: credentialStatusProof, + txAuthor, + }); + } + + async prepareUpdateCredentialStatus(credentialStatus, proofValue, address) { + return MsgUpdateCredentialStatus.fromPartial({ + credentialStatusDocument: credentialStatus, + credentialStatusProof: proofValue, + txAuthor: address, + }); + } + + async sendUpdateVC(credentialStatus, proofValue, granteeMnemonic) { + if (!this.channel) { + await this.connect(); + } + const { wallet, address } = await this.hidWalletService.generateWallet( + granteeMnemonic, + ); + const msgUpdateCredentialStatus = await this.prepareUpdateCredentialStatus( + credentialStatus, + proofValue, + address, + ); + + const authExecMsg: MsgExec = { + grantee: address, + msgs: [ + { + typeUrl: '/hypersign.ssi.v1.MsgUpdateCredentialStatus', + value: MsgUpdateCredentialStatus.encode( + msgUpdateCredentialStatus, + ).finish(), + }, + ], + }; + + const fee = { + amount: [ + { + denom: 'uhid', + amount: '100', + }, + ], + gas: '500000', + granter: this.granterAddress, // NOTE: It is VERY IMPORTANT to explicitly pass granter's address + }; + + const txMsg = { + typeUrl: '/cosmos.authz.v1beta1.MsgExec', + value: authExecMsg, + }; + + const data = { + type: 'CRED_UPDATE', + txMsg, + }; + + const queue = 'TXN_QUEUE_' + address; + await this.channel.assertQueue(queue, { + durable: false, + }); + + const sendToQueue1 = await this.channel.sendToQueue( + queue, + Buffer.from(JSON.stringify(data)), + ); + + await this.invokeTxnController(address, granteeMnemonic); + } + + async sendVCTxn(credentialStatus, credentialStatusProof, granteeMnemonic) { + if (!this.channel) { + await this.connect(); + } + const { wallet, address } = await this.hidWalletService.generateWallet( + granteeMnemonic, + ); + + const msgRegisterCredentialStatus = + await this.prepareRegisterCredentialStatus( + credentialStatus, + credentialStatusProof, + address, + ); + const authExecMsg: MsgExec = { + grantee: address, + msgs: [ + { + typeUrl: '/hypersign.ssi.v1.MsgRegisterCredentialStatus', + value: MsgRegisterCredentialStatus.encode( + msgRegisterCredentialStatus, + ).finish(), + }, + ], + }; + + const fee = { + amount: [ + { + denom: 'uhid', + amount: '100', + }, + ], + gas: '500000', + granter: this.granterAddress, // NOTE: It is VERY IMPORTANT to explicitly pass granter's address + }; + + const txMsg = { + typeUrl: '/cosmos.authz.v1beta1.MsgExec', + value: authExecMsg, + }; + + const data = { + type: 'CRED_REGISTER', + txMsg, + }; + + const queue = 'TXN_QUEUE_' + address; + await this.channel.assertQueue(queue, { + durable: false, + }); + + const sendToQueue1 = await this.channel.sendToQueue( + queue, + Buffer.from(JSON.stringify(data)), + ); + + await this.invokeTxnController(address, granteeMnemonic); + } + + async prepareMsgUpdateDID(didDocument, signInfos, versionId, txAuthor) { + return MsgUpdateDID.fromPartial({ + didDocument, + didDocumentProofs: signInfos, + versionId, + txAuthor, + }); + } + + async sendDIDDeactivate( + didDocument: any, + signInfos: any, + versionId: any, + granteeMnemonic: any, + ) { + if (!this.channel) { + await this.connect(); + } + + const { wallet, address } = await this.hidWalletService.generateWallet( + granteeMnemonic, + ); + const msgDeactivateDID = await this.prepareMsgDeactivateDID( + didDocument, + signInfos, + versionId, + address, + ); + + const authExecMsg: MsgExec = { + grantee: address, + msgs: [ + { + typeUrl: '/hypersign.ssi.v1.MsgDeactivateDID', + value: MsgDeactivateDID.encode(msgDeactivateDID).finish(), + }, + ], + }; + + const fee = { + amount: [ + { + denom: 'uhid', + amount: '100', + }, + ], + gas: '500000', + granter: this.granterAddress, // NOTE: It is VERY IMPORTANT to explicitly pass granter's address + }; + const txMsg = { + typeUrl: '/cosmos.authz.v1beta1.MsgExec', + value: authExecMsg, + }; + const queue = 'TXN_QUEUE_' + address; + await this.channel.assertQueue(queue, { + durable: false, + }); + + const data = { + type: 'DID_DEACTIVATE', + txMsg, + }; + const sendToQueue1 = await this.channel.sendToQueue( + queue, + Buffer.from(JSON.stringify(data)), + ); + + await this.invokeTxnController(address, granteeMnemonic); + } + prepareMsgDeactivateDID( + didDocument: any, + signInfos: any, + versionId: any, + address: string, + ) { + return MsgDeactivateDID.fromPartial({ + didDocumentId: didDocument.id, + didDocumentProofs: signInfos, + txAuthor: address, + versionId, + }); + } + + async sendDIDUpdate(didDocument, signInfos, versionId, granteeMnemonic) { + if (!this.channel) { + await this.connect(); + } + + const { wallet, address } = await this.hidWalletService.generateWallet( + granteeMnemonic, + ); + const msgUpdateDID = await this.prepareMsgUpdateDID( + didDocument, + signInfos, + versionId, + address, + ); + const authExecMsg: MsgExec = { + grantee: address, + msgs: [ + { + typeUrl: '/hypersign.ssi.v1.MsgUpdateDID', + value: MsgUpdateDID.encode(msgUpdateDID).finish(), + }, + ], + }; + + const fee = { + amount: [ + { + denom: 'uhid', + amount: '100', + }, + ], + gas: '500000', + granter: this.granterAddress, // NOTE: It is VERY IMPORTANT to explicitly pass granter's address + }; + const txMsg = { + typeUrl: '/cosmos.authz.v1beta1.MsgExec', + value: authExecMsg, + }; + const queue = 'TXN_QUEUE_' + address; + await this.channel.assertQueue(queue, { + durable: false, + }); + + const data = { + type: 'DID_UPDATE', + txMsg, + }; + const sendToQueue1 = await this.channel.sendToQueue( + queue, + Buffer.from(JSON.stringify(data)), + ); + + await this.invokeTxnController(address, granteeMnemonic); + } + + async sendDIDTxn( + didDocument, + didDocumentSigned, + verificationMethodId, + granteeMnemonic, + ) { + if (!this.channel) { + await this.connect(); + } + + const { wallet, address } = await this.hidWalletService.generateWallet( + granteeMnemonic, + ); + const msgCreateDID = await this.prepareMsgCreateDID( + didDocument, + didDocumentSigned, + verificationMethodId, + address, + ); + + const authExecMsg: MsgExec = { + grantee: address, + msgs: [ + { + typeUrl: '/hypersign.ssi.v1.MsgRegisterDID', + value: MsgRegisterDID.encode(msgCreateDID).finish(), + }, + ], + }; + const fee = { + amount: [ + { + denom: 'uhid', + amount: '100', + }, + ], + gas: '500000', + granter: this.granterAddress, // NOTE: It is VERY IMPORTANT to explicitly pass granter's address + }; + const txMsg = { + typeUrl: '/cosmos.authz.v1beta1.MsgExec', + value: authExecMsg, + }; + const queue = 'TXN_QUEUE_' + address; + await this.channel.assertQueue(queue, { + durable: false, + }); + + const data = { + type: 'DID_REGISTER', + txMsg, + }; + const sendToQueue1 = await this.channel.sendToQueue( + queue, + Buffer.from(JSON.stringify(data)), + ); + + await this.invokeTxnController(address, granteeMnemonic); + } + + prepareSchemaMsg(schema, proof, txAuthor) { + return MsgRegisterCredentialSchema.fromPartial({ + credentialSchemaDocument: schema, + credentialSchemaProof: proof, + txAuthor: txAuthor, + }); + } + + async sendSchemaTxn(schema, proof, granteeMnemonic) { + if (!this.channel) { + await this.connect(); + } + + const { wallet, address } = await this.hidWalletService.generateWallet( + granteeMnemonic, + ); + const msgSchema = await this.prepareSchemaMsg(schema, proof, address); + + const authExecMsg: MsgExec = { + grantee: address, + msgs: [ + { + typeUrl: '/hypersign.ssi.v1.MsgRegisterCredentialSchema', + value: MsgRegisterCredentialSchema.encode(msgSchema).finish(), + }, + ], + }; + const fee = { + amount: [ + { + denom: 'uhid', + amount: '100', + }, + ], + gas: '500000', + granter: this.granterAddress, // NOTE: It is VERY IMPORTANT to explicitly pass granter's address + }; + const txMsg = { + typeUrl: '/cosmos.authz.v1beta1.MsgExec', + value: authExecMsg, + }; + const queue = 'TXN_QUEUE_' + address; + await this.channel.assertQueue(queue, { + durable: false, + }); + + const data = { + type: 'SCHEMA_REGISTER', + txMsg, + }; + const sendToQueue1 = await this.channel.sendToQueue( + queue, + Buffer.from(JSON.stringify(data)), + ); + + await this.invokeTxnController(address, granteeMnemonic); + return sendToQueue1; + } +}