From fbc1e4f7c22c89a461346b69376ffc6309595414 Mon Sep 17 00:00:00 2001 From: eum602 Date: Sat, 7 Oct 2023 19:30:04 -0500 Subject: [PATCH 1/7] add p256 signing capability Signed-off-by: eum602 --- .example.env | 1 + .example.env.dev | 1 + package.json | 4 +- src/config/index.ts | 1 + src/services/did/did.document.service.ts | 2 +- .../key-manager/key-manager.service.ts | 64 +++++- .../verifiable.credentials.service.ts | 215 +++++++++++++----- .../verification.registry.ts | 4 +- yarn.lock | 28 +-- 9 files changed, 234 insertions(+), 86 deletions(-) diff --git a/.example.env b/.example.env index 84a8238..5b6b48d 100644 --- a/.example.env +++ b/.example.env @@ -73,6 +73,7 @@ EMAIL_TRANSPORTER = AWS # KEY_MANAGER_DID_JWT = /did-jwt/generate # KEY_MANAGER_DID_COMM_ENCRYPT = /didcomm/x25519/encrypt # KEY_MANAGER_SECP256K1_PLAIN_MESSAGE_SIGN = /secp256k1/sign/plain-message +# KEY_MANAGER_P256_PLAIN_MESSAGE_SIGN = /secp256k1/sign/plain-message # KEY_MANAGER_SECP256K1_SIGN_LACCHAIN_TRANSACTION=/secp256k1/sign/lacchain-tx diff --git a/.example.env.dev b/.example.env.dev index 1f7f3ed..a5dfdbd 100644 --- a/.example.env.dev +++ b/.example.env.dev @@ -72,6 +72,7 @@ EMAIL_TRANSPORTER = AWS # KEY_MANAGER_DID_JWT = /did-jwt/generate # KEY_MANAGER_DID_COMM_ENCRYPT = /didcomm/x25519/encrypt # KEY_MANAGER_SECP256K1_PLAIN_MESSAGE_SIGN = /secp256k1/sign/plain-message +# KEY_MANAGER_P256_PLAIN_MESSAGE_SIGN = /secp256k1/sign/plain-message # KEY_MANAGER_SECP256K1_SIGN_LACCHAIN_TRANSACTION=/secp256k1/sign/lacchain-tx diff --git a/package.json b/package.json index ac17606..d9c5551 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lacpass-client", - "version": "0.0.7", + "version": "0.0.8", "description": "Rest api for lacpass Client", "license": "MIT", "scripts": { @@ -81,7 +81,7 @@ "form-data": "^4.0.0", "helmet": "^5.0.2", "jsonwebtoken": "^9.0.0", - "lacchain-trust": "^0.0.2", + "lacchain-trust": "^0.0.3", "morgan": "^1.10.0", "multer": "^1.4.4", "nodemailer": "^6.7.3", diff --git a/src/config/index.ts b/src/config/index.ts index 55d0a0d..f9dff62 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -174,6 +174,7 @@ export const { KEY_MANAGER_DID_JWT, KEY_MANAGER_DID_COMM_ENCRYPT, KEY_MANAGER_SECP256K1_PLAIN_MESSAGE_SIGN, + KEY_MANAGER_P256_PLAIN_MESSAGE_SIGN, KEY_MANAGER_SECP256K1_SIGN_LACCHAIN_TRANSACTION, SECURE_RELAY_MESSAGE_DELIVERER_BASE_URL, SECURE_RELAY_MESSAGE_DELIVERER_SEND diff --git a/src/services/did/did.document.service.ts b/src/services/did/did.document.service.ts index 29e6f0e..60398b8 100644 --- a/src/services/did/did.document.service.ts +++ b/src/services/did/did.document.service.ts @@ -67,7 +67,7 @@ export class DidDocumentService { static filterSecp256k1PublicKeysFromJwkAssertionKeys( doc: any, algorithm: string, - curve: 'secp256k1' + curve: 'secp256k1' | 'P-256' ): { id: string; publicKeyBuffer: Buffer }[] | undefined { const keys = doc.assertionMethod.filter( (ka: { crv: string; publicKeyJwk: any; type: string }) => diff --git a/src/services/external/key-manager/key-manager.service.ts b/src/services/external/key-manager/key-manager.service.ts index 68c94c9..d412c8a 100644 --- a/src/services/external/key-manager/key-manager.service.ts +++ b/src/services/external/key-manager/key-manager.service.ts @@ -6,7 +6,8 @@ import { KEY_MANAGER_BASE_URL, log4TSProvider, KEY_MANAGER_SECP256K1_PLAIN_MESSAGE_SIGN, - KEY_MANAGER_SECP256K1_SIGN_LACCHAIN_TRANSACTION + KEY_MANAGER_SECP256K1_SIGN_LACCHAIN_TRANSACTION, + KEY_MANAGER_P256_PLAIN_MESSAGE_SIGN } from '../../../config'; import { Service } from 'typedi'; import { ErrorsMessages } from '../../../constants/errorMessages'; @@ -16,11 +17,13 @@ import { IDidCommService, IDidCommToEncryptData, ISignPlainMessageByAddress, - ISecp256k1SignatureMessageResponse, + ISignPlainMessageByCompressedPublicKey, + IECDSASignatureMessageResponse, Secp256k1GenericSignerService, ISignedTransaction, ILacchainTransaction, - Secp256k1SignLacchainTransactionService + Secp256k1SignLacchainTransactionService, + P256SignerServiceDb } from 'lacchain-key-manager'; @Service() @@ -31,21 +34,25 @@ export class KeyManagerService { public createDidJwt: (didJwt: IDidJwt) => Promise; public didCommEncrypt: (args: IDidCommToEncryptData) => Promise; - public secpSignPlainMessage: ( + public secp256k1SignPlainMessage: ( message: ISignPlainMessageByAddress - ) => Promise; + ) => Promise; + public p256SignPlainMessage: ( + message: ISignPlainMessageByCompressedPublicKey + ) => Promise; public signLacchainTransaction: ( lacchainTransaction: ILacchainTransaction ) => Promise; // eslint-disable-next-line max-len private secp256k1SignLacchainTransactionService: Secp256k1SignLacchainTransactionService | null; + private p256SignPlainMessageService: P256SignerServiceDb | null; log = log4TSProvider.getLogger('IdentityManagerService'); constructor() { if (IS_CLIENT_DEPENDENT_SERVICE !== 'true') { this.log.info('Configuring library usage for key manager service'); this.createDidJwt = this.createDidJwtByLib; this.didCommEncrypt = this.didCommEncryptByLib; - this.secpSignPlainMessage = this.secpSignPlainMessageByLib; + this.secp256k1SignPlainMessage = this.secp256k1SignPlainMessageByLib; const S = require('lacchain-key-manager').DidJwtDbService; this.didJwtService = new S(); @@ -60,18 +67,26 @@ export class KeyManagerService { const V = require('lacchain-key-manager').Secp256k1SignLacchainTransactionServiceDb; this.secp256k1SignLacchainTransactionService = new V(); + + this.p256SignPlainMessage = this.p256SignPlainMessageByLib; + const W = require('lacchain-key-manager').P256SignerServiceDb; + this.p256SignPlainMessageService = new W(); } else { this.log.info('Configuring key manager as external service connection'); this.didJwtService = null; this.createDidJwt = this.createDidJwtByExternalService; this.didCommEncrypt = this.didCommEncryptByExternalService; this.didCommEncryptService = null; - this.secpSignPlainMessage = this.secpSignPlainMessageByExternalService; + this.secp256k1SignPlainMessage = + this.secp256k1SignPlainMessageByExternalService; this.secp256k1GenericSignerService = null; this.secp256k1SignLacchainTransactionService = null; this.signLacchainTransaction = this.signLacchainTransactionByExternalService; + + this.p256SignPlainMessageService = null; + this.p256SignPlainMessage = this.p256SignPlainMessageByExternalService; } } private async createDidJwtByLib(didJwt: IDidJwt): Promise { @@ -82,12 +97,18 @@ export class KeyManagerService { return (await this.didCommEncryptService?.encrypt(args)) as any; } - private async secpSignPlainMessageByLib( + private async secp256k1SignPlainMessageByLib( message: ISignPlainMessageByAddress - ): Promise { + ): Promise { return await this.secp256k1GenericSignerService?.signPlainMessage(message); } + private async p256SignPlainMessageByLib( + message: ISignPlainMessageByCompressedPublicKey + ): Promise { + return await this.p256SignPlainMessageService?.signPlainMessage(message); + } + async signLacchainTransactionByLib( lacchainTransaction: ILacchainTransaction ): Promise { @@ -138,9 +159,9 @@ export class KeyManagerService { return (await result.json()) as any; } - private async secpSignPlainMessageByExternalService( + private async secp256k1SignPlainMessageByExternalService( message: ISignPlainMessageByAddress - ): Promise { + ): Promise { const result = await fetch( `${KEY_MANAGER_BASE_URL}${KEY_MANAGER_SECP256K1_PLAIN_MESSAGE_SIGN}`, { @@ -159,6 +180,27 @@ export class KeyManagerService { return (await result.json()) as any; } + private async p256SignPlainMessageByExternalService( + message: ISignPlainMessageByCompressedPublicKey + ): Promise { + const result = await fetch( + `${KEY_MANAGER_BASE_URL}${KEY_MANAGER_P256_PLAIN_MESSAGE_SIGN}`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(message) + } + ); + console.log('status', result.status); + if (result.status !== 200) { + console.log(await result.text()); + throw new InternalServerError(ErrorsMessages.PLAIN_MESSAGE_SIGNING_ERROR); + } + return (await result.json()) as any; + } + async signLacchainTransactionByExternalService( lacchainTransaction: ILacchainTransaction ): Promise { diff --git a/src/services/verifiable-credentials/verifiable.credentials.service.ts b/src/services/verifiable-credentials/verifiable.credentials.service.ts index acbc465..5df4611 100644 --- a/src/services/verifiable-credentials/verifiable.credentials.service.ts +++ b/src/services/verifiable-credentials/verifiable.credentials.service.ts @@ -34,7 +34,10 @@ import { import { validateOrReject } from 'class-validator'; import canonicalize from 'canonicalize'; import { KeyManagerService } from '@services/external/key-manager/key-manager.service'; -import { ISignPlainMessageByAddress } from 'lacchain-key-manager'; +import { + ISignPlainMessageByAddress, + ISignPlainMessageByCompressedPublicKey +} from 'lacchain-key-manager'; import { MEDICINAL_PRODUCT_NAMES } from '@constants/ddcc.medicinal.code.mapper'; import { IAttachment, @@ -49,6 +52,8 @@ import { VerificationRegistry } from './verification.registry'; import { IEthereumTransactionResponse } from 'src/interfaces/ethereum/transaction'; import { ProofOfExistenceMode } from '@constants/poe'; +type keyType = 'secp256k1' | 'P-256'; +type assertionPublicKeyType = { hexPubKey: string; keyId: string }; @Service() export class VerifiableCredentialService { private readonly base58 = require('base-x')( @@ -68,10 +73,10 @@ export class VerifiableCredentialService { string, string >(); - private assertionPublicKeys: Map< + private assertionPublicKeys: Map = new Map< string, - { hexPubKey: string; keyId: string } - > = new Map(); + assertionPublicKeyType + >(); private didServiceLac1: DidServiceLac1; private keyManager: KeyManagerService; private verificationRegistryService: VerificationRegistry; @@ -291,10 +296,17 @@ export class VerifiableCredentialService { return Buffer.from(buffPubKey).toString('hex'); } + /** + * Gets Jwk assertion keys from DIDDocument and returns the one that matches + * with the provided params + * @param {string} did - Decentralized identifier + * @param {keyType} type - Type of Key: e.g. 'secp256k1', 'P-256', etc + * @return {Promise} - returned value + */ async getOrSetOrCreateAssertionPublicKeyFromDid( did: string, - type: 'secp256k1' - ): Promise<{ hexPubKey: string; keyId: string }> { + type: keyType + ): Promise { const assertionPublicKey = this.assertionPublicKeys.get(did); if (assertionPublicKey) { return assertionPublicKey; @@ -305,49 +317,23 @@ export class VerifiableCredentialService { DidDocumentService.filterSecp256k1PublicKeysFromJwkAssertionKeys( didDoc, assertionRelationshipSearchKeyword, - 'secp256k1' + type ); + let pk; if (foundAssertionPublicKeys) { - for (const assertionPublicKey of foundAssertionPublicKeys) { - // TODO: generalize find algorithm to fit with any kind of key. - // and apply to all methods - const hexPubKey = - '0x' + assertionPublicKey.publicKeyBuffer.toString('hex'); - const messageRequest: ISignPlainMessageByAddress = { - address: computeAddress(hexPubKey), - messageHash: - '0x' + crypto.createHash('sha256').update('Proof').digest('hex') - }; - try { - await this.keyManager.secpSignPlainMessage(messageRequest); - } catch (e: any) { - // TODO:check in "DEPENDENT SERVICE" way - if (e && e.message === 'Key not found') { - console.log('error was', e.message); - this.log.info( - 'secp256 private key related to', - hexPubKey, - ' assertion key was not found. Ignoring this key ...' - ); - continue; - } - this.log.info( - 'Unexpected error encountered from key manager, error was', - e - ); - throw new BadRequestError(ErrorsMessages.INTERNAL_SERVER_ERROR); - } - this.log.info('Selecting Assertion Public Key', hexPubKey); - const pk = { - hexPubKey, - keyId: assertionPublicKey.id - }; - this.assertionPublicKeys.set(did, pk); - return pk; + if (type === 'secp256k1') { + pk = await this.trySetForSecp256k1(foundAssertionPublicKeys); + } else if (type === 'P-256') { + pk = await this.trySetForP256(foundAssertionPublicKeys); } } - const validDays = 365; + if (pk) { + this.assertionPublicKeys.set(did, pk); + return pk; + } + + const validDays = 365 * 4; this.log.info( // eslint-disable-next-line max-len `Couldn't find "assertion" key with type ${assertionRelationshipSearchKeyword} for did ${did} ... creating one for ${validDays} days` @@ -356,7 +342,7 @@ export class VerifiableCredentialService { did, validDays, relation: 'asse', - jwkType: type + jwkType: type == 'P-256' ? 'secp256r1' : type }; const response = await this.didServiceLac1.addNewJwkAttribute(attribute); @@ -369,10 +355,11 @@ export class VerifiableCredentialService { DidDocumentService.filterSecp256k1PublicKeysFromJwkAssertionKeys( didDoc, assertionRelationshipSearchKeyword, - 'secp256k1' + type )?.find(el => { const hexPubKey = '0x' + el.publicKeyBuffer.toString('hex'); - return computeAddress(hexPubKey) === computeAddress(newAssertionKeyHex); + // eslint-disable-next-line max-len + return computeAddress(hexPubKey) === computeAddress(newAssertionKeyHex); // kust using as as way to compare them }); if (foundAssertionPublicKey) { const hexPubKey = Buffer.from( @@ -388,6 +375,118 @@ export class VerifiableCredentialService { throw new BadRequestError(ErrorsMessages.VM_NOT_FOUND); } + async trySetForSecp256k1( + assertionPublicKeys: { + id: string; + publicKeyBuffer: Buffer; + }[] + ): Promise { + for (const assertionPublicKey of assertionPublicKeys) { + const r = await this.verifyKeyForSecp256k1(assertionPublicKey); + if (!r) { + continue; + } + return r; + } + return false; + } + + async verifyKeyForSecp256k1(assertionPublicKey: { + id: string; + publicKeyBuffer: Buffer; + }): Promise { + // TODO: generalize find algorithm to fit with any kind of key. + // and apply to all methods + const hexPubKey = '0x' + assertionPublicKey.publicKeyBuffer.toString('hex'); + const messageRequest: ISignPlainMessageByAddress = { + address: computeAddress(hexPubKey), + message: '0x' + crypto.createHash('sha256').update('Proof').digest('hex') + }; + try { + await this.keyManager.secp256k1SignPlainMessage(messageRequest); + } catch (e: any) { + // TODO:check in "DEPENDENT SERVICE" way + if (e && e.message === 'Key not found') { + console.log('error was', e.message); + this.log.info( + 'secp256 private key related to', + hexPubKey, + ' assertion key was not found. Ignoring this key ...' + ); + return false; + } + this.log.info( + 'Unexpected error encountered from key manager, error was', + e + ); + throw new BadRequestError(ErrorsMessages.INTERNAL_SERVER_ERROR); + } + this.log.info('Selecting Assertion Public Key', hexPubKey); + const pk = { + hexPubKey, + keyId: assertionPublicKey.id + }; + // this.assertionPublicKeys.set(did, pk); + return pk; + } + + async trySetForP256( + assertionPublicKeys: { + id: string; + publicKeyBuffer: Buffer; + }[] + ): Promise { + for (const assertionPublicKey of assertionPublicKeys) { + const r = await this.verifyKeyForP256(assertionPublicKey); + if (!r) { + continue; + } + return r; + } + return false; + } + + async verifyKeyForP256(assertionPublicKey: { + id: string; + publicKeyBuffer: Buffer; + }): Promise { + // TODO: generalize find algorithm to fit with any kind of key. + // and apply to all methods + const hexPubKey = '0x' + assertionPublicKey.publicKeyBuffer.toString('hex'); + const messageRequest: ISignPlainMessageByCompressedPublicKey = { + compressedPublicKey: + // eslint-disable-next-line max-len + '0x02' + assertionPublicKey.publicKeyBuffer.toString('hex'), // TODO: set all pub keys with 0x02/0x04 prefixes + message: '0x' + crypto.createHash('sha256').update('Proof').digest('hex') + }; + try { + await this.keyManager.p256SignPlainMessage(messageRequest); + } catch (e: any) { + // TODO:check in "DEPENDENT SERVICE" way + if (e && e.message === 'Key not found') { + console.log('error was', e.message); + this.log.info( + 'secp256 private key related to', + hexPubKey, + ' assertion key was not found. Ignoring this key ...' + ); + return false; + } + this.log.info( + 'Unexpected error encountered from key manager, error was', + e + ); + throw new BadRequestError(ErrorsMessages.INTERNAL_SERVER_ERROR); + } + this.log.info('Selecting Assertion Public Key', hexPubKey); + const pk = { + hexPubKey, + keyId: assertionPublicKey.id + }; + // this.assertionPublicKeys.set(did, pk); + return pk; + } + async getAuthAddressFromDid(issuerDid: string): Promise { const buffAuthPublicKey = Buffer.from( await this.getOrSetAuthPublicKey(issuerDid) @@ -726,19 +825,23 @@ export class VerifiableCredentialService { credentialData: ICredential, issuerDid: string ): Promise { - const credentialHash = this.computeCredentialHash(credentialData); + const credentialHash = this.computeCredentialHash(credentialData); // TODO: !! const assertionKey = await this.getOrSetOrCreateAssertionPublicKeyFromDid( issuerDid, - 'secp256k1' + 'P-256' ); - const hexPubKey = assertionKey.hexPubKey.startsWith('0x') - ? assertionKey.hexPubKey - : '0x' + assertionKey.hexPubKey; - const messageRequest: ISignPlainMessageByAddress = { - address: computeAddress(hexPubKey), - messageHash: credentialHash + // invariant verification: + const pubKey = assertionKey.hexPubKey.replace('0x', ''); + if (!pubKey || pubKey.length !== 64) { + throw new InternalServerError(ErrorsMessages.INTERNAL_SERVER_ERROR); + } + + const p256CompressedPubKey = '0x' + pubKey; + const messageRequest: ISignPlainMessageByCompressedPublicKey = { + compressedPublicKey: p256CompressedPubKey, + message: credentialHash }; - const proofValueResponse = await this.keyManager.secpSignPlainMessage( + const proofValueResponse = await this.keyManager.p256SignPlainMessage( messageRequest ); // TODO: add onchain proof diff --git a/src/services/verifiable-credentials/verification.registry.ts b/src/services/verifiable-credentials/verification.registry.ts index 0e430c7..451be6e 100644 --- a/src/services/verifiable-credentials/verification.registry.ts +++ b/src/services/verifiable-credentials/verification.registry.ts @@ -155,10 +155,10 @@ export class VerificationRegistry { const messageRequest: ISignPlainMessageByAddress = { address: controllerAddressResponse.controller, - messageHash: typeDataHash + message: typeDataHash }; try { - const { signature } = await this.keyManager.secpSignPlainMessage( + const { signature } = await this.keyManager.secp256k1SignPlainMessage( messageRequest ); const sig = splitSignature(signature); diff --git a/yarn.lock b/yarn.lock index 8362358..2173a1b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5790,10 +5790,10 @@ koa@^2.8.2: type-is "^1.6.16" vary "^1.1.2" -lacchain-identity@^0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/lacchain-identity/-/lacchain-identity-0.0.2.tgz#f67082667b4793a3edd67a83c68c5c11902fb73a" - integrity sha512-Th72Swzc6vub3IeM9+u0w+scc98h7Xcqxp2QLK1Xa7pXuRHMWmmRJPFM1pAnj6qPj4l+pzUKUDoAevML2W+fZg== +lacchain-identity@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/lacchain-identity/-/lacchain-identity-0.0.3.tgz#8323aa6c75981f15aeb0b1919c0a43d39e12f4e1" + integrity sha512-4TJ7VGayF1ksi1xSqcMUg6+44vJyeeNu2u/X45l/MDDX1+R2Wr4dbRmA3f6LwmUEKi53+GL+KmuBtb8fyitaTA== dependencies: "@lacchain/gas-model-provider" "^1.0.1" aws-sdk "^2.1116.0" @@ -5812,7 +5812,7 @@ lacchain-identity@^0.0.2: helmet "^5.0.2" json-canonicalize "^1.0.6" jsonwebtoken "^9.0.0" - lacchain-key-manager "^0.0.2" + lacchain-key-manager "^0.0.3" morgan "^1.10.0" multer "^1.4.4" nodemailer "^6.7.3" @@ -5832,10 +5832,10 @@ lacchain-identity@^0.0.2: typescript-logging "^2.1.0" typescript-logging-log4ts-style "^2.1.0" -lacchain-key-manager@^0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/lacchain-key-manager/-/lacchain-key-manager-0.0.2.tgz#e9996ad66bdbf52e4db3148390a09dff630413b7" - integrity sha512-8qDeQUYHgZylcPQDkKc3N6eprVUlp0vCH+4GsH+iznZ6HlWhhdghMgEgYwSq1jlmM9L/jBos+m7m1iWBDWoQbg== +lacchain-key-manager@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/lacchain-key-manager/-/lacchain-key-manager-0.0.3.tgz#d2f577eb0849753522f8b2709f954ca97fb212e3" + integrity sha512-bCZqJ957lPg6WItbAX3BTaY7Ai5nY7Di5Eyyx0939qvedb/0tbvOu+EV8GsScfqyPmfGBhJ42PRnHRR0th5ZBA== dependencies: "@lacchain/gas-model-provider" "^1.0.1" DIDComm-js "git+https://github.com/decentralized-identity/DIDComm-js.git" @@ -5873,10 +5873,10 @@ lacchain-key-manager@^0.0.2: typescript-logging "^2.1.0" typescript-logging-log4ts-style "^2.1.0" -lacchain-trust@^0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/lacchain-trust/-/lacchain-trust-0.0.2.tgz#85eac993af82f195704b9d0fa8e34dc2b7d4ca6c" - integrity sha512-krSxYyaDLcShRS2nIS91Pnuf6PW4PGN81qeBp5X3N1HKRQD1EQ7L2aQtp1U3slQ05dkUpoIo4SVhi/oLUUaxwA== +lacchain-trust@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/lacchain-trust/-/lacchain-trust-0.0.3.tgz#086220e00849f81496b2965fa8e98d77e09c7044" + integrity sha512-TPuF6PbsIWc06iQ6X0iPRIq8/sxbdY8qSU68TOKDiYNAo3nl9xO8JJ+ab46dKlY8t+4sZ+OysSkUniq73LOg2Q== dependencies: "@lacchain/gas-model-provider" "^1.0.1" aws-sdk "^2.1116.0" @@ -5895,7 +5895,7 @@ lacchain-trust@^0.0.2: express-rate-limit "^6.3.0" helmet "^5.0.2" jsonwebtoken "^9.0.0" - lacchain-identity "^0.0.2" + lacchain-identity "^0.0.3" morgan "^1.10.0" multer "^1.4.4" nodemailer "^6.7.3" From 480f2020421df22be14f0bea028437d192784526 Mon Sep 17 00:00:00 2001 From: eum602 Date: Sat, 7 Oct 2023 23:32:22 -0500 Subject: [PATCH 2/7] feat: add ecdsa-jcs-2019 proof type Signed-off-by: eum602 --- package.json | 2 +- .../verifiable-credential/ddcc.credential.ts | 16 +++- .../verifiable.credentials.service.ts | 94 ++++++++++++++++--- yarn.lock | 28 +++--- 4 files changed, 111 insertions(+), 29 deletions(-) diff --git a/package.json b/package.json index d9c5551..93b5f3d 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "form-data": "^4.0.0", "helmet": "^5.0.2", "jsonwebtoken": "^9.0.0", - "lacchain-trust": "^0.0.3", + "lacchain-trust": "^0.0.4", "morgan": "^1.10.0", "multer": "^1.4.4", "nodemailer": "^6.7.3", diff --git a/src/interfaces/verifiable-credential/ddcc.credential.ts b/src/interfaces/verifiable-credential/ddcc.credential.ts index d6a165c..e42dcfd 100644 --- a/src/interfaces/verifiable-credential/ddcc.credential.ts +++ b/src/interfaces/verifiable-credential/ddcc.credential.ts @@ -68,8 +68,22 @@ export interface IType1Proof { proofValue: string; } +export interface IType2ProofConfig { + type: 'DataIntegrityProof'; + cryptosuite: 'ecdsa-jcs-2019'; + created: string; + proofPurpose: 'assertionMethod'; + verificationMethod: string; + domain?: string; +} +export interface IType2Proof extends IType2ProofConfig { + proofValue: string; +} + export type IDDCCVerifiableCredential = IDDCCCredential & { proof: IType1Proof; }; -export type IVerifiableCredential = ICredential & { proof: IType1Proof }; +export type IVerifiableCredential = ICredential & { + proof: IType1Proof | IType2Proof; +}; diff --git a/src/services/verifiable-credentials/verifiable.credentials.service.ts b/src/services/verifiable-credentials/verifiable.credentials.service.ts index 5df4611..f28d19e 100644 --- a/src/services/verifiable-credentials/verifiable.credentials.service.ts +++ b/src/services/verifiable-credentials/verifiable.credentials.service.ts @@ -12,6 +12,8 @@ import { IDDCCCredential, IDDCCVerifiableCredential, IType1Proof, + IType2Proof, + IType2ProofConfig, IVerifiableCredential } from 'src/interfaces/verifiable-credential/ddcc.credential'; import crypto from 'crypto'; @@ -238,7 +240,7 @@ export class VerifiableCredentialService { issuerDid: string, credentialData: ICredential ): Promise { - const credentialHash = this.computeCredentialHash(credentialData); + const credentialHash = this.computeRfc8785AndSha256(credentialData); let expiration = 0; if (credentialData && credentialData.expirationDate) { const d = new Date(credentialData.expirationDate).getTime(); @@ -336,7 +338,7 @@ export class VerifiableCredentialService { const validDays = 365 * 4; this.log.info( // eslint-disable-next-line max-len - `Couldn't find "assertion" key with type ${assertionRelationshipSearchKeyword} for did ${did} ... creating one for ${validDays} days` + `Couldn't find "assertion" key with type ${assertionRelationshipSearchKeyword} and key type ${type} for did ${did} ... creating one for ${validDays} days` ); const attribute: INewJwkAttribute = { did, @@ -421,7 +423,7 @@ export class VerifiableCredentialService { ); throw new BadRequestError(ErrorsMessages.INTERNAL_SERVER_ERROR); } - this.log.info('Selecting Assertion Public Key', hexPubKey); + this.log.info('Selecting secp256k1 Assertion Public Key', hexPubKey); const pk = { hexPubKey, keyId: assertionPublicKey.id @@ -478,7 +480,7 @@ export class VerifiableCredentialService { ); throw new BadRequestError(ErrorsMessages.INTERNAL_SERVER_ERROR); } - this.log.info('Selecting Assertion Public Key', hexPubKey); + this.log.info('Selecting P-256 Assertion Public Key', hexPubKey); const pk = { hexPubKey, keyId: assertionPublicKey.id @@ -802,22 +804,20 @@ export class VerifiableCredentialService { credential: ICredential, issuerDid: string ): Promise { - const proof = await this.getIType1ProofAssertionMethodTemplate( + const proof = await this.getIType2ProofAssertionMethodTemplate( credential, issuerDid ); - // TODO: add custom fields to proof return { ...credential, proof }; } - computeCredentialHash(credentialData: ICredential) { - const credentialDataString = canonicalize(credentialData); - if (!credentialDataString) { + computeRfc8785AndSha256(data: any) { + const canonizedData = canonicalize(data); + if (!canonizedData) { throw new BadRequestError(ErrorsMessages.CANONICALIZE_ERROR); } return ( - '0x' + - crypto.createHash('sha256').update(credentialDataString).digest('hex') + '0x' + crypto.createHash('sha256').update(canonizedData).digest('hex') ); } @@ -825,7 +825,7 @@ export class VerifiableCredentialService { credentialData: ICredential, issuerDid: string ): Promise { - const credentialHash = this.computeCredentialHash(credentialData); // TODO: !! + const credentialHash = this.computeRfc8785AndSha256(credentialData); // TODO: !! const assertionKey = await this.getOrSetOrCreateAssertionPublicKeyFromDid( issuerDid, 'P-256' @@ -836,7 +836,7 @@ export class VerifiableCredentialService { throw new InternalServerError(ErrorsMessages.INTERNAL_SERVER_ERROR); } - const p256CompressedPubKey = '0x' + pubKey; + const p256CompressedPubKey = '0x02' + pubKey; const messageRequest: ISignPlainMessageByCompressedPublicKey = { compressedPublicKey: p256CompressedPubKey, message: credentialHash @@ -856,6 +856,74 @@ export class VerifiableCredentialService { return type1Proof; } + async getIType2ProofAssertionMethodTemplate( + unsecuredDocument: ICredential, + issuerDid: string + ): Promise { + const canonicalDocumentHash = this.computeRfc8785AndSha256( + unsecuredDocument + ).replace('0x', ''); // TODO: !! + const assertionKey = await this.getOrSetOrCreateAssertionPublicKeyFromDid( + issuerDid, + 'P-256' + ); + // invariant verification: + const pubKey = assertionKey.hexPubKey.replace('0x', ''); + if (!pubKey || pubKey.length !== 64) { + throw new InternalServerError(ErrorsMessages.INTERNAL_SERVER_ERROR); + } + + const proofConfig: IType2ProofConfig = { + type: 'DataIntegrityProof', + proofPurpose: 'assertionMethod', + verificationMethod: assertionKey.keyId, + domain: this.domain, + cryptosuite: 'ecdsa-jcs-2019', + created: this.getDate() // TODO: verify + }; + + const proofConfigHash = this.computeRfc8785AndSha256(proofConfig).replace( + '0x', + '' + ); + + const hashData = '0x' + proofConfigHash.concat(canonicalDocumentHash); + + const p256CompressedPubKey = '0x02' + pubKey; + const messageRequest: ISignPlainMessageByCompressedPublicKey = { + compressedPublicKey: p256CompressedPubKey, + message: hashData + }; + const proofValueResponse = await this.keyManager.p256SignPlainMessage( + messageRequest + ); + const type2Proof: IType2Proof = { + ...proofConfig, + proofValue: this.base58.encode( + Buffer.from(proofValueResponse.signature.replace('0x', ''), 'hex') + ) + }; + return type2Proof; + } + + private getDate() { + const t = new Date(); + const y = t.getUTCFullYear(); + const month = this.getTwoDigitFormat(t.getUTCMonth()); + const d = this.getTwoDigitFormat(t.getUTCDate()); + const h = this.getTwoDigitFormat(t.getUTCHours()); + const m = this.getTwoDigitFormat(t.getUTCMinutes()); + const s = this.getTwoDigitFormat(t.getUTCSeconds()); + return `${y}-${month}-${d}T${h}:${m}:${s}Z`; + } + + private getTwoDigitFormat(el: number): string { + if (el < 10) { + return '0'.concat(el.toString()); + } + return el.toString(); + } + private encode() { const publicDirectoryContractAddress = resolvePublicDirectoryAddress(); const chainOfTrustContractAddress = resolveChainOfTrustAddress(); diff --git a/yarn.lock b/yarn.lock index 2173a1b..0b55eb7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5790,10 +5790,10 @@ koa@^2.8.2: type-is "^1.6.16" vary "^1.1.2" -lacchain-identity@^0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/lacchain-identity/-/lacchain-identity-0.0.3.tgz#8323aa6c75981f15aeb0b1919c0a43d39e12f4e1" - integrity sha512-4TJ7VGayF1ksi1xSqcMUg6+44vJyeeNu2u/X45l/MDDX1+R2Wr4dbRmA3f6LwmUEKi53+GL+KmuBtb8fyitaTA== +lacchain-identity@^0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/lacchain-identity/-/lacchain-identity-0.0.4.tgz#7c822a6158fd6450729645ae4a4a61cef34a6a24" + integrity sha512-DP4mEQnm8kYTh8E6zPadeBMRb+kxMS8j7hIHzOm5roKsDFvbvHOziYsJ3Sl8CkACfqt34eF9QLaMHoEsnvqsXQ== dependencies: "@lacchain/gas-model-provider" "^1.0.1" aws-sdk "^2.1116.0" @@ -5812,7 +5812,7 @@ lacchain-identity@^0.0.3: helmet "^5.0.2" json-canonicalize "^1.0.6" jsonwebtoken "^9.0.0" - lacchain-key-manager "^0.0.3" + lacchain-key-manager "^0.0.4" morgan "^1.10.0" multer "^1.4.4" nodemailer "^6.7.3" @@ -5832,10 +5832,10 @@ lacchain-identity@^0.0.3: typescript-logging "^2.1.0" typescript-logging-log4ts-style "^2.1.0" -lacchain-key-manager@^0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/lacchain-key-manager/-/lacchain-key-manager-0.0.3.tgz#d2f577eb0849753522f8b2709f954ca97fb212e3" - integrity sha512-bCZqJ957lPg6WItbAX3BTaY7Ai5nY7Di5Eyyx0939qvedb/0tbvOu+EV8GsScfqyPmfGBhJ42PRnHRR0th5ZBA== +lacchain-key-manager@^0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/lacchain-key-manager/-/lacchain-key-manager-0.0.4.tgz#d1aa010dd5e78ed4315d573c95f892ab42b92686" + integrity sha512-840rp9YuX+EoYe5NST5/mDGFXti11i1/FAqTYjjD5P08F0HRxmIrhtK2veJHvHx1YgQA9xVKmdgGt46zlTLCzA== dependencies: "@lacchain/gas-model-provider" "^1.0.1" DIDComm-js "git+https://github.com/decentralized-identity/DIDComm-js.git" @@ -5873,10 +5873,10 @@ lacchain-key-manager@^0.0.3: typescript-logging "^2.1.0" typescript-logging-log4ts-style "^2.1.0" -lacchain-trust@^0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/lacchain-trust/-/lacchain-trust-0.0.3.tgz#086220e00849f81496b2965fa8e98d77e09c7044" - integrity sha512-TPuF6PbsIWc06iQ6X0iPRIq8/sxbdY8qSU68TOKDiYNAo3nl9xO8JJ+ab46dKlY8t+4sZ+OysSkUniq73LOg2Q== +lacchain-trust@^0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/lacchain-trust/-/lacchain-trust-0.0.4.tgz#394bec600376077f039f58bbe29c5c2725456ecb" + integrity sha512-QswkluR3v0p+IQIByk/2+Rcqu53UYT+WMRpfraiNQaHKeL7HOknYiC9XVypnbep9MWMOdqI8KVhk4xxJB1PX8g== dependencies: "@lacchain/gas-model-provider" "^1.0.1" aws-sdk "^2.1116.0" @@ -5895,7 +5895,7 @@ lacchain-trust@^0.0.3: express-rate-limit "^6.3.0" helmet "^5.0.2" jsonwebtoken "^9.0.0" - lacchain-identity "^0.0.3" + lacchain-identity "^0.0.4" morgan "^1.10.0" multer "^1.4.4" nodemailer "^6.7.3" From 1e2a6fe2b8649a9f84f8d5418b43a1475796f914 Mon Sep 17 00:00:00 2001 From: eum602 Date: Sun, 8 Oct 2023 14:42:29 -0500 Subject: [PATCH 3/7] update format to verifiable credentials V2 --- .../verifiable-credential/ddcc.credential.ts | 14 ++-- .../verifiable.credentials.service.ts | 64 ++++++++++++------- 2 files changed, 48 insertions(+), 30 deletions(-) diff --git a/src/interfaces/verifiable-credential/ddcc.credential.ts b/src/interfaces/verifiable-credential/ddcc.credential.ts index e42dcfd..efc4edd 100644 --- a/src/interfaces/verifiable-credential/ddcc.credential.ts +++ b/src/interfaces/verifiable-credential/ddcc.credential.ts @@ -1,14 +1,14 @@ -export interface ICredential { +export interface ICredentialV2 { '@context': string[]; id: string; type: string[] | string; issuer: string; name: string; identifier: string; - issuanceDate: string; - expirationDate?: string; + validFrom: string; + validUntil?: string; } -export interface IDDCCCredential extends ICredential { +export interface IDDCCCredential extends ICredentialV2 { credentialSubject: IDDCCCredentialSubject; } @@ -81,9 +81,9 @@ export interface IType2Proof extends IType2ProofConfig { } export type IDDCCVerifiableCredential = IDDCCCredential & { - proof: IType1Proof; + proof: IType2Proof; }; -export type IVerifiableCredential = ICredential & { - proof: IType1Proof | IType2Proof; +export type IVerifiableCredential = ICredentialV2 & { + proof: IType2Proof; }; diff --git a/src/services/verifiable-credentials/verifiable.credentials.service.ts b/src/services/verifiable-credentials/verifiable.credentials.service.ts index f28d19e..7fe502f 100644 --- a/src/services/verifiable-credentials/verifiable.credentials.service.ts +++ b/src/services/verifiable-credentials/verifiable.credentials.service.ts @@ -8,7 +8,7 @@ import { } from 'lacchain-trust'; import { INewJwkAttribute, EcJwk } from 'lacchain-identity'; import { - ICredential, + ICredentialV2, IDDCCCredential, IDDCCVerifiableCredential, IType1Proof, @@ -234,16 +234,16 @@ export class VerifiableCredentialService { * Leaves a proof of existence. Resolves the controller of the issuer did and signs * the proof of existence with its associated private key * @param {string} issuerDid - * @param {ICredential} credentialData + * @param {ICredentialV2} credentialData */ async addProofOfExistence( issuerDid: string, - credentialData: ICredential + credentialData: ICredentialV2 ): Promise { const credentialHash = this.computeRfc8785AndSha256(credentialData); let expiration = 0; - if (credentialData && credentialData.expirationDate) { - const d = new Date(credentialData.expirationDate).getTime(); + if (credentialData && credentialData.validUntil) { + const d = new Date(credentialData.validUntil).getTime(); if (d < new Date().getTime()) { this.log.info( // eslint-disable-next-line max-len @@ -251,7 +251,7 @@ export class VerifiableCredentialService { ); } else { expiration = Math.floor( - new Date(credentialData.expirationDate).getTime() / 1000 + new Date(credentialData.validUntil).getTime() / 1000 ); } } @@ -620,7 +620,7 @@ export class VerifiableCredentialService { async new(): Promise { return { '@context': [ - 'https://www.w3.org/2018/credentials/v1', + 'https://www.w3.org/ns/credentials/v2', // eslint-disable-next-line max-len 'https://credentials-library.lacchain.net/credentials/health/vaccination/v3' ], @@ -635,7 +635,7 @@ export class VerifiableCredentialService { // eslint-disable-next-line quote-props identifier: '', // eslint-disable-next-line quote-props - issuanceDate: new Date().toJSON(), + validFrom: this.getUtcDate(), // eslint-disable-next-line quote-props credentialSubject: { type: 'VaccinationEvent', @@ -700,9 +700,9 @@ export class VerifiableCredentialService { if (certificate && certificate.period && certificate.period.start) { try { - ddccCredential.issuanceDate = new Date( - certificate.period.start - ).toJSON(); + ddccCredential.validFrom = this.getDate( + new Date(certificate.period.start) + ); } catch (e) { this.log.info( 'invalid certificate start date, defaulting to current date' @@ -712,9 +712,9 @@ export class VerifiableCredentialService { if (certificate && certificate.period && certificate.period.end) { try { - ddccCredential.expirationDate = new Date( - certificate.period.end - ).toJSON(); + ddccCredential.validUntil = this.getDate( + new Date(certificate.period.end) + ); } catch (e) { this.log.info('invalid certificate end date, leaving it blank'); } @@ -729,6 +729,7 @@ export class VerifiableCredentialService { ddccCredential.credentialSubject.countryOfVaccination = vaccination.country.code; ddccCredential.credentialSubject.dateOfVaccination = vaccination.date; + if (vaccination.centre) { ddccCredential.credentialSubject.administeringCentre = vaccination.centre; } @@ -740,8 +741,16 @@ export class VerifiableCredentialService { ddccCredential.credentialSubject.totalDoses = vaccination.totalDoses; } if (vaccination.validFrom) { - ddccCredential.credentialSubject.nextVaccinationDate = - vaccination.validFrom; + try { + ddccCredential.credentialSubject.validFrom = this.getDate( + new Date(vaccination.validFrom) + ); + } catch (e) { + this.log.info( + // eslint-disable-next-line max-len + 'Invalid certificate "validFrom" value ... skipping the update of this value in the credential' + ); + } } ddccCredential.credentialSubject.order = vaccination.dose.toString(); // recipient @@ -801,7 +810,7 @@ export class VerifiableCredentialService { * @return {Promise} A DDCC Verifiable credential */ async addProof( - credential: ICredential, + credential: ICredentialV2, issuerDid: string ): Promise { const proof = await this.getIType2ProofAssertionMethodTemplate( @@ -822,7 +831,7 @@ export class VerifiableCredentialService { } async getIType1ProofAssertionMethodTemplate( - credentialData: ICredential, + credentialData: ICredentialV2, issuerDid: string ): Promise { const credentialHash = this.computeRfc8785AndSha256(credentialData); // TODO: !! @@ -857,7 +866,7 @@ export class VerifiableCredentialService { } async getIType2ProofAssertionMethodTemplate( - unsecuredDocument: ICredential, + unsecuredDocument: ICredentialV2, issuerDid: string ): Promise { const canonicalDocumentHash = this.computeRfc8785AndSha256( @@ -879,7 +888,7 @@ export class VerifiableCredentialService { verificationMethod: assertionKey.keyId, domain: this.domain, cryptosuite: 'ecdsa-jcs-2019', - created: this.getDate() // TODO: verify + created: this.getUtcDate() // TODO: verify }; const proofConfigHash = this.computeRfc8785AndSha256(proofConfig).replace( @@ -906,10 +915,9 @@ export class VerifiableCredentialService { return type2Proof; } - private getDate() { - const t = new Date(); + private getUtcDate(t = new Date()) { const y = t.getUTCFullYear(); - const month = this.getTwoDigitFormat(t.getUTCMonth()); + const month = this.getTwoDigitFormat(t.getUTCMonth() + 1); const d = this.getTwoDigitFormat(t.getUTCDate()); const h = this.getTwoDigitFormat(t.getUTCHours()); const m = this.getTwoDigitFormat(t.getUTCMinutes()); @@ -917,6 +925,16 @@ export class VerifiableCredentialService { return `${y}-${month}-${d}T${h}:${m}:${s}Z`; } + private getDate(t: Date) { + const y = t.getUTCFullYear(); + const month = this.getTwoDigitFormat(t.getMonth() + 1); + const d = this.getTwoDigitFormat(t.getDate()); + const h = this.getTwoDigitFormat(t.getHours()); + const m = this.getTwoDigitFormat(t.getMinutes()); + const s = this.getTwoDigitFormat(t.getSeconds()); + return `${y}-${month}-${d}T${h}:${m}:${s}Z`; + } + private getTwoDigitFormat(el: number): string { if (el < 10) { return '0'.concat(el.toString()); From 7f54bb368c0d6b076b52a5ab3f05851cb2eefec4 Mon Sep 17 00:00:00 2001 From: eum602 Date: Sun, 8 Oct 2023 22:14:08 -0500 Subject: [PATCH 4/7] fix: set encoding as 'hex' on signing message with ecdsa-p256 Signed-off-by: eum602 --- package.json | 2 +- .../verifiable.credentials.service.ts | 5 ++-- yarn.lock | 28 +++++++++---------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 93b5f3d..4b853b0 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "form-data": "^4.0.0", "helmet": "^5.0.2", "jsonwebtoken": "^9.0.0", - "lacchain-trust": "^0.0.4", + "lacchain-trust": "^0.0.5", "morgan": "^1.10.0", "multer": "^1.4.4", "nodemailer": "^6.7.3", diff --git a/src/services/verifiable-credentials/verifiable.credentials.service.ts b/src/services/verifiable-credentials/verifiable.credentials.service.ts index 7fe502f..48bf85e 100644 --- a/src/services/verifiable-credentials/verifiable.credentials.service.ts +++ b/src/services/verifiable-credentials/verifiable.credentials.service.ts @@ -896,12 +896,13 @@ export class VerifiableCredentialService { '' ); - const hashData = '0x' + proofConfigHash.concat(canonicalDocumentHash); + const hashData = proofConfigHash.concat(canonicalDocumentHash); const p256CompressedPubKey = '0x02' + pubKey; const messageRequest: ISignPlainMessageByCompressedPublicKey = { compressedPublicKey: p256CompressedPubKey, - message: hashData + message: hashData, + encoding: 'hex' }; const proofValueResponse = await this.keyManager.p256SignPlainMessage( messageRequest diff --git a/yarn.lock b/yarn.lock index 0b55eb7..59850c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5790,10 +5790,10 @@ koa@^2.8.2: type-is "^1.6.16" vary "^1.1.2" -lacchain-identity@^0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/lacchain-identity/-/lacchain-identity-0.0.4.tgz#7c822a6158fd6450729645ae4a4a61cef34a6a24" - integrity sha512-DP4mEQnm8kYTh8E6zPadeBMRb+kxMS8j7hIHzOm5roKsDFvbvHOziYsJ3Sl8CkACfqt34eF9QLaMHoEsnvqsXQ== +lacchain-identity@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/lacchain-identity/-/lacchain-identity-0.0.5.tgz#8eca44089f64fc5e9cc9d09964802ce72e0b9d4d" + integrity sha512-7+71+VwTwaLl26TgpQKlzKNRjDTNzNVOriKAd9ceFh9r9go9yXwZknPDVjSLGeJ3hHXg4K92L0AIXEloW0hNMw== dependencies: "@lacchain/gas-model-provider" "^1.0.1" aws-sdk "^2.1116.0" @@ -5812,7 +5812,7 @@ lacchain-identity@^0.0.4: helmet "^5.0.2" json-canonicalize "^1.0.6" jsonwebtoken "^9.0.0" - lacchain-key-manager "^0.0.4" + lacchain-key-manager "^0.0.5" morgan "^1.10.0" multer "^1.4.4" nodemailer "^6.7.3" @@ -5832,10 +5832,10 @@ lacchain-identity@^0.0.4: typescript-logging "^2.1.0" typescript-logging-log4ts-style "^2.1.0" -lacchain-key-manager@^0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/lacchain-key-manager/-/lacchain-key-manager-0.0.4.tgz#d1aa010dd5e78ed4315d573c95f892ab42b92686" - integrity sha512-840rp9YuX+EoYe5NST5/mDGFXti11i1/FAqTYjjD5P08F0HRxmIrhtK2veJHvHx1YgQA9xVKmdgGt46zlTLCzA== +lacchain-key-manager@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/lacchain-key-manager/-/lacchain-key-manager-0.0.5.tgz#6ef03936dece45f1842a812cf59b65a62a204d8a" + integrity sha512-iI74iRw4Khqhyg/N68TLxw2pKl48eNTDmVxcjcj+MMTmt0rKDoJEhLyOCJoB4248hPl7yArkEcnbYJOVLHhWxQ== dependencies: "@lacchain/gas-model-provider" "^1.0.1" DIDComm-js "git+https://github.com/decentralized-identity/DIDComm-js.git" @@ -5873,10 +5873,10 @@ lacchain-key-manager@^0.0.4: typescript-logging "^2.1.0" typescript-logging-log4ts-style "^2.1.0" -lacchain-trust@^0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/lacchain-trust/-/lacchain-trust-0.0.4.tgz#394bec600376077f039f58bbe29c5c2725456ecb" - integrity sha512-QswkluR3v0p+IQIByk/2+Rcqu53UYT+WMRpfraiNQaHKeL7HOknYiC9XVypnbep9MWMOdqI8KVhk4xxJB1PX8g== +lacchain-trust@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/lacchain-trust/-/lacchain-trust-0.0.5.tgz#d998c89fdb2dc77460859806a17a1759660de0c3" + integrity sha512-cx1wLFHT5Yxmk4lDl3oelry7I8b9Vx08vhjVYdw5A7/Pky/4u1sgsALknQq9rAyGlJGOLZYnqUS+CJ/8++4fNg== dependencies: "@lacchain/gas-model-provider" "^1.0.1" aws-sdk "^2.1116.0" @@ -5895,7 +5895,7 @@ lacchain-trust@^0.0.4: express-rate-limit "^6.3.0" helmet "^5.0.2" jsonwebtoken "^9.0.0" - lacchain-identity "^0.0.4" + lacchain-identity "^0.0.5" morgan "^1.10.0" multer "^1.4.4" nodemailer "^6.7.3" From 9efbd60c54f6b760dabde143ca152c4126172279 Mon Sep 17 00:00:00 2001 From: eum602 Date: Mon, 9 Oct 2023 01:19:45 -0500 Subject: [PATCH 5/7] fix: credentialHash for PoE Signed-off-by: eum602 --- .../verifiable.credentials.service.ts | 95 ++++++++++++------- 1 file changed, 62 insertions(+), 33 deletions(-) diff --git a/src/services/verifiable-credentials/verifiable.credentials.service.ts b/src/services/verifiable-credentials/verifiable.credentials.service.ts index 48bf85e..de673bc 100644 --- a/src/services/verifiable-credentials/verifiable.credentials.service.ts +++ b/src/services/verifiable-credentials/verifiable.credentials.service.ts @@ -56,6 +56,7 @@ import { ProofOfExistenceMode } from '@constants/poe'; type keyType = 'secp256k1' | 'P-256'; type assertionPublicKeyType = { hexPubKey: string; keyId: string }; +type credentialFingerPrint = { hash: string; digest: string }; @Service() export class VerifiableCredentialService { private readonly base58 = require('base-x')( @@ -191,9 +192,23 @@ export class VerifiableCredentialService { qrDescription ); const { issuerDid, receiverDid } = ddccCoreDataSet; + + const assertionKey = await this.getOrSetOrCreateAssertionPublicKeyFromDid( + issuerDid, + 'P-256' + ); + const proofConfig: IType2ProofConfig = { + type: 'DataIntegrityProof', + proofPurpose: 'assertionMethod', + verificationMethod: assertionKey.keyId, + domain: this.domain, + cryptosuite: 'ecdsa-jcs-2019', + created: this.getUtcDate() + }; const ddccVerifiableCredential = (await this.addProof( ddccCredential, - issuerDid + issuerDid, + proofConfig )) as IDDCCVerifiableCredential; const message = JSON.stringify(ddccVerifiableCredential); const authAddress = await this.getAuthAddressFromDid(issuerDid); @@ -203,10 +218,15 @@ export class VerifiableCredentialService { let issueTxResponse: IEthereumTransactionResponse | null = null; // TODO: add environment varible to configure PoE behavior if (this.proofOfExistenceMode !== ProofOfExistenceMode.DISABLED) { + const credentialHash = this.computeEcdsaJcs2019CredentialHash( + ddccCredential, + proofConfig + ).digest; try { issueTxResponse = await this.addProofOfExistence( issuerDid, - ddccCredential + ddccCredential.validUntil, + credentialHash ); } catch (e) { this.log.info('Error adding proof of existence', e); @@ -234,30 +254,31 @@ export class VerifiableCredentialService { * Leaves a proof of existence. Resolves the controller of the issuer did and signs * the proof of existence with its associated private key * @param {string} issuerDid - * @param {ICredentialV2} credentialData + * @param {string} validUntil + * @param {string} digest - digest of 32 bytes representing the fingerprint + * of credentialDat */ async addProofOfExistence( issuerDid: string, - credentialData: ICredentialV2 + validUntil: string | undefined, + digest: string ): Promise { - const credentialHash = this.computeRfc8785AndSha256(credentialData); let expiration = 0; - if (credentialData && credentialData.validUntil) { - const d = new Date(credentialData.validUntil).getTime(); + if (validUntil) { + const d = new Date(validUntil).getTime(); if (d < new Date().getTime()) { this.log.info( // eslint-disable-next-line max-len 'Credential is expired, setting onchain expiration date to zero => never expires' ); } else { - expiration = Math.floor( - new Date(credentialData.validUntil).getTime() / 1000 - ); + expiration = Math.floor(new Date(validUntil).getTime() / 1000); } } + console.log('digest is', digest); return this.verificationRegistryService.verifyAndIssueSigned( issuerDid, - credentialHash, + digest, expiration ); } @@ -807,15 +828,18 @@ export class VerifiableCredentialService { * Adds proof to a Credential of type DDCC * @param {IDDCCCredential} credential - Credential without proof * @param {string} issuerDid - Issuing entity + * @param {IType2ProofConfig} proofConfig - The proof configuration * @return {Promise} A DDCC Verifiable credential */ async addProof( credential: ICredentialV2, - issuerDid: string + issuerDid: string, + proofConfig: IType2ProofConfig ): Promise { const proof = await this.getIType2ProofAssertionMethodTemplate( credential, - issuerDid + issuerDid, + proofConfig ); return { ...credential, proof }; } @@ -834,7 +858,7 @@ export class VerifiableCredentialService { credentialData: ICredentialV2, issuerDid: string ): Promise { - const credentialHash = this.computeRfc8785AndSha256(credentialData); // TODO: !! + const credentialHash = this.computeRfc8785AndSha256(credentialData); const assertionKey = await this.getOrSetOrCreateAssertionPublicKeyFromDid( issuerDid, 'P-256' @@ -865,13 +889,28 @@ export class VerifiableCredentialService { return type1Proof; } - async getIType2ProofAssertionMethodTemplate( + computeEcdsaJcs2019CredentialHash( unsecuredDocument: ICredentialV2, - issuerDid: string - ): Promise { + proofConfig: IType2ProofConfig + ): credentialFingerPrint { const canonicalDocumentHash = this.computeRfc8785AndSha256( unsecuredDocument - ).replace('0x', ''); // TODO: !! + ).replace('0x', ''); + const proofConfigHash = this.computeRfc8785AndSha256(proofConfig).replace( + '0x', + '' + ); + const credentialHash = proofConfigHash.concat(canonicalDocumentHash); + const digest = + '0x' + crypto.createHash('sha256').update(credentialHash).digest('hex'); + return { hash: credentialHash, digest }; + } + + async getIType2ProofAssertionMethodTemplate( + unsecuredDocument: ICredentialV2, + issuerDid: string, + proofConfig: IType2ProofConfig + ): Promise { const assertionKey = await this.getOrSetOrCreateAssertionPublicKeyFromDid( issuerDid, 'P-256' @@ -881,27 +920,17 @@ export class VerifiableCredentialService { if (!pubKey || pubKey.length !== 64) { throw new InternalServerError(ErrorsMessages.INTERNAL_SERVER_ERROR); } - - const proofConfig: IType2ProofConfig = { - type: 'DataIntegrityProof', - proofPurpose: 'assertionMethod', - verificationMethod: assertionKey.keyId, - domain: this.domain, - cryptosuite: 'ecdsa-jcs-2019', - created: this.getUtcDate() // TODO: verify - }; - - const proofConfigHash = this.computeRfc8785AndSha256(proofConfig).replace( - '0x', - '' + const hashData = this.computeEcdsaJcs2019CredentialHash( + unsecuredDocument, + proofConfig ); - const hashData = proofConfigHash.concat(canonicalDocumentHash); + console.log('during proof add process', hashData.digest); const p256CompressedPubKey = '0x02' + pubKey; const messageRequest: ISignPlainMessageByCompressedPublicKey = { compressedPublicKey: p256CompressedPubKey, - message: hashData, + message: hashData.hash, encoding: 'hex' }; const proofValueResponse = await this.keyManager.p256SignPlainMessage( From 81e835b0707ebad58734c90e6ecf54ab21ee20c2 Mon Sep 17 00:00:00 2001 From: eum602 Date: Mon, 9 Oct 2023 01:50:13 -0500 Subject: [PATCH 6/7] feat: add support to add proofs of 'type ecdsa-jcs' Signed-off-by: eum602 --- CHANGELOG.md | 5 +++++ package.json | 2 +- yarn.lock | 28 ++++++++++++++-------------- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c570cd..2cf8fb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +### 0.0.8 + +* Update credential schema version to https://www.w3.org/ns/credentials/v2 +* Add support for ecdsa-jcs-2019 cryptographic suite for verifiable credentials + ### 0.0.7 * Add PoE for emitted credentials. diff --git a/package.json b/package.json index 4b853b0..8f162f4 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "form-data": "^4.0.0", "helmet": "^5.0.2", "jsonwebtoken": "^9.0.0", - "lacchain-trust": "^0.0.5", + "lacchain-trust": "^0.0.6", "morgan": "^1.10.0", "multer": "^1.4.4", "nodemailer": "^6.7.3", diff --git a/yarn.lock b/yarn.lock index 59850c5..5b49b32 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5790,10 +5790,10 @@ koa@^2.8.2: type-is "^1.6.16" vary "^1.1.2" -lacchain-identity@^0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/lacchain-identity/-/lacchain-identity-0.0.5.tgz#8eca44089f64fc5e9cc9d09964802ce72e0b9d4d" - integrity sha512-7+71+VwTwaLl26TgpQKlzKNRjDTNzNVOriKAd9ceFh9r9go9yXwZknPDVjSLGeJ3hHXg4K92L0AIXEloW0hNMw== +lacchain-identity@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/lacchain-identity/-/lacchain-identity-0.0.6.tgz#9f59a4a35d06d3c9a316dacd9606e09ae6c4837c" + integrity sha512-p250BxYpxe3/6CFGbicvAgOBkLLyl2XeImCmH869xpgZST4a45G35JkAc2z1nHAYIDfvPmnpsJqJsow5rYuzwg== dependencies: "@lacchain/gas-model-provider" "^1.0.1" aws-sdk "^2.1116.0" @@ -5812,7 +5812,7 @@ lacchain-identity@^0.0.5: helmet "^5.0.2" json-canonicalize "^1.0.6" jsonwebtoken "^9.0.0" - lacchain-key-manager "^0.0.5" + lacchain-key-manager "^0.0.6" morgan "^1.10.0" multer "^1.4.4" nodemailer "^6.7.3" @@ -5832,10 +5832,10 @@ lacchain-identity@^0.0.5: typescript-logging "^2.1.0" typescript-logging-log4ts-style "^2.1.0" -lacchain-key-manager@^0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/lacchain-key-manager/-/lacchain-key-manager-0.0.5.tgz#6ef03936dece45f1842a812cf59b65a62a204d8a" - integrity sha512-iI74iRw4Khqhyg/N68TLxw2pKl48eNTDmVxcjcj+MMTmt0rKDoJEhLyOCJoB4248hPl7yArkEcnbYJOVLHhWxQ== +lacchain-key-manager@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/lacchain-key-manager/-/lacchain-key-manager-0.0.6.tgz#2d4c3bd175ab11332a7f4657d70438ee47bf7f18" + integrity sha512-RRuazKwiLniOuGE/PmriZqjXbOjBYIe9a1vxCCHeJGnVs6mVp3dD9MnDArszUd+xiJLL4L9CLa0hzTOYZjXOhA== dependencies: "@lacchain/gas-model-provider" "^1.0.1" DIDComm-js "git+https://github.com/decentralized-identity/DIDComm-js.git" @@ -5873,10 +5873,10 @@ lacchain-key-manager@^0.0.5: typescript-logging "^2.1.0" typescript-logging-log4ts-style "^2.1.0" -lacchain-trust@^0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/lacchain-trust/-/lacchain-trust-0.0.5.tgz#d998c89fdb2dc77460859806a17a1759660de0c3" - integrity sha512-cx1wLFHT5Yxmk4lDl3oelry7I8b9Vx08vhjVYdw5A7/Pky/4u1sgsALknQq9rAyGlJGOLZYnqUS+CJ/8++4fNg== +lacchain-trust@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/lacchain-trust/-/lacchain-trust-0.0.6.tgz#a049a3212c4be91c99ada046d33a3a6f3eb83783" + integrity sha512-He7vIEySgGHDDtc2UJRQ3XQhMROwDecUdLeOoYzybQpNgLZTFESLLG8s6BVsPNSWgZHUt+5qt3zmCN4avE2gXQ== dependencies: "@lacchain/gas-model-provider" "^1.0.1" aws-sdk "^2.1116.0" @@ -5895,7 +5895,7 @@ lacchain-trust@^0.0.5: express-rate-limit "^6.3.0" helmet "^5.0.2" jsonwebtoken "^9.0.0" - lacchain-identity "^0.0.5" + lacchain-identity "^0.0.6" morgan "^1.10.0" multer "^1.4.4" nodemailer "^6.7.3" From 6ce173931ff07ed6df2f094e445fb1a8dedf0cbd Mon Sep 17 00:00:00 2001 From: eum602 Date: Tue, 10 Oct 2023 01:54:04 -0500 Subject: [PATCH 7/7] update verification registry contract address Signed-off-by: eum602 --- .example.env | 2 +- .example.env.dev | 2 +- CHANGELOG.md | 3 ++ package.json | 2 +- .../lacchain/verification.registry.abi.ts | 29 ++++--------------- src/constants/verification.registry.ts | 2 +- .../verifiable.credentials.service.ts | 3 -- .../verification.registry.ts | 4 +-- 8 files changed, 14 insertions(+), 33 deletions(-) diff --git a/.example.env b/.example.env index 5b6b48d..0b00d1f 100644 --- a/.example.env +++ b/.example.env @@ -114,4 +114,4 @@ NODE_ADDRESS = 0xad730de8c4bfc3d845f7ce851bcf2ea17c049585 ## verification registry # PROOF_OF_EXISTENCE_MODE = "ENABLED_NOT_THROWABLE" # options: "STRICT", "DISABLED", by default "ENABLED_NOT_THROWABLE" -# VERIFICATION_REGISTRY_CONTRACT_ADDRESS = '0xF17Da8641771c0196318515b662b0C00132C4163' # optional, just in case you are willing to use another verification registry \ No newline at end of file +# VERIFICATION_REGISTRY_CONTRACT_ADDRESS = '0x64CaA0fC7E0C1f051078da9525A31D00dB1F50eE' # optional, just in case you are willing to use another verification registry \ No newline at end of file diff --git a/.example.env.dev b/.example.env.dev index a5dfdbd..fe301d7 100644 --- a/.example.env.dev +++ b/.example.env.dev @@ -113,4 +113,4 @@ NODE_ADDRESS = 0xad730de8c4bfc3d845f7ce851bcf2ea17c049585 ## verification registry # PROOF_OF_EXISTENCE_MODE = "ENABLED_NOT_THROWABLE" # options: "STRICT", "DISABLED", by default "ENABLED_NOT_THROWABLE" -# VERIFICATION_REGISTRY_CONTRACT_ADDRESS = '0xF17Da8641771c0196318515b662b0C00132C4163' # optional, just in case you are willing to use another verification registry \ No newline at end of file +# VERIFICATION_REGISTRY_CONTRACT_ADDRESS = '0x64CaA0fC7E0C1f051078da9525A31D00dB1F50eE' # optional, just in case you are willing to use another verification registry \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cf8fb8..7a19f8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +### 0.0.9 + +* Update verification Registry default contract address for OpenProtest network to '0x64CaA0fC7E0C1f051078da9525A31D00dB1F50eE' (since this considers isRevoked flag on queried for a digest issued by some entity) ### 0.0.8 * Update credential schema version to https://www.w3.org/ns/credentials/v2 diff --git a/package.json b/package.json index 8f162f4..4cffd85 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lacpass-client", - "version": "0.0.8", + "version": "0.0.9", "description": "Rest api for lacpass Client", "license": "MIT", "scripts": { diff --git a/src/constants/lacchain/verification.registry.abi.ts b/src/constants/lacchain/verification.registry.abi.ts index 224f605..3fbbc48 100644 --- a/src/constants/lacchain/verification.registry.abi.ts +++ b/src/constants/lacchain/verification.registry.abi.ts @@ -377,6 +377,11 @@ export const VERIFICATION_REGISTRY_ABI = [ internalType: 'bool', name: 'onHold', type: 'bool' + }, + { + internalType: 'bool', + name: 'isRevoked', + type: 'bool' } ], stateMutability: 'view', @@ -401,30 +406,6 @@ export const VERIFICATION_REGISTRY_ABI = [ stateMutability: 'view', type: 'function' }, - { - inputs: [ - { - internalType: 'address', - name: 'issuer', - type: 'address' - }, - { - internalType: 'bytes32', - name: 'digest', - type: 'bytes32' - } - ], - name: 'isValidCredential', - outputs: [ - { - internalType: 'bool', - name: 'value', - type: 'bool' - } - ], - stateMutability: 'view', - type: 'function' - }, { inputs: [ { diff --git a/src/constants/verification.registry.ts b/src/constants/verification.registry.ts index 54f7196..8fd5fc3 100644 --- a/src/constants/verification.registry.ts +++ b/src/constants/verification.registry.ts @@ -2,5 +2,5 @@ export const VERIFICATION_REGISTRY_CONTRACT_ADDRESSES: Map = new Map(); VERIFICATION_REGISTRY_CONTRACT_ADDRESSES.set( '0x9e55c', - '0xF17Da8641771c0196318515b662b0C00132C4163' + '0x64CaA0fC7E0C1f051078da9525A31D00dB1F50eE' ); diff --git a/src/services/verifiable-credentials/verifiable.credentials.service.ts b/src/services/verifiable-credentials/verifiable.credentials.service.ts index de673bc..e867a58 100644 --- a/src/services/verifiable-credentials/verifiable.credentials.service.ts +++ b/src/services/verifiable-credentials/verifiable.credentials.service.ts @@ -275,7 +275,6 @@ export class VerifiableCredentialService { expiration = Math.floor(new Date(validUntil).getTime() / 1000); } } - console.log('digest is', digest); return this.verificationRegistryService.verifyAndIssueSigned( issuerDid, digest, @@ -925,8 +924,6 @@ export class VerifiableCredentialService { proofConfig ); - console.log('during proof add process', hashData.digest); - const p256CompressedPubKey = '0x02' + pubKey; const messageRequest: ISignPlainMessageByCompressedPublicKey = { compressedPublicKey: p256CompressedPubKey, diff --git a/src/services/verifiable-credentials/verification.registry.ts b/src/services/verifiable-credentials/verification.registry.ts index 451be6e..a4146e1 100644 --- a/src/services/verifiable-credentials/verification.registry.ts +++ b/src/services/verifiable-credentials/verification.registry.ts @@ -59,8 +59,8 @@ export class VerificationRegistry { ); this.domainSeparator = keccak256(eds); this.ISSUE_TYPEHASH = keccak256( - toUtf8Bytes('Issue(bytes32 digest, uint256 exp, address identity)') - ); // OK -> 0xaaf414ba23a8cfcf004a7f75188441e59666f98d85447b5665cf04052d8e2bc3 + toUtf8Bytes('Issue(bytes32 digest,uint256 exp,address identity)') + ); this.didServiceLac1 = new DidServiceLac1(); this.keyManager = new KeyManagerService(); const rpcUrl = getRpcUrl();