import type { EbsiEnvConfiguration, EbsiIssuer, } from "@cef-ebsi/verifiable-credential"; import type { CreateVerifiableCredentialOptions, Schemas, TypeExtensions, } from "@cef-ebsi/verifiable-credential/vcdm11.js"; import { ES256Signer } from "@cef-ebsi/did-jwt"; import { createVerifiableCredentialJwt } from "@cef-ebsi/verifiable-credential/vcdm11.js"; import { randomBytes, randomUUID } from "node:crypto"; export async function issueAccreditation( serverDid: string, serverKid: string, issuerPrivateKey: Uint8Array, issuerAccreditationUrl: string, credentialSchemaIds: string[], accreditedForSchemaId: string, ebsiEnvConfiguration: EbsiEnvConfiguration, createVerifiableCredentialOptions: CreateVerifiableCredentialOptions, additionalVcPayload: Partial, types: string[], sub: string, ): Promise<{ reservedAttributeId: string; vcJwt: string; }> { /** * Reserved Attribute ID * * When the Verifiable Credential is intended to be in the Trusted * Issuers Registry the issuer mock makes a preregistration (aka invitation) * defining the metadata, and later on the subject registers the * content of the credential (aka acceptance). The "reserved attribute * ID" acts an identifier to be able to update or consult this credential * in the Trusted Issuers Registry. */ const reservedAttributeId = `0x${randomBytes(32).toString("hex")}`; // Issue VC const vcIssuer: EbsiIssuer = { alg: "ES256", did: serverDid, kid: serverKid, signer: ES256Signer(issuerPrivateKey), } satisfies EbsiIssuer; const issuedAt = new Date(); const issuanceDate = `${issuedAt.toISOString().slice(0, -5)}Z`; const expiresAt = new Date( issuedAt.getTime() + 1000 * 60 * 60 * 24, // 24 hours ); const expirationDate = `${expiresAt.toISOString().slice(0, -5)}Z`; const vcPayload = { "@context": ["https://www.w3.org/2018/credentials/v1"], credentialSchema: credentialSchemaIds.map((schemaId) => ({ id: schemaId, type: "FullJsonSchemaValidator2021", })), credentialSubject: { accreditedFor: [ { limitJurisdiction: "https://publications.europa.eu/resource/authority/atu/FIN", schemaId: accreditedForSchemaId, types: [ "VerifiableCredential", "VerifiableAttestation", "CTRevocable", // Is allowed to issue CTRevocable VCs "CTRevocableBitstring", // Is allowed to issue CTRevocableBitstring VCs ], }, ], id: sub, reservedAttributeId, }, expirationDate, id: `vc:ebsi:conformance#${randomUUID()}`, issuanceDate, issued: issuanceDate, issuer: vcIssuer.did, termsOfUse: { id: issuerAccreditationUrl, type: "IssuanceCertificate", }, type: types, validFrom: issuanceDate, ...(additionalVcPayload as Partial< Schemas["Accreditation"] & TypeExtensions["termsOfUse"]["IssuanceCertificate"] >), } satisfies Schemas["Accreditation"] & TypeExtensions["termsOfUse"]["IssuanceCertificate"]; const vcJwt = await createVerifiableCredentialJwt( vcPayload, vcIssuer, ebsiEnvConfiguration, createVerifiableCredentialOptions, ); return { reservedAttributeId, vcJwt }; } export async function issueAttestation( serverDid: string, serverKid: string, issuerPrivateKey: Uint8Array, issuerAccreditationUrl: string, credentialSchemaIds: string[], ebsiEnvConfiguration: EbsiEnvConfiguration, createVerifiableCredentialOptions: CreateVerifiableCredentialOptions, additionalVcPayload: Partial, types: string[], sub: string, ): Promise<{ reservedAttributeId: string; vcJwt: string; }> { /** * Reserved Attribute ID * * When the Verifiable Credential is intended to be in the Trusted * Issuers Registry the issuer mock makes a preregistration (aka invitation) * defining the metadata, and later on the subject registers the * content of the credential (aka acceptance). The "reserved attribute * ID" acts an identifier to be able to update or consult this credential * in the Trusted Issuers Registry. */ let reservedAttributeId = ""; if (types.includes("VerifiableAuthorisationForTrustChain")) { reservedAttributeId = `0x${randomBytes(32).toString("hex")}`; } // Issue VC const vcIssuer: EbsiIssuer = { alg: "ES256", did: serverDid, kid: serverKid, signer: ES256Signer(issuerPrivateKey), } satisfies EbsiIssuer; const issuedAt = new Date(); const issuanceDate = `${issuedAt.toISOString().slice(0, -5)}Z`; const expiresAt = new Date( issuedAt.getTime() + 1000 * 60 * 60 * 24, // 24 hours ); const expirationDate = `${expiresAt.toISOString().slice(0, -5)}Z`; const vcPayload = { "@context": ["https://www.w3.org/2018/credentials/v1"], credentialSchema: credentialSchemaIds.map((schemaId) => ({ id: schemaId, type: "FullJsonSchemaValidator2021", })), credentialSubject: { id: sub, ...(reservedAttributeId && { reservedAttributeId }), }, expirationDate, id: `vc:ebsi:conformance#${randomUUID()}`, issuanceDate, issued: issuanceDate, issuer: vcIssuer.did, type: types, validFrom: issuanceDate, ...(issuerAccreditationUrl && { termsOfUse: { id: issuerAccreditationUrl, type: "IssuanceCertificate", } satisfies TypeExtensions["termsOfUse"]["IssuanceCertificate"], }), ...additionalVcPayload, } satisfies Schemas["Attestation"]; const vcJwt = await createVerifiableCredentialJwt( vcPayload, vcIssuer, ebsiEnvConfiguration, createVerifiableCredentialOptions, ); return { reservedAttributeId, vcJwt }; }