|
| 1 | +import type { CheqdDidCreateOptions } from '../src' |
| 2 | + |
| 3 | +import { |
| 4 | + SECURITY_JWS_CONTEXT_URL, |
| 5 | + DidDocumentBuilder, |
| 6 | + getEd25519VerificationKey2018, |
| 7 | + KeyType, |
| 8 | + utils, |
| 9 | + Agent, |
| 10 | + TypedArrayEncoder, |
| 11 | + DifPresentationExchangeProofFormatService, |
| 12 | + JsonLdCredentialFormatService, |
| 13 | + CredentialsModule, |
| 14 | + V2CredentialProtocol, |
| 15 | + ProofsModule, |
| 16 | + V2ProofProtocol, |
| 17 | + CacheModule, |
| 18 | + InMemoryLruCache, |
| 19 | + W3cCredentialsModule, |
| 20 | + CredentialState, |
| 21 | + CredentialExchangeRecord, |
| 22 | + JsonTransformer, |
| 23 | + ProofEventTypes, |
| 24 | + CredentialEventTypes, |
| 25 | +} from '@credo-ts/core' |
| 26 | + |
| 27 | +import { getInMemoryAgentOptions, makeConnection, waitForCredentialRecordSubject } from '../../core/tests/helpers' |
| 28 | + |
| 29 | +import { cheqdPayerSeeds, getCheqdModules } from './setupCheqdModule' |
| 30 | +import { EventReplaySubject, setupEventReplaySubjects, setupSubjectTransports, testLogger } from '../../core/tests' |
| 31 | + |
| 32 | +let did = `did:cheqd:testnet:${utils.uuid()}` |
| 33 | + |
| 34 | +const signCredentialOptions = { |
| 35 | + credential: { |
| 36 | + '@context': [ |
| 37 | + 'https://www.w3.org/2018/credentials/v1', |
| 38 | + 'https://w3id.org/citizenship/v1', |
| 39 | + 'https://w3id.org/security/bbs/v1', |
| 40 | + ], |
| 41 | + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', |
| 42 | + type: ['VerifiableCredential', 'PermanentResidentCard'], |
| 43 | + issuer: did, |
| 44 | + issuanceDate: '2019-12-03T12:19:52Z', |
| 45 | + expirationDate: '2029-12-03T12:19:52Z', |
| 46 | + identifier: '83627465', |
| 47 | + name: 'Permanent Resident Card', |
| 48 | + credentialSubject: { |
| 49 | + id: 'did:example:b34ca6cd37bbf23', |
| 50 | + type: ['PermanentResident', 'Person'], |
| 51 | + givenName: 'JOHN', |
| 52 | + familyName: 'SMITH', |
| 53 | + gender: 'Male', |
| 54 | + image: 'data:image/png;base64,iVBORw0KGgokJggg==', |
| 55 | + residentSince: '2015-01-01', |
| 56 | + description: 'Government of Example Permanent Resident Card.', |
| 57 | + lprCategory: 'C09', |
| 58 | + lprNumber: '999-999-999', |
| 59 | + commuterClassification: 'C1', |
| 60 | + birthCountry: 'Bahamas', |
| 61 | + birthDate: '1958-07-17', |
| 62 | + }, |
| 63 | + }, |
| 64 | + options: { |
| 65 | + proofType: 'Ed25519Signature2018', |
| 66 | + proofPurpose: 'assertionMethod', |
| 67 | + }, |
| 68 | +} |
| 69 | + |
| 70 | +const jsonLdCredentialFormat = new JsonLdCredentialFormatService() |
| 71 | +const jsonLdProofFormat = new DifPresentationExchangeProofFormatService() |
| 72 | + |
| 73 | +const getCheqdJsonLdModules = () => |
| 74 | + ({ |
| 75 | + ...getCheqdModules(cheqdPayerSeeds[0], 'https://rpc.cheqd.network'), |
| 76 | + credentials: new CredentialsModule({ |
| 77 | + credentialProtocols: [ |
| 78 | + new V2CredentialProtocol({ |
| 79 | + credentialFormats: [jsonLdCredentialFormat], |
| 80 | + }), |
| 81 | + ], |
| 82 | + }), |
| 83 | + proofs: new ProofsModule({ |
| 84 | + proofProtocols: [ |
| 85 | + new V2ProofProtocol({ |
| 86 | + proofFormats: [jsonLdProofFormat], |
| 87 | + }), |
| 88 | + ], |
| 89 | + }), |
| 90 | + cache: new CacheModule({ |
| 91 | + cache: new InMemoryLruCache({ limit: 100 }), |
| 92 | + }), |
| 93 | + w3cCredentials: new W3cCredentialsModule({}), |
| 94 | + } as const) |
| 95 | + |
| 96 | +// TODO: extract these very specific tests to the jsonld format |
| 97 | +describe('Cheqd V2 Credentials - JSON-LD - Ed25519', () => { |
| 98 | + let faberAgent: Agent<ReturnType<typeof getCheqdJsonLdModules>> |
| 99 | + let faberReplay: EventReplaySubject |
| 100 | + let aliceAgent: Agent<ReturnType<typeof getCheqdJsonLdModules>> |
| 101 | + let aliceReplay: EventReplaySubject |
| 102 | + let aliceConnectionId: string |
| 103 | + |
| 104 | + beforeAll(async () => { |
| 105 | + faberAgent = new Agent( |
| 106 | + getInMemoryAgentOptions( |
| 107 | + 'Faber Agent Indy/JsonLD', |
| 108 | + { |
| 109 | + endpoints: ['rxjs:faber'], |
| 110 | + }, |
| 111 | + getCheqdJsonLdModules() |
| 112 | + ) |
| 113 | + ) |
| 114 | + aliceAgent = new Agent( |
| 115 | + getInMemoryAgentOptions( |
| 116 | + 'Alice Agent Indy/JsonLD', |
| 117 | + { |
| 118 | + endpoints: ['rxjs:alice'], |
| 119 | + }, |
| 120 | + getCheqdJsonLdModules() |
| 121 | + ) |
| 122 | + ) |
| 123 | + |
| 124 | + setupSubjectTransports([faberAgent, aliceAgent]) |
| 125 | + ;[faberReplay, aliceReplay] = setupEventReplaySubjects( |
| 126 | + [faberAgent, aliceAgent], |
| 127 | + [CredentialEventTypes.CredentialStateChanged, ProofEventTypes.ProofStateChanged] |
| 128 | + ) |
| 129 | + await faberAgent.initialize() |
| 130 | + await aliceAgent.initialize() |
| 131 | + ;[, { id: aliceConnectionId }] = await makeConnection(faberAgent, aliceAgent) |
| 132 | + |
| 133 | + await faberAgent.context.wallet.createKey({ |
| 134 | + privateKey: TypedArrayEncoder.fromString('testseed000000000000000000000001'), |
| 135 | + keyType: KeyType.Ed25519, |
| 136 | + }) |
| 137 | + }) |
| 138 | + |
| 139 | + afterAll(async () => { |
| 140 | + await faberAgent.shutdown() |
| 141 | + await faberAgent.wallet.delete() |
| 142 | + await aliceAgent.shutdown() |
| 143 | + await aliceAgent.wallet.delete() |
| 144 | + }) |
| 145 | + |
| 146 | + it('should create a did:cheqd did using custom did document containing Ed25519 key', async () => { |
| 147 | + const ed25519Key = await faberAgent.wallet.createKey({ |
| 148 | + keyType: KeyType.Ed25519, |
| 149 | + }) |
| 150 | + |
| 151 | + const createResult = await faberAgent.dids.create<CheqdDidCreateOptions>({ |
| 152 | + method: 'cheqd', |
| 153 | + didDocument: new DidDocumentBuilder(did) |
| 154 | + .addContext(SECURITY_JWS_CONTEXT_URL) |
| 155 | + .addVerificationMethod( |
| 156 | + getEd25519VerificationKey2018({ |
| 157 | + key: ed25519Key, |
| 158 | + controller: did, |
| 159 | + id: `${did}#${ed25519Key.fingerprint}`, |
| 160 | + }) |
| 161 | + ) |
| 162 | + .addAssertionMethod(`${did}#${ed25519Key.fingerprint}`) |
| 163 | + .addAuthentication(`${did}#${ed25519Key.fingerprint}`) |
| 164 | + .build(), |
| 165 | + }) |
| 166 | + |
| 167 | + expect(createResult).toMatchObject({ |
| 168 | + didState: { |
| 169 | + state: 'finished', |
| 170 | + }, |
| 171 | + }) |
| 172 | + |
| 173 | + expect(createResult.didState.didDocument?.toJSON()).toMatchObject({ |
| 174 | + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/jws-2020/v1'], |
| 175 | + verificationMethod: [ |
| 176 | + { |
| 177 | + controller: did, |
| 178 | + type: 'Ed25519VerificationKey2018', |
| 179 | + publicKeyBase58: ed25519Key.publicKeyBase58, |
| 180 | + }, |
| 181 | + ], |
| 182 | + }) |
| 183 | + }) |
| 184 | + |
| 185 | + test('Alice starts with V2 (ld format, Ed25519 signature) credential proposal to Faber', async () => { |
| 186 | + testLogger.test('Alice sends (v2 jsonld) credential proposal to Faber') |
| 187 | + |
| 188 | + const credentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ |
| 189 | + connectionId: aliceConnectionId, |
| 190 | + protocolVersion: 'v2', |
| 191 | + credentialFormats: { |
| 192 | + jsonld: signCredentialOptions, |
| 193 | + }, |
| 194 | + comment: 'v2 propose credential test for W3C Credentials', |
| 195 | + }) |
| 196 | + |
| 197 | + expect(credentialExchangeRecord.connectionId).toEqual(aliceConnectionId) |
| 198 | + expect(credentialExchangeRecord.protocolVersion).toEqual('v2') |
| 199 | + expect(credentialExchangeRecord.state).toEqual(CredentialState.ProposalSent) |
| 200 | + expect(credentialExchangeRecord.threadId).not.toBeNull() |
| 201 | + |
| 202 | + testLogger.test('Faber waits for credential proposal from Alice') |
| 203 | + let faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { |
| 204 | + threadId: credentialExchangeRecord.threadId, |
| 205 | + state: CredentialState.ProposalReceived, |
| 206 | + }) |
| 207 | + |
| 208 | + testLogger.test('Faber sends credential offer to Alice') |
| 209 | + await faberAgent.credentials.acceptProposal({ |
| 210 | + credentialRecordId: faberCredentialRecord.id, |
| 211 | + comment: 'V2 W3C Offer', |
| 212 | + }) |
| 213 | + |
| 214 | + testLogger.test('Alice waits for credential offer from Faber') |
| 215 | + let aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { |
| 216 | + threadId: faberCredentialRecord.threadId, |
| 217 | + state: CredentialState.OfferReceived, |
| 218 | + }) |
| 219 | + |
| 220 | + const offerMessage = await aliceAgent.credentials.findOfferMessage(aliceCredentialRecord.id) |
| 221 | + expect(JsonTransformer.toJSON(offerMessage)).toMatchObject({ |
| 222 | + '@type': 'https://didcomm.org/issue-credential/2.0/offer-credential', |
| 223 | + '@id': expect.any(String), |
| 224 | + comment: 'V2 W3C Offer', |
| 225 | + formats: [ |
| 226 | + { |
| 227 | + attach_id: expect.any(String), |
| 228 | + format: 'aries/ld-proof-vc-detail@v1.0', |
| 229 | + }, |
| 230 | + ], |
| 231 | + 'offers~attach': [ |
| 232 | + { |
| 233 | + '@id': expect.any(String), |
| 234 | + 'mime-type': 'application/json', |
| 235 | + data: expect.any(Object), |
| 236 | + lastmod_time: undefined, |
| 237 | + byte_count: undefined, |
| 238 | + }, |
| 239 | + ], |
| 240 | + '~thread': { |
| 241 | + thid: expect.any(String), |
| 242 | + pthid: undefined, |
| 243 | + sender_order: undefined, |
| 244 | + received_orders: undefined, |
| 245 | + }, |
| 246 | + '~service': undefined, |
| 247 | + '~attach': undefined, |
| 248 | + '~please_ack': undefined, |
| 249 | + '~timing': undefined, |
| 250 | + '~transport': undefined, |
| 251 | + '~l10n': undefined, |
| 252 | + credential_preview: expect.any(Object), |
| 253 | + replacement_id: undefined, |
| 254 | + }) |
| 255 | + expect(aliceCredentialRecord.id).not.toBeNull() |
| 256 | + expect(aliceCredentialRecord.type).toBe(CredentialExchangeRecord.type) |
| 257 | + |
| 258 | + const offerCredentialExchangeRecord = await aliceAgent.credentials.acceptOffer({ |
| 259 | + credentialRecordId: aliceCredentialRecord.id, |
| 260 | + credentialFormats: { |
| 261 | + jsonld: {}, |
| 262 | + }, |
| 263 | + }) |
| 264 | + |
| 265 | + expect(offerCredentialExchangeRecord.connectionId).toEqual(aliceConnectionId) |
| 266 | + expect(offerCredentialExchangeRecord.protocolVersion).toEqual('v2') |
| 267 | + expect(offerCredentialExchangeRecord.state).toEqual(CredentialState.RequestSent) |
| 268 | + expect(offerCredentialExchangeRecord.threadId).not.toBeNull() |
| 269 | + |
| 270 | + testLogger.test('Faber waits for credential request from Alice') |
| 271 | + await waitForCredentialRecordSubject(faberReplay, { |
| 272 | + threadId: aliceCredentialRecord.threadId, |
| 273 | + state: CredentialState.RequestReceived, |
| 274 | + }) |
| 275 | + |
| 276 | + testLogger.test('Faber sends credential to Alice') |
| 277 | + |
| 278 | + await faberAgent.credentials.acceptRequest({ |
| 279 | + credentialRecordId: faberCredentialRecord.id, |
| 280 | + comment: 'V2 Indy Credential', |
| 281 | + }) |
| 282 | + |
| 283 | + testLogger.test('Alice waits for credential from Faber') |
| 284 | + aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { |
| 285 | + threadId: faberCredentialRecord.threadId, |
| 286 | + state: CredentialState.CredentialReceived, |
| 287 | + }) |
| 288 | + |
| 289 | + testLogger.test('Alice sends credential ack to Faber') |
| 290 | + await aliceAgent.credentials.acceptCredential({ credentialRecordId: aliceCredentialRecord.id }) |
| 291 | + |
| 292 | + testLogger.test('Faber waits for credential ack from Alice') |
| 293 | + faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { |
| 294 | + threadId: faberCredentialRecord.threadId, |
| 295 | + state: CredentialState.Done, |
| 296 | + }) |
| 297 | + expect(aliceCredentialRecord).toMatchObject({ |
| 298 | + type: CredentialExchangeRecord.type, |
| 299 | + id: expect.any(String), |
| 300 | + createdAt: expect.any(Date), |
| 301 | + threadId: expect.any(String), |
| 302 | + connectionId: expect.any(String), |
| 303 | + state: CredentialState.CredentialReceived, |
| 304 | + }) |
| 305 | + |
| 306 | + const credentialMessage = await faberAgent.credentials.findCredentialMessage(faberCredentialRecord.id) |
| 307 | + expect(JsonTransformer.toJSON(credentialMessage)).toMatchObject({ |
| 308 | + '@type': 'https://didcomm.org/issue-credential/2.0/issue-credential', |
| 309 | + '@id': expect.any(String), |
| 310 | + comment: 'V2 Indy Credential', |
| 311 | + formats: [ |
| 312 | + { |
| 313 | + attach_id: expect.any(String), |
| 314 | + format: 'aries/ld-proof-vc@v1.0', |
| 315 | + }, |
| 316 | + ], |
| 317 | + 'credentials~attach': [ |
| 318 | + { |
| 319 | + '@id': expect.any(String), |
| 320 | + 'mime-type': 'application/json', |
| 321 | + data: expect.any(Object), |
| 322 | + lastmod_time: undefined, |
| 323 | + byte_count: undefined, |
| 324 | + }, |
| 325 | + ], |
| 326 | + '~thread': { |
| 327 | + thid: expect.any(String), |
| 328 | + pthid: undefined, |
| 329 | + sender_order: undefined, |
| 330 | + received_orders: undefined, |
| 331 | + }, |
| 332 | + '~please_ack': { on: ['RECEIPT'] }, |
| 333 | + '~service': undefined, |
| 334 | + '~attach': undefined, |
| 335 | + '~timing': undefined, |
| 336 | + '~transport': undefined, |
| 337 | + '~l10n': undefined, |
| 338 | + }) |
| 339 | + }) |
| 340 | +}) |
0 commit comments