diff --git a/.example.env b/.example.env index d3f2ca4..14c389d 100644 --- a/.example.env +++ b/.example.env @@ -64,7 +64,7 @@ SECP256K1_SIGN_ETHEREUM_TRANSACTION=/secp256k1/sign/ethereum-tx # Did Registry CHAIN_ID = 0x9e55c -DID_REGISTRY_ADDRESS = 0x00f23cda3cbec27ae630894dd84e88033676d136 # optional address, just in case you are willing to use another did registry +# CUSTOM_DID_REG = '0x54358D929CCf45C7cCEE8Ca60FCD0C0402489F54,lac1,0001,0x9e55c' #'didRegistryAddress,didType,didVersion,chainId' # DOMAIN_NAME = lacchain.id # optional param just in case you are willing to use another domain name diff --git a/.example.env.dev b/.example.env.dev index 968ce25..59ecaeb 100644 --- a/.example.env.dev +++ b/.example.env.dev @@ -65,7 +65,7 @@ SECP256K1_SIGN_LACCHAIN_TRANSACTION=/secp256k1/sign/lacchain-tx # Did Registry CHAIN_ID = 0x9e55c -# DID_REGISTRY_ADDRESS = 0x690b9a9e9aa1c9db991c7721a92d351db4fac990 # optional address, just in case you are willing to use another did registry +# CUSTOM_DID_REG = '0x54358D929CCf45C7cCEE8Ca60FCD0C0402489F54,lac1,0001,0x9e55c' #'didRegistryAddress,didType,didVersion,chainId' # DOMAIN_NAME = lacchain.id # optional param just in case you are willing to use another domain name diff --git a/docs/API-Guide.md b/docs/API-Guide.md index e826910..546616f 100644 --- a/docs/API-Guide.md +++ b/docs/API-Guide.md @@ -19,7 +19,7 @@ api_url=http://localhost:3001 # Set LACPass API url 2. Create a did ```sh -create_did_url="$api_url"/api/v1/did-lac1 +create_did_url="$api_url"/api/v1/did/lac1 r=`curl -s -X 'POST' \ ${create_did_url} \ -H 'accept: application/json' \ @@ -34,7 +34,7 @@ echo "Did was created: $did" ```sh ## input variables path_to_crt=../certs/DSC/DSC.crt # you should point to the public pem certificate that represents the signing certificate used to sign health related data -did="did:lac1:1iT4ksoK9qcxYYe8vGu2fZEDsYHCkJ7FkpxMeSJUrgLRDMmH3g5hKGry4H9bqGpZJPpB" # replace with the did previously created +did="did:lac1:1iT4XSXrcTkcUJ7mMgSW9eWubkNkShNLjfau6T3mSg6fyXAuj2DdaHW55eFQ6D9hjq7w" # replace with the did previously created # process @@ -42,4 +42,20 @@ add_pem_certificate_url="$api_url"/api/v1/did/lac1/attribute/add/jwk-from-x509ce relation=asse data='{"did":'"\"$did\""', "relation":'"\"$relation\""'}' curl -X 'POST' ${add_pem_certificate_url} -H 'accept: application/json' -F x509Cert=@$path_to_crt -F data=$data +``` + +4. Disassociate an x509 Certificate from a Did + +```sh +## input variables +path_to_crt=../certs/DSC/DSC.crt # you should point to the public pem certificate that represents the signing certificate used to sign health related data +did="did:lac1:1iT4XSXrcTkcUJ7mMgSW9eWubkNkShNLjfau6T3mSg6fyXAuj2DdaHW55eFQ6D9hjq7w" # replace with the did previously created +compromised=false +backwardRevocationDays=0 # backward revocation days + +# process +disassociate_pem_certificate_url="$api_url"/api/v1/did/lac1/attribute/revoke/jwk-from-x509certificate +relation=asse +data='{"did":'"\"$did\""', "relation":'"\"$relation\""', "compromised":'$compromised', "backwardRevocationDays":'$backwardRevocationDays'}' +curl -X 'DELETE' ${disassociate_pem_certificate_url} -H 'accept: application/json' -F x509Cert=@$path_to_crt -F data=$data ``` \ No newline at end of file diff --git a/package.json b/package.json index 7f5c647..f5265d6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lacpass-identity", - "version": "0.0.23", + "version": "0.0.24", "description": "Rest api for laccpass identity manager", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", diff --git a/src/config/index.ts b/src/config/index.ts index be3c8b0..cf1b1c0 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -1,14 +1,16 @@ import { randomUUID } from 'crypto'; import { + DEFAULT_DID_REGISTRY, DEFAULT_DOMAIN_NAME, - DEFAULT_REGISTRY, + DID_CODE, DOMAIN_NAMES, - REGISTRY + SUPPORTED_DID_TYPES } from '../constants/did-web/lac/didRegistryAddresses'; import { config } from 'dotenv'; import { ethers } from 'ethers'; import { LogLevel } from 'typescript-logging'; import { Log4TSProvider } from 'typescript-logging-log4ts-style'; +import { DidRegistryParams } from 'src/interfaces/did/did.generics'; config({ path: `.env.${process.env.ENV || 'dev'}` }); @@ -64,35 +66,87 @@ export const TESTING_ENV = process.env.ENV === 'test'; export const CI_ENV = process.env.ENV === 'ci'; export const JWT_SECRET_DEFAULT = 'some-secret-string-default'; -export const resolveDidRegistryAddress = ( - didRegistryAddress = process.env.DID_REGISTRY_ADDRESS -): string => { - // const didRegistryAddress = process.env.DID_REGISTRY_ADDRESS; - if (didRegistryAddress) { - if (!ethers.utils.isAddress(didRegistryAddress)) { +export const resolveChainRegistry = (): DidRegistryParams => { + const supportedChainId = getChainId(); + const customDidRegEnv = process.env.CUSTOM_DID_REG; + // 'didRegistryAddress,didType,didVersion,chainId' + if (!customDidRegEnv) { + // return default did registry + const defaultDidRegistry = DEFAULT_DID_REGISTRY.get(supportedChainId); + if (!defaultDidRegistry) { log.error( - 'Specified DID_REGISTRY_ADDRESS', - DID_REGISTRY_ADDRESS, - 'is not a valid address ... exiting' + 'There is no default did registry for chainId ' + supportedChainId ); - process.exit(1); // exiting since this is a critical error - } - if ( - didRegistryAddress && - !REGISTRY.get(CHAIN_ID)?.find(el => el === didRegistryAddress) - ) { - log.info('Unknown specified did registry address ', didRegistryAddress); + process.exit(1); } - log.info('Returning custom did registry address', didRegistryAddress); - return didRegistryAddress; + return { + ...defaultDidRegistry, + chainId: supportedChainId + }; + } + const customDidRegParam = customDidRegEnv.split(','); + if (customDidRegParam.length !== 4) { + log.error( + 'Invalid number of param when reading CUSTOM_DID_REG environemt variable, read:', + customDidRegEnv + ); + process.exit(1); + } + const [didRegistryAddress, didMethod, didVersion, chainId] = + customDidRegParam; + const didType = DID_CODE.get(didMethod); + if (!didType) { + log.error('did type not supported, read did method:', didMethod); + process.exit(1); + } + // validate type, version tuple is supported + const isSupportedDidType = SUPPORTED_DID_TYPES.get(didType); + if ( + !isSupportedDidType || + (isSupportedDidType && !isSupportedDidType.get(didVersion)) + ) { + log.error( + 'Unsupported did type/didVersion, read didType:', + didType, + 'read didVersion:', + didVersion + ); + process.exit(1); + } + // validate chainId is supported + if (chainId !== supportedChainId) { + log.error( + 'Unsupported chainId passed alongside custom did registry, read chainId:', + chainId, + 'supported version:', + supportedChainId + ); + process.exit(1); } - const wellKnownRegistryAddress = DEFAULT_REGISTRY.get(CHAIN_ID); - if (!wellKnownRegistryAddress) { - log.error('Could not find well-known registry address for chain', CHAIN_ID); + // add registry + if (!ethers.utils.isAddress(didRegistryAddress)) { + log.error( + 'Specified did registry address: ', + didRegistryAddress, + ', is not a valid address ... exiting' + ); process.exit(1); // exiting since this is a critical error } - log.info('Returning default registry address', wellKnownRegistryAddress); - return wellKnownRegistryAddress; + log.info( + 'Returning custom did registry address', + didRegistryAddress, + 'chainId:', + chainId, + 'didMethod:', + didMethod + ); + return { + didRegistryAddress, + didMethod, + didType, + didVersion, + chainId: supportedChainId + }; }; export const resolveDidDomainName = (): string => { @@ -120,7 +174,6 @@ export const resolveDidDomainName = (): string => { return defaultDomainName; }; -export const DID_REGISTRY_ADDRESS = resolveDidRegistryAddress(); export const DOMAIN_NAME = resolveDidDomainName(); export const { diff --git a/src/constants/did-web/lac/didRegistryAddresses.ts b/src/constants/did-web/lac/didRegistryAddresses.ts index c8988e6..8165f8f 100644 --- a/src/constants/did-web/lac/didRegistryAddresses.ts +++ b/src/constants/did-web/lac/didRegistryAddresses.ts @@ -1,6 +1,51 @@ -export const REGISTRY: Map = new Map(); -REGISTRY.set('0x9e55c', ['0xA850839d7b37B8e18a9673213CC0f7400dDd8771']); -export const DEFAULT_REGISTRY: Map = new Map(); -DEFAULT_REGISTRY.set('0x9e55c', '0xA850839d7b37B8e18a9673213CC0f7400dDd8771'); +import { DidSpec } from 'src/interfaces/did/did.generics'; + +type didversionType = Map; +type didType = Map; +type didRegistryType = Map; +type chainregistryType = Map; + +export const DID_LAC1_NAME = 'lac1'; +export const DID_CODE = new Map([[DID_LAC1_NAME, '0001']]); +export const OPEN_PROTESTESNET_CHAIN_ID = '0x9e55c'; + +export const SUPPORTED_DID_TYPES = new Map([ + ['0001', new Map([['0001', true]])] +]); + +// setting for open protestnet +export const CHAIN_REGISTRY: chainregistryType = new Map< + string, + didRegistryType +>(); +const openProtestnetSupportedRegistries: didRegistryType = new Map< + string, + didType +>(); +openProtestnetSupportedRegistries.set( + '0x54358D929CCf45C7cCEE8Ca60FCD0C0402489F54', + new Map([['0001', new Map([['0001', true]])]]) +); +CHAIN_REGISTRY.set( + OPEN_PROTESTESNET_CHAIN_ID, + openProtestnetSupportedRegistries +); + +export const DEFAULT_DID_REGISTRY: Map = new Map< + string, + DidSpec +>([ + [ + OPEN_PROTESTESNET_CHAIN_ID, + { + didType: '0001', + didVersion: '0001', + didRegistryAddress: '0x54358D929CCf45C7cCEE8Ca60FCD0C0402489F54', + didMethod: 'lac1' + } + ] +]); + +// WIP: for did:web export const DOMAIN_NAMES: string[] = ['lacchain.id', 'another.lacchain.id']; export const DEFAULT_DOMAIN_NAME = 'lacchain.id'; diff --git a/src/constants/did-web/lac/lacDidRegistryAbi.ts b/src/constants/did-web/lac/lacDidRegistryAbi.ts index 5b47bb7..3b0918a 100644 --- a/src/constants/did-web/lac/lacDidRegistryAbi.ts +++ b/src/constants/did-web/lac/lacDidRegistryAbi.ts @@ -5,6 +5,26 @@ export const LAC_DIDREGISTRY_RECOVERABLE_ABI = [ internalType: 'uint256', name: '_minKeyRotationTime', type: 'uint256' + }, + { + internalType: 'uint256', + name: '_maxAttempts', + type: 'uint256' + }, + { + internalType: 'uint256', + name: '_minControllers', + type: 'uint256' + }, + { + internalType: 'uint256', + name: '_resetSeconds', + type: 'uint256' + }, + { + internalType: 'address', + name: 'trustedForwarderAddr', + type: 'address' } ], stateMutability: 'nonpayable', @@ -37,11 +57,23 @@ export const LAC_DIDREGISTRY_RECOVERABLE_ABI = [ name: 'validTo', type: 'uint256' }, + { + indexed: false, + internalType: 'uint256', + name: 'changeTime', + type: 'uint256' + }, { indexed: false, internalType: 'uint256', name: 'previousChange', type: 'uint256' + }, + { + indexed: false, + internalType: 'bool', + name: 'compromised', + type: 'bool' } ], name: 'DIDAttributeChanged', @@ -72,6 +104,55 @@ export const LAC_DIDREGISTRY_RECOVERABLE_ABI = [ name: 'DIDControllerChanged', type: 'event' }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'identity', + type: 'address' + }, + { + indexed: false, + internalType: 'bytes32', + name: 'delegateType', + type: 'bytes32' + }, + { + indexed: false, + internalType: 'address', + name: 'delegate', + type: 'address' + }, + { + indexed: false, + internalType: 'uint256', + name: 'validTo', + type: 'uint256' + }, + { + indexed: false, + internalType: 'uint256', + name: 'changeTime', + type: 'uint256' + }, + { + indexed: false, + internalType: 'uint256', + name: 'previousChange', + type: 'uint256' + }, + { + indexed: false, + internalType: 'bool', + name: 'reason', + type: 'bool' + } + ], + name: 'DIDDelegateChanged', + type: 'event' + }, { inputs: [ { @@ -90,6 +171,106 @@ export const LAC_DIDREGISTRY_RECOVERABLE_ABI = [ stateMutability: 'nonpayable', type: 'function' }, + { + inputs: [ + { + internalType: 'address', + name: 'identity', + type: 'address' + }, + { + internalType: 'bytes32', + name: 'delegateType', + type: 'bytes32' + }, + { + internalType: 'address', + name: 'delegate', + type: 'address' + }, + { + internalType: 'uint256', + name: 'validity', + type: 'uint256' + } + ], + name: 'addDelegate', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: 'identity', + type: 'address' + }, + { + internalType: 'uint8', + name: 'sigV', + type: 'uint8' + }, + { + internalType: 'bytes32', + name: 'sigR', + type: 'bytes32' + }, + { + internalType: 'bytes32', + name: 'sigS', + type: 'bytes32' + }, + { + internalType: 'bytes32', + name: 'delegateType', + type: 'bytes32' + }, + { + internalType: 'address', + name: 'delegate', + type: 'address' + }, + { + internalType: 'uint256', + name: 'validity', + type: 'uint256' + } + ], + name: 'addDelegateSigned', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address' + }, + { + internalType: 'bytes32', + name: '', + type: 'bytes32' + }, + { + internalType: 'bytes32', + name: '', + type: 'bytes32' + } + ], + name: 'attributes', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256' + } + ], + stateMutability: 'view', + type: 'function' + }, { inputs: [ { @@ -184,6 +365,35 @@ export const LAC_DIDREGISTRY_RECOVERABLE_ABI = [ stateMutability: 'view', type: 'function' }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address' + }, + { + internalType: 'bytes32', + name: '', + type: 'bytes32' + }, + { + internalType: 'address', + name: '', + type: 'address' + } + ], + name: 'delegates', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256' + } + ], + stateMutability: 'view', + type: 'function' + }, { inputs: [ { @@ -215,6 +425,54 @@ export const LAC_DIDREGISTRY_RECOVERABLE_ABI = [ stateMutability: 'nonpayable', type: 'function' }, + { + inputs: [ + { + internalType: 'address', + name: 'identity', + type: 'address' + }, + { + internalType: 'bytes32', + name: 'attributeNameHash', + type: 'bytes32' + }, + { + internalType: 'bytes32', + name: 'attributeValueHash', + type: 'bytes32' + } + ], + name: 'expirationAttribute', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address' + } + ], + name: 'failedAttempts', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256' + } + ], + stateMutability: 'view', + type: 'function' + }, { inputs: [ { @@ -253,6 +511,25 @@ export const LAC_DIDREGISTRY_RECOVERABLE_ABI = [ stateMutability: 'view', type: 'function' }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address' + } + ], + name: 'lastAttempt', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256' + } + ], + stateMutability: 'view', + type: 'function' + }, { inputs: [ { @@ -272,6 +549,63 @@ export const LAC_DIDREGISTRY_RECOVERABLE_ABI = [ stateMutability: 'view', type: 'function' }, + { + inputs: [ + { + internalType: 'address', + name: 'identity', + type: 'address' + }, + { + internalType: 'uint8', + name: 'sigV', + type: 'uint8' + }, + { + internalType: 'bytes32', + name: 'sigR', + type: 'bytes32' + }, + { + internalType: 'bytes32', + name: 'sigS', + type: 'bytes32' + }, + { + internalType: 'address', + name: 'proofController', + type: 'address' + } + ], + name: 'recover', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address' + }, + { + internalType: 'uint256', + name: '', + type: 'uint256' + } + ], + name: 'recoveredKeys', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address' + } + ], + stateMutability: 'view', + type: 'function' + }, { inputs: [ { @@ -306,6 +640,16 @@ export const LAC_DIDREGISTRY_RECOVERABLE_ABI = [ internalType: 'bytes', name: 'value', type: 'bytes' + }, + { + internalType: 'uint256', + name: 'revokeDeltaTime', + type: 'uint256' + }, + { + internalType: 'bool', + name: 'compromised', + type: 'bool' } ], name: 'revokeAttribute', @@ -344,6 +688,16 @@ export const LAC_DIDREGISTRY_RECOVERABLE_ABI = [ internalType: 'bytes', name: 'value', type: 'bytes' + }, + { + internalType: 'uint256', + name: 'revokeDeltaTime', + type: 'uint256' + }, + { + internalType: 'bool', + name: 'compromised', + type: 'bool' } ], name: 'revokeAttributeSigned', @@ -351,6 +705,87 @@ export const LAC_DIDREGISTRY_RECOVERABLE_ABI = [ stateMutability: 'nonpayable', type: 'function' }, + { + inputs: [ + { + internalType: 'address', + name: 'identity', + type: 'address' + }, + { + internalType: 'bytes32', + name: 'delegateType', + type: 'bytes32' + }, + { + internalType: 'address', + name: 'delegate', + type: 'address' + }, + { + internalType: 'uint256', + name: 'revokeDeltaTime', + type: 'uint256' + }, + { + internalType: 'bool', + name: 'compromised', + type: 'bool' + } + ], + name: 'revokeDelegate', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: 'identity', + type: 'address' + }, + { + internalType: 'uint8', + name: 'sigV', + type: 'uint8' + }, + { + internalType: 'bytes32', + name: 'sigR', + type: 'bytes32' + }, + { + internalType: 'bytes32', + name: 'sigS', + type: 'bytes32' + }, + { + internalType: 'bytes32', + name: 'delegateType', + type: 'bytes32' + }, + { + internalType: 'address', + name: 'delegate', + type: 'address' + }, + { + internalType: 'uint256', + name: 'revokeDeltaTime', + type: 'uint256' + }, + { + internalType: 'bool', + name: 'compromised', + type: 'bool' + } + ], + name: 'revokeDelegateSigned', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, { inputs: [ { @@ -421,5 +856,34 @@ export const LAC_DIDREGISTRY_RECOVERABLE_ABI = [ outputs: [], stateMutability: 'nonpayable', type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: 'identity', + type: 'address' + }, + { + internalType: 'bytes32', + name: 'delegateType', + type: 'bytes32' + }, + { + internalType: 'address', + name: 'delegate', + type: 'address' + } + ], + name: 'validDelegate', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool' + } + ], + stateMutability: 'view', + type: 'function' } ]; diff --git a/src/constants/errorMessages.ts b/src/constants/errorMessages.ts index f27164e..1134a56 100644 --- a/src/constants/errorMessages.ts +++ b/src/constants/errorMessages.ts @@ -39,7 +39,12 @@ export enum ErrorsMessages { X509_INVALID_COMMON_NAME = 'Invalid x509 CN name', ATTRIBUTE_VALUE_ERROR = 'Attribute value is not an address', // eslint-disable-next-line max-len - LACCHAIN_CONTRACT_TRANSACTION_ERROR = 'There was an error, there may be an issue with the params you are sending' + LACCHAIN_CONTRACT_TRANSACTION_ERROR = 'There was an error, there may be an issue with the params you are sending', + // eslint-disable-next-line max-len + UNEXPECTED_RESPONSE_IN_SUCCESSFUL_TRANSACTION_ERROR = 'Transaction was successfully completed but received an unexpected response', + UNSUPPORTED_CHAIN_ID_IN_DID = 'Unsupported chainId was found in DID', + UNSUPPORTED_DID_TYPE = 'Unsupported DID type', + UNSUPPORTED_DID_VERSION = 'Unsupported DID version' } export const Errors = { diff --git a/src/controllers/did-lac1/attribute.controller.ts b/src/controllers/did-lac1/attribute.controller.ts index 9f5639f..7664e10 100644 --- a/src/controllers/did-lac1/attribute.controller.ts +++ b/src/controllers/did-lac1/attribute.controller.ts @@ -3,14 +3,15 @@ import { Post, BadRequestError, Body, - UploadedFile + UploadedFile, + Delete } from 'routing-controllers'; import { Service } from 'typedi'; import { ErrorsMessages } from '../../constants/errorMessages'; import { AccountIdAttributeDTO, NewAccountIdAttributeDTO -} from '../../dto/did-lac/addAttributeDTO'; +} from '../../dto/did-lac/attributeDTO'; import { DidLac1Service } from '@services/did-lac/didLac1.service'; @JsonController('/did/lac1/attribute') @@ -35,6 +36,23 @@ export class DidLac1AttributeController { } } + @Delete('/revoke/jwk-from-x509certificate') + async revokeAttributeFromX509Certificate( + @Body({ validate: true }) formData: any, + @UploadedFile('x509Cert') x509Cert: Express.Multer.File + ) { + try { + return this.didService.rawRevokeAttributeFromX509Certificate( + formData, + x509Cert + ); + } catch (error: any) { + throw new BadRequestError( + error.detail ?? error.message ?? ErrorsMessages.INTERNAL_SERVER_ERROR + ); + } + } + @Post('/add/delegate/address') async addEthereumAccountAttribute( @Body({ validate: true }) attribute: AccountIdAttributeDTO diff --git a/src/controllers/did-web-lac/attribute.controller.ts b/src/controllers/did-web-lac/attribute.controller.ts index 4e78bec..506e834 100644 --- a/src/controllers/did-web-lac/attribute.controller.ts +++ b/src/controllers/did-web-lac/attribute.controller.ts @@ -10,7 +10,7 @@ import { ErrorsMessages } from '../../constants/errorMessages'; import { EcJwkAttributeDTO, RsaJwkAttributeDTO -} from '../../dto/did-lac/addAttributeDTO'; +} from '../../dto/did-lac/attributeDTO'; @JsonController('/did-web-lac/attribute') @Service() diff --git a/src/dto/did-lac/addAttributeDTO.ts b/src/dto/did-lac/attributeDTO.ts similarity index 69% rename from src/dto/did-lac/addAttributeDTO.ts rename to src/dto/did-lac/attributeDTO.ts index 0549a53..8bd3ae0 100644 --- a/src/dto/did-lac/addAttributeDTO.ts +++ b/src/dto/did-lac/attributeDTO.ts @@ -1,6 +1,7 @@ import { Type } from 'class-transformer'; import { IsArray, + IsBoolean, IsDefined, IsNumber, IsObject, @@ -74,3 +75,29 @@ export class NewAccountIdAttributeDTO { @IsString() relation!: string; } + +export class RevokeAttributeDTO { + @IsString() + did!: string; + @IsString() + relation!: string; + @IsNumber() + backwardRevocationDays!: number; + @IsDefined() + @IsBoolean() + compromised!: boolean; +} + +// export class RevokeAttributeValidator { +// @IsString() +// did!: string; +// @IsDefined() +// @Type(() => X509Certificate) +// x509!: X509Certificate; +// @IsString() +// relation!: string; +// @IsNumber() +// backwardRevocationDays!: number; +// @IsBoolean() +// compromised!: boolean; +// } diff --git a/src/index.ts b/src/index.ts index b421518..9903669 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,7 @@ export { export { DidService, did as DidType } from './services/interfaces/did.service'; export { Did as DidEntity } from './entities/did.entity'; export { Secp256k1 as Secp256k1Entity } from 'lacpass-key-manager'; -export * from './dto/did-lac/addAttributeDTO'; +export * from './dto/did-lac/attributeDTO'; export * from './dto/did-lac/delegateDTO'; export * from './interfaces/did-lacchain/did-lacchain.interface'; export * from './interfaces/did-lacchain/did-lacchain-response.interface'; diff --git a/src/interfaces/did-lacchain/did-lacchain.interface.ts b/src/interfaces/did-lacchain/did-lacchain.interface.ts index e88f110..418397e 100644 --- a/src/interfaces/did-lacchain/did-lacchain.interface.ts +++ b/src/interfaces/did-lacchain/did-lacchain.interface.ts @@ -48,12 +48,28 @@ export interface IJwkAttribute { relation: string; } +export interface IJwkRevokeAttribute { + did: string; + jwk: any; + backwardRevocationDays: number; + relation: string; + compromised: boolean; +} + export interface IJwkAttribute1 { did: string; jwk: any; exp: number; relation: string; } + +export interface IJwkRevokeAttribute1 { + did: string; + jwk: any; + relation: string; + compromised: boolean; + revokeDeltaTime: number; +} export interface IJwkRsaAttribute { did: string; rsaJwk: RsaJwk; @@ -87,3 +103,37 @@ export interface IX509Attribute { validDays: number; relation: string; } + +export interface IX509RevokeAttribute { + did: string; + x509: X509Certificate; + relation: string; + backwardRevocationDays: number; + compromised: boolean; +} + +export interface IGenericRevokeAttributeFields { + did: string; + relation: string; + algorithm: string; + encodingMethod: string; + value: any; + revokeDeltaTime: number; // in seconds + compromised: boolean; +} + +export interface CommonAttributeFields { + did: string; + relation: string; + algorithm: string; + encodingMethod: string; +} + +export interface IRevokeAttribute { + identityAddress: string; + name: string; + value: string; + revokeDeltaTime: number; + compromised: boolean; + didRegistryAddress: string; +} diff --git a/src/interfaces/did/did.generics.ts b/src/interfaces/did/did.generics.ts new file mode 100644 index 0000000..535f27d --- /dev/null +++ b/src/interfaces/did/did.generics.ts @@ -0,0 +1,14 @@ +export interface DidSpec { + didMethod: string; + didType: string; + didVersion: string; + didRegistryAddress: string; +} + +export interface DidRegistryParams { + didMethod: string; + didType: string; + didVersion: string; + didRegistryAddress: string; + chainId: string; +} diff --git a/src/services/did-lac/did-registry.ts b/src/services/did-lac/did-registry.ts index 016c0ce..92efdc0 100644 --- a/src/services/did-lac/did-registry.ts +++ b/src/services/did-lac/did-registry.ts @@ -3,9 +3,19 @@ import { GasModelProvider, GasModelSigner } from '@lacchain/gas-model-provider'; // eslint-disable-next-line max-len import { LAC_DIDREGISTRY_RECOVERABLE_ABI } from '../../constants/did-web/lac/lacDidRegistryAbi'; import { log4TSProvider } from '../../config'; +import { IRevokeAttribute } from 'src/interfaces/did-lacchain/did-lacchain.interface'; +import { + IEthereumTransactionResponse, + ITransaction +} from 'src/interfaces/ethereum/transaction'; +import { Interface, keccak256, toUtf8Bytes } from 'ethers/lib/utils'; +import { LacchainLib } from './lacchain/lacchain-ethers'; +import { BadRequestError } from 'routing-controllers'; +import { ErrorsMessages } from '../../constants/errorMessages'; export default class DIDRegistryContractInterface { private registry: ethers.Contract; + private readonly lacchainLib: LacchainLib; log = log4TSProvider.getLogger('didRegistryService'); constructor( didRegistryAddress: string, @@ -13,6 +23,8 @@ export default class DIDRegistryContractInterface { privateKey: string | undefined, nodeAddress: string ) { + // TODO: factor providers in such way that did service is independent + this.lacchainLib = new LacchainLib(nodeAddress, rpcUrl); if (!privateKey) { // just for reading privateKey = Wallet.createRandom().privateKey; @@ -38,4 +50,72 @@ export default class DIDRegistryContractInterface { async lookupController(address: string) { return this.registry.identityController(address); } + + async expirationAttribute( + identity: string, + attributeNameHash: string, + attributeValueHash: string + ): Promise { + const v = await this.registry.expirationAttribute( + identity, + attributeNameHash, + attributeValueHash + ); + return parseInt(ethers.utils.formatUnits(v, 0)); + } + + async revokeAttribute( + attribute: IRevokeAttribute + ): Promise { + const { name, identityAddress, didRegistryAddress } = attribute; + const expirationTime = await this.expirationAttribute( + identityAddress, + keccak256(toUtf8Bytes(name)), + keccak256(attribute.value) + ); + if (expirationTime < Math.floor(Date.now() / 1000)) { + // eslint-disable-next-line max-len + this.log.info( + // eslint-disable-next-line max-len + 'The attribute/delegate has already expired or was already revoked ... Revoking again' + ); + } + + const methodName = 'revokeAttribute'; + const setAttributeMethodSignature = [ + `function ${methodName}(address,bytes,bytes,uint256,bool) external` + ]; + const setAttributeInterface = new Interface(setAttributeMethodSignature); + const encodedData = setAttributeInterface.encodeFunctionData(methodName, [ + identityAddress, + toUtf8Bytes(name), + attribute.value, + attribute.revokeDeltaTime, + attribute.compromised + ]); + this.log.info('Performing against did registry: ', didRegistryAddress); + // TODO: fix pointing to the did reg of the did + const didControllerAddress = await this.lookupController(identityAddress); + const tx: ITransaction = { + from: didControllerAddress, + to: didRegistryAddress, + data: encodedData + }; + const txResponse = await this.lacchainLib.signAndSend(tx); + const receipt = await this.registry.provider.getTransactionReceipt( + txResponse.txHash + ); + const iFace = new ethers.utils.Interface(LAC_DIDREGISTRY_RECOVERABLE_ABI); + for (const log of receipt.logs) { + if (log.address == didRegistryAddress) { + const parsed = iFace.parseLog(log); + if (parsed.name == 'DIDAttributeChanged') { + return txResponse; + } + } + } + throw new BadRequestError( + ErrorsMessages.UNEXPECTED_RESPONSE_IN_SUCCESSFUL_TRANSACTION_ERROR + ); // may happen if contract is updated where event structures are changed + } } diff --git a/src/services/did-lac/did.service.ts b/src/services/did-lac/did.service.ts index cc17acc..3542216 100644 --- a/src/services/did-lac/did.service.ts +++ b/src/services/did-lac/did.service.ts @@ -5,29 +5,34 @@ import { KeyManagerService } from '../external/key-manager.service'; import { EntityMapper } from '@clients/mapper/entityMapper.service'; import { CHAIN_ID, - getChainId, log4TSProvider, - resolveDidRegistryAddress, getRpcUrl, - getNodeAddress + getNodeAddress, + resolveChainRegistry } from '../../config'; import { Interface, isAddress, keccak256, toUtf8Bytes } from 'ethers/lib/utils'; import DIDRegistryContractInterface from './did-registry'; -import { BadRequestError } from 'routing-controllers'; +import { BadRequestError, InternalServerError } from 'routing-controllers'; import { DidLacService, didLacAttributes } from './interfaces/did-lac.service'; import { ErrorsMessages } from '../../constants/errorMessages'; import { + CommonAttributeFields, IAccountIdAttribute, IAddAccountIdAttribute, IGenericAttributeFields, + IGenericRevokeAttributeFields, IJwkAttribute, IJwkAttribute1, IJwkEcAttribute, + IJwkRevokeAttribute, + IJwkRevokeAttribute1, IJwkRsaAttribute, INewAccountIdAttribute, INewOnchainDelegate, IOnchainDelegate, - IX509Attribute + IRevokeAttribute, + IX509Attribute, + IX509RevokeAttribute } from 'src/interfaces/did-lacchain/did-lacchain.interface'; import { IEthereumTransactionResponse, @@ -44,6 +49,13 @@ import { import { X509Certificate } from 'crypto'; // eslint-disable-next-line max-len import { INewDelegateResponse } from 'src/interfaces/did-lacchain/did-lacchain-response.interface'; +import { RevokeAttributeDTO } from '../../dto/did-lac/attributeDTO'; +import { validateOrReject } from 'class-validator'; +import { + CHAIN_REGISTRY, + SUPPORTED_DID_TYPES +} from '../../constants/did-web/lac/didRegistryAddresses'; +import { DidRegistryParams } from 'src/interfaces/did/did.generics'; @Service() export abstract class DidService implements DidLacService { @@ -53,36 +65,34 @@ export abstract class DidService implements DidLacService { ); private readonly hex = require('base-x')('0123456789abcdef'); - private readonly didEncodingVersion = '0001'; // constant for encoding - // eslint-disable-next-line max-len - private readonly didType = '0001'; // constant + private readonly didEncodingVersion: string; + private readonly didType: string; private readonly chainId: string; private readonly didRegistryAddress: string; + + private readonly defaultDidRegistryParams: DidRegistryParams; + private readonly rpcUrl: string; private readonly nodeAddress: string; - private readonly didIdentifier: string; - - private didRegistryContractInterface: DIDRegistryContractInterface; + private readonly didMethod: string; private readonly lacchainLib: LacchainLib; log = log4TSProvider.getLogger('didService'); private keyManagerService: KeyManagerService; - constructor(didIdentifier: string) { + + constructor(didMethod: string) { this.keyManagerService = new KeyManagerService(); - this.chainId = getChainId(); - this.didRegistryAddress = resolveDidRegistryAddress(); - this.didIdentifier = didIdentifier; + this.defaultDidRegistryParams = resolveChainRegistry(); + this.didType = this.defaultDidRegistryParams.didType; + this.didEncodingVersion = this.defaultDidRegistryParams.didVersion; + this.chainId = this.defaultDidRegistryParams.chainId; + this.didRegistryAddress = this.defaultDidRegistryParams.didRegistryAddress; + this.didMethod = didMethod; this.rpcUrl = getRpcUrl(); this.nodeAddress = getNodeAddress(); - this.didRegistryContractInterface = new DIDRegistryContractInterface( - this.didRegistryAddress, // base did registry - this.rpcUrl, - undefined, - this.nodeAddress - ); // TODO: factor providers in such way that did service is independent this.lacchainLib = new LacchainLib(this.nodeAddress, this.rpcUrl); } @@ -132,8 +142,7 @@ export abstract class DidService implements DidLacService { value, exp ]); - const didControllerAddress = - await this.didRegistryContractInterface.lookupController(address); + const didControllerAddress = await this.lookupController(did); const tx: ITransaction = { from: didControllerAddress, to: didRegistryAddress, @@ -177,7 +186,9 @@ export abstract class DidService implements DidLacService { return this._addJwkAttributeWithDays(jwkAttribute); } - async addJwkFromPem(x509Attribute: IX509Attribute): Promise { + async addJwkFromPem( + x509Attribute: IX509Attribute + ): Promise { const { x509, did, relation } = x509Attribute; // TODO: verify key usage // console.log('key usage: ' + x509.keyUsage); @@ -223,10 +234,37 @@ export abstract class DidService implements DidLacService { return this._addJwkAttribute(jwkAttr); } + async revokeJwkFromPem( + x509Attribute: IX509RevokeAttribute + ): Promise { + const { x509, did, relation, backwardRevocationDays, compromised } = + x509Attribute; + const serialNumber = x509.serialNumber; + if (serialNumber.length <= 0) { + throw new BadRequestError(ErrorsMessages.INVALID_X509_SERIAL_NUMBER); + } + const subject = x509.subject; + if (subject.length <= 0) { + throw new BadRequestError(ErrorsMessages.INVALID_X509_SUBJECT); + } + const revokeDeltaTime = Math.floor(backwardRevocationDays * 86400); + const pubKey = x509.publicKey; + const jwk = pubKey.export({ format: 'jwk' }); + const extendedJwk = { ...jwk, x5c: [x509.raw.toString('base64')] }; + const jwkAttr: IJwkRevokeAttribute1 = { + did, + jwk: extendedJwk, + relation, + revokeDeltaTime, + compromised + }; + return this._revokeJwkAttribute(jwkAttr); + } + async rawAddAttributeFromX509Certificate( formData: any, x509Cert: Express.Multer.File - ): Promise { + ): Promise { const { did, relation } = this._validateAndExtractDidFromObject(formData); const x509 = new X509Certificate(x509Cert.buffer); return this.addJwkFromPem({ @@ -236,6 +274,22 @@ export abstract class DidService implements DidLacService { } as IX509Attribute); } + async rawRevokeAttributeFromX509Certificate( + formData: any, + x509Cert: Express.Multer.File + ): Promise { + const { did, relation, backwardRevocationDays, compromised } = + await this._validateAndExtractDidAndRevokeParams(formData); + const x509 = new X509Certificate(x509Cert.buffer); + return this.revokeJwkFromPem({ + x509, + did, + relation, + backwardRevocationDays, + compromised + } as IX509RevokeAttribute); + } + private async _addJwkAttributeWithDays( jwkAttribute: IJwkAttribute ): Promise { @@ -251,7 +305,26 @@ export abstract class DidService implements DidLacService { return this._addJwkAttribute(mappedJwkAttribute); } - private async _addJwkAttribute(jwkAttribute: IJwkAttribute1): Promise { + private async _revokeJwkAttributeWithDays( + jwkAttribute: IJwkRevokeAttribute + ): Promise { + const backwardRevocationDays = jwkAttribute.backwardRevocationDays; + const revokeDeltaTime = + Math.floor(Date.now() / 1000) + 86400 * backwardRevocationDays; + const { did, jwk, relation, compromised } = jwkAttribute; + const mappedJwkAttribute: IJwkRevokeAttribute1 = { + did, + jwk, + relation, + revokeDeltaTime, + compromised + }; + return this._revokeJwkAttribute(mappedJwkAttribute); + } + + private async _addJwkAttribute( + jwkAttribute: IJwkAttribute1 + ): Promise { const attribute: IGenericAttributeFields = { did: jwkAttribute.did, exp: jwkAttribute.exp, @@ -263,6 +336,21 @@ export abstract class DidService implements DidLacService { return this._addAttribute(attribute); } + private async _revokeJwkAttribute( + jwkAttribute: IJwkRevokeAttribute1 + ): Promise { + const attribute: IGenericRevokeAttributeFields = { + did: jwkAttribute.did, + relation: jwkAttribute.relation, + algorithm: 'jwk', + encodingMethod: 'cbor', + value: encode(jwkAttribute.jwk), + revokeDeltaTime: jwkAttribute.revokeDeltaTime, + compromised: jwkAttribute.compromised + }; + return this._revokeAttribute(attribute); + } + async addNewEthereumAccountIdAttribute( newAccountIdAttribute: INewAccountIdAttribute ): Promise { @@ -321,7 +409,7 @@ export abstract class DidService implements DidLacService { private async _addAttribute( attribute: IGenericAttributeFields - ): Promise { + ): Promise { const { algorithm, encodingMethod, value } = attribute; if (!ATTRIBUTE_ENCODING_METHODS.get(encodingMethod)) { const message = ErrorsMessages.UNSUPPORTED_ATTRIBUTE_ENCODING_METHOD; @@ -358,8 +446,7 @@ export abstract class DidService implements DidLacService { value, attribute.exp ]); - const didControllerAddress = - await this.didRegistryContractInterface.lookupController(address); + const didControllerAddress = await this.lookupController(attribute.did); const tx: ITransaction = { from: didControllerAddress, to: didRegistryAddress, @@ -368,21 +455,81 @@ export abstract class DidService implements DidLacService { return this.lacchainLib.signAndSend(tx); } + private async _validateParamsAndGetCommonAttributeParams( + attribute: CommonAttributeFields + ): Promise<{ + name: string; + identityAddress: string; + didRegistryAddress: string; + }> { + const { algorithm, encodingMethod } = attribute; + if (!ATTRIBUTE_ENCODING_METHODS.get(encodingMethod)) { + const message = ErrorsMessages.UNSUPPORTED_ATTRIBUTE_ENCODING_METHOD; + this.log.info(message); + throw new BadRequestError(message); + } + const { address, didRegistryAddress, chainId } = this.decodeDid( + attribute.did + ); + if (chainId.toLowerCase() !== CHAIN_ID.toLowerCase()) { + const message = ErrorsMessages.UNSUPPORTED_CHAIN_ID; + this.log.info(message); + throw new BadRequestError(message); + } + + const { relation } = attribute; + if (!VM_RELATIONS.get(relation)) { + const message = ErrorsMessages.INVALID_VM_RELATION_TYPE; + this.log.info(message); + throw new BadRequestError(message); + } + const keyAttrDidController = attribute.did; + // asse/did/esecp256k1rm/hex + const name = `${relation}/${keyAttrDidController}/${algorithm}/${encodingMethod}`; + return { name, identityAddress: address, didRegistryAddress }; + } + + private async _validateRevokeParamsAndGetCommonAttributeParams( + attribute: IGenericRevokeAttributeFields + ): Promise<{ + name: string; + identityAddress: string; + didRegistryAddress: string; + }> { + return this._validateParamsAndGetCommonAttributeParams(attribute); + } + + private async _revokeAttribute( + attribute: IGenericRevokeAttributeFields + ): Promise { + const { name, identityAddress, didRegistryAddress } = + await this._validateRevokeParamsAndGetCommonAttributeParams(attribute); + + const revokeAttribute: IRevokeAttribute = { + identityAddress, + name, + value: attribute.value, + revokeDeltaTime: attribute.revokeDeltaTime, + compromised: attribute.compromised, + didRegistryAddress + }; + // get correct did interface + const didReg = this.findSupportedLacchainDidRegistry(attribute.did); + return didReg.revokeAttribute(revokeAttribute); + } + addAttribute(_did: string, _rsaPublicKey: string): Promise { throw new Error('Method not implemented.'); } async getController(did: string): Promise { - const { address, chainId } = this.decodeDid(did); - console.log(address, chainId); - console.log(CHAIN_ID); + const { chainId } = this.decodeDid(did); if (chainId.toLowerCase() !== CHAIN_ID.toLowerCase()) { const message = ErrorsMessages.UNSUPPORTED_CHAIN_ID; this.log.info(message); throw new BadRequestError(message); } - const didController = - await this.didRegistryContractInterface.lookupController(address); + const didController = await this.lookupController(did); return { controller: didController }; } @@ -395,7 +542,7 @@ export abstract class DidService implements DidLacService { const did = EntityMapper.mapTo(Did, {}); did.keyId = key.keyId; did.did = - this.didIdentifier + + this.didMethod + this.encode( this.didType, this.chainId, @@ -407,7 +554,7 @@ export abstract class DidService implements DidLacService { } decodeDid(did: string): didLacAttributes { - const trimmed = did.replace(this.didIdentifier, ''); + const trimmed = did.replace(this.didMethod, ''); const data = Buffer.from(this.base58.decode(trimmed)); const len = data.length; const encodedPayload = data.subarray(0, len - 4); @@ -418,16 +565,15 @@ export abstract class DidService implements DidLacService { this.log.info(message); throw new BadRequestError(message); } - const version = data.subarray(0, 2); - const didType = data.subarray(2, 4); - if (version.toString('hex') !== this.didEncodingVersion) { + const version = data.subarray(0, 2).toString('hex'); + const didType = data.subarray(2, 4).toString('hex'); + if (version !== this.didEncodingVersion) { // TODO handle better versioning const message = 'Unsupported encoding version'; this.log.info(message); throw new BadRequestError(message); } - - if (didType.toString('hex') !== this.didType) { + if (didType !== this.didType) { // TODO handle better versioning const message = 'Unsupported did type'; this.log.info(message); @@ -445,7 +591,14 @@ export abstract class DidService implements DidLacService { c = c.substring(1); } const chainId = '0x' + c; - return { address, didRegistryAddress, chainId }; + return { + address, + didMethod: this.didMethod, + didRegistryAddress, + chainId, + version, + didType + }; } // todo: validate parameters @@ -497,6 +650,32 @@ export abstract class DidService implements DidLacService { return JSON.parse(formData.data) as { did: string; relation: string }; } + private async _validateAndExtractDidAndRevokeParams(formData: any): Promise<{ + did: string; + relation: string; + compromised: boolean; + backwardRevocationDays: number; + }> { + if (!formData?.data) { + throw new BadRequestError(ErrorsMessages.BAD_REQUEST_ERROR); + } + if (Object.keys(formData?.data).length === 0) { + throw new BadRequestError(ErrorsMessages.BAD_REQUEST_ERROR); + } + const parsed = JSON.parse(formData.data); + const v = new RevokeAttributeDTO(); + v.did = parsed.did; + v.relation = parsed.relation; + v.compromised = parsed.compromised; + v.backwardRevocationDays = parsed.backwardRevocationDays; + try { + await validateOrReject(v); + } catch (err: any) { + throw new BadRequestError(err); + } + return v; + } + private _validateX509CertificateSubjectField(subject: string) { const orgRegex = /O=.+/i; const foundOrg = subject.search(orgRegex); @@ -520,4 +699,42 @@ export abstract class DidService implements DidLacService { const buffStr = '0x' + Buffer.from(str).slice(0, 32).toString('hex'); return buffStr + '0'.repeat(66 - buffStr.length); } + + async lookupController(did: string): Promise { + // get did registry interface to interact with + const registry = this.findSupportedLacchainDidRegistry(did); + const identity = this.decodeDid(did).address; + return registry.lookupController(identity); + } + + /** + * Verifies that the passed did is supported by checking the CHAIN_ID. + * Returns an instance of a supported verification otherwise throws an error. + * DID TYPE and DID Version + * @param {string} did + * @return {DidRegistryContractInterface} + */ + findSupportedLacchainDidRegistry(did: string): DIDRegistryContractInterface { + const { chainId, didType, version, didRegistryAddress } = + this.decodeDid(did); + const foundDidRegistry = CHAIN_REGISTRY.get(chainId); + if (!foundDidRegistry || this.chainId !== chainId) { + // to improve chainId as an array + throw new BadRequestError(ErrorsMessages.UNSUPPORTED_CHAIN_ID_IN_DID); + } + const typeDetails = SUPPORTED_DID_TYPES.get(didType); + if (!typeDetails) { + throw new InternalServerError(ErrorsMessages.UNSUPPORTED_DID_TYPE); + } + const isVersion = typeDetails.get(version); + if (!isVersion) { + throw new BadRequestError(ErrorsMessages.UNSUPPORTED_DID_VERSION); + } + return new DIDRegistryContractInterface( + didRegistryAddress, + this.rpcUrl, + undefined, + this.nodeAddress + ); + } } diff --git a/src/services/did-lac/interfaces/did-lac.service.ts b/src/services/did-lac/interfaces/did-lac.service.ts index a34fa2d..69b78d4 100644 --- a/src/services/did-lac/interfaces/did-lac.service.ts +++ b/src/services/did-lac/interfaces/did-lac.service.ts @@ -2,17 +2,20 @@ import { IJwkEcAttribute, IJwkRsaAttribute, INewAccountIdAttribute, - INewOnchainDelegate, - IX509Attribute + INewOnchainDelegate } from 'src/interfaces/did-lacchain/did-lacchain.interface'; import { DidService } from '../../interfaces/did.service'; // eslint-disable-next-line max-len import { INewDelegateResponse } from 'src/interfaces/did-lacchain/did-lacchain-response.interface'; +import { IEthereumTransactionResponse } from 'src/interfaces/ethereum/transaction'; export type didLacAttributes = { address: string; didRegistryAddress: string; chainId: string; + version: string; + didType: string; + didMethod: string; }; export interface DidLacService extends DidService { addAttribute(did: string, rsaPublicKey: string): Promise; @@ -29,5 +32,9 @@ export interface DidLacService extends DidService { rawAddAttributeFromX509Certificate( formData: any, x509Cert: Express.Multer.File - ): Promise; + ): Promise; + rawRevokeAttributeFromX509Certificate( + formData: any, + x509Cert: Express.Multer.File + ): Promise; }