From a61667f36d1317dfcd43c47d6befdd38b4d7d3a5 Mon Sep 17 00:00:00 2001 From: kimdhamilton Date: Mon, 5 Sep 2022 10:08:36 -0700 Subject: [PATCH] consume latest verite lib and simplify / reuse CM/PDs --- lib/attestation-fns.ts | 102 +++++++++--------- lib/constants.ts | 39 ++++--- lib/credential-fns.ts | 10 +- lib/manifest-fns.ts | 63 +++-------- lib/manifest/output-descriptors/address.ts | 70 ++++++------ .../output-descriptors/counterparty.ts | 64 ++++++----- lib/manifest/output-descriptors/index.ts | 29 +++-- lib/manifest/output-descriptors/kybaml.ts | 50 ++++----- lib/manifest/output-descriptors/kycaml.ts | 50 ++++----- lib/verification-fns.ts | 36 ++----- .../presentation-definitions/address.ts | 69 ------------ .../presentation-definitions/counterparty.ts | 57 ---------- .../presentation-definitions/index.ts | 74 ++++++++++--- .../presentation-definitions/kybaml.ts | 58 ---------- .../presentation-definitions/kycaml.ts | 58 ---------- package-lock.json | 14 +-- package.json | 2 +- pages/api/credentials/offer.ts | 11 +- pages/api/credentials/submit.ts | 31 ++++-- pages/api/verifications/offer.ts | 8 +- pages/api/verifications/submit.ts | 12 ++- pages/index.tsx | 15 +-- pages/verifier.tsx | 10 +- 23 files changed, 358 insertions(+), 574 deletions(-) delete mode 100644 lib/verification/presentation-definitions/address.ts delete mode 100644 lib/verification/presentation-definitions/counterparty.ts delete mode 100644 lib/verification/presentation-definitions/kybaml.ts delete mode 100644 lib/verification/presentation-definitions/kycaml.ts diff --git a/lib/attestation-fns.ts b/lib/attestation-fns.ts index dc8ba8a..f0bd5e4 100644 --- a/lib/attestation-fns.ts +++ b/lib/attestation-fns.ts @@ -1,6 +1,9 @@ import { faker } from "@faker-js/faker" import { ethers } from "ethers" -import type { Attestation } from "verite" +import { ADDRESS_OWNER_ATTESTATION, Attestation, COUNTERPARTY_ACCOUNT_HOLDER_ATTESTATION, KYBPAML_ATTESTATION, KYCAML_ATTESTATION, KYCAML_CREDENTIAL_TYPE_NAME } from "verite" + +import { AttestationKeys, AttestationTypes } from "./constants" +import { apiDebug } from "./debug" type GenerateAttestationOptions = { approvalDate?: Date @@ -9,7 +12,7 @@ type GenerateAttestationOptions = { } type GenerateAttestation = ( - type: string, + type: AttestationTypes, opts?: GenerateAttestationOptions ) => Promise @@ -17,60 +20,63 @@ type GenerateAttestation = ( * */ export const generateAttestation: GenerateAttestation = async (type, opts) => { - if (type === "kycaml") { - const approvalDate = opts?.approvalDate || new Date() + const approvalDate = opts?.approvalDate || new Date() + apiDebug(`generateAttestation for type=${type}`) + if (type.definition.process) { return { - type: "KYCAMLAttestation", - process: "https://verite.id/definitions/processes/kycaml/0.0.1/usa", + type: type.type, + process: type.definition.process, approvalDate: approvalDate.toISOString() } - } - - if (type === "kybaml") { - const approvalDate = opts?.approvalDate || new Date() - - return { - type: "KYBPAMLAttestation", - process: - "https://verite.id/definitions/processes/kycaml/0.0.1/generic--usa-legal_person", - approvalDate: approvalDate.toISOString() + } else { + if (type.id === AttestationKeys.address) { + if (!opts?.chain) { + throw new Error("Missing attribute: chain") + } + const wallet = ethers.Wallet.createRandom() + const issuanceDate = opts?.issuanceDate ?? new Date() + const proof = await wallet.signMessage( + [opts.chain, wallet.address, issuanceDate.toISOString()].join("") + ) + return { + type: ADDRESS_OWNER_ATTESTATION, + chain: opts.chain, + address: wallet.address, + proof + } } - } - - if (type === "address") { - if (!opts?.chain) { - throw new Error("Missing attribute: chain") - } - const wallet = ethers.Wallet.createRandom() - const issuanceDate = opts?.issuanceDate ?? new Date() - const proof = await wallet.signMessage( - [opts.chain, wallet.address, issuanceDate.toISOString()].join("") - ) - - return { - type: "AddressOwner", - chain: opts.chain, - address: wallet.address, - proof - } - } - - if (type === "counterparty") { - return { - type: "CounterpartyAccountHolder", - legalName: faker.name.findName(), - address: { - type: "PostalAddress", - addressLocality: faker.address.city(), - addressRegion: faker.address.stateAbbr(), - postalCode: faker.address.zipCode(), - addressCountry: "United States" - }, - accountNumber: faker.finance.ethereumAddress() + + if (type.id === AttestationKeys.counterparty) { + return { + type: COUNTERPARTY_ACCOUNT_HOLDER_ATTESTATION, + legalName: faker.name.findName(), + address: { + type: "PostalAddress", + addressLocality: faker.address.city(), + addressRegion: faker.address.stateAbbr(), + postalCode: faker.address.zipCode(), + addressCountry: "United States" + }, + accountNumber: faker.finance.ethereumAddress() + } } } // Unknown Attestation type, throw. throw new Error(`Unknown attestation type: ${type}`) } + + +export function getCredentialType(attestationType: string): string { + if (attestationType === KYCAML_ATTESTATION) { + return KYCAML_CREDENTIAL_TYPE_NAME + } else if (attestationType === KYBPAML_ATTESTATION) { + return "KYBPAMLCredential" + } else if (attestationType === ADDRESS_OWNER_ATTESTATION) { + return "AddressOwnerCredential" + } else if (attestationType === COUNTERPARTY_ACCOUNT_HOLDER_ATTESTATION) { + return "CounterpartyAccountHolderCredential" + } + throw new Error(`Unrecognized attestation type ${attestationType}`) +} \ No newline at end of file diff --git a/lib/constants.ts b/lib/constants.ts index 4674b6a..80e3a52 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -1,3 +1,5 @@ +import { ADDRESS_OWNER_ATTESTATION, AttestationDefinition, COUNTERPARTY_ACCOUNT_HOLDER_ATTESTATION, getAttestionDefinition, KYBPAML_ATTESTATION, KYCAML_ATTESTATION } from "verite" + /** * */ @@ -7,13 +9,21 @@ export type BaseCredentialProperty = { secondary?: string } +export enum AttestationKeys { + kycaml = "kycaml", + kybpaml = "kybpaml", + address = "addresss", + counterparty = "counterparty" +} + /** * */ -export type CredentialType = BaseCredentialProperty< - "kycaml" | "kybaml" | "address" | "counterparty" +export type AttestationTypes = BaseCredentialProperty< +AttestationKeys > & { - type: string + type: string, + definition: AttestationDefinition } /** @@ -58,26 +68,31 @@ export type ChainId = BaseCredentialProperty & { /** * */ -export const CREDENTIAL_TYPES: CredentialType[] = [ +export const ATTESTATION_TYPES: AttestationTypes[] = [ { - id: "kycaml", + id: AttestationKeys.kycaml, name: "KYC/AML Attestation", - type: "KYCAMLAttestation" + type: KYCAML_ATTESTATION, + definition: getAttestionDefinition(KYCAML_ATTESTATION) }, { - id: "kybaml", + id: AttestationKeys.kybpaml, name: "KYBP/AML Attestation", - type: "KYBPAMLAttestation" + type: KYBPAML_ATTESTATION, + definition: getAttestionDefinition(KYBPAML_ATTESTATION) }, { - id: "address", + id: AttestationKeys.address, name: "Address Ownership", - type: "AddressOwner" + type: ADDRESS_OWNER_ATTESTATION, + definition: getAttestionDefinition(ADDRESS_OWNER_ATTESTATION) + }, { - id: "counterparty", + id: AttestationKeys.counterparty, name: "Counterparty Compliance", - type: "CounterpartyAccountHolder" + type: COUNTERPARTY_ACCOUNT_HOLDER_ATTESTATION, + definition: getAttestionDefinition(COUNTERPARTY_ACCOUNT_HOLDER_ATTESTATION) } ] diff --git a/lib/credential-fns.ts b/lib/credential-fns.ts index 3102f10..53d8bb8 100644 --- a/lib/credential-fns.ts +++ b/lib/credential-fns.ts @@ -1,6 +1,6 @@ import { - CredentialType, - CREDENTIAL_TYPES, + AttestationTypes, + ATTESTATION_TYPES, CredentialIssuer, CREDENTIAL_ISSUERS, CredentialStatus, @@ -18,10 +18,10 @@ const TWO_MONTHS = 2 * 30 * 24 * 60 * 60 * 1000 /** * */ -export const findCredentialType = (id: string): CredentialType => { - const item = CREDENTIAL_TYPES.find((t) => t.id === id) +export const findAttestationType = (id: string): AttestationTypes => { + const item = ATTESTATION_TYPES.find((t) => t.id === id) if (!item) { - throw new Error(`Unknown credential type: ${id}`) + throw new Error(`Unknown attestation type: ${id}`) } return item } diff --git a/lib/manifest-fns.ts b/lib/manifest-fns.ts index 50accac..1389617 100644 --- a/lib/manifest-fns.ts +++ b/lib/manifest-fns.ts @@ -1,35 +1,15 @@ -import type { CredentialManifest, PresentationDefinition } from "verite" +import { CredentialManifest, CredentialManifestBuilder, CREDENTIAL_MANIFEST_SPEC_VERSION_1_0_0, PresentationDefinition, proofOfControlPresentationDefinition } from "verite" -import { fullURL } from "./url-fns" +import { apiDebug } from "./debug" -import { CredentialIssuer, CredentialType } from "lib/constants" -import { findCredentialType } from "lib/credential-fns" -import { OUTPUT_DESCRIPTORS } from "lib/manifest/output-descriptors" +import { CredentialIssuer, AttestationTypes } from "lib/constants" +import { getOutputDescriptors } from "lib/manifest/output-descriptors" -const PRESENTATION_DEFINITION: PresentationDefinition = { - id: "PROOF_OF_CONTROL_PRESENTATION_DEF_ID", - format: { - jwt_vp: { - alg: ["EdDSA"] - } - }, - input_descriptors: [ - { - id: "proofOfIdentifierControlVP", - name: "Proof of Control Verifiable Presentation", - purpose: - "A Verifiable Presentation establishing proof of identifier control over the DID.", - schema: [ - { - uri: fullURL("/api/schemas/ProofOfControl") - } - ] - } - ] -} + +const PRESENTATION_DEFINITION: PresentationDefinition = proofOfControlPresentationDefinition() export type GenerateManifest = ( - type: CredentialType["id"], + type: AttestationTypes, issuer: CredentialIssuer ) => CredentialManifest @@ -37,24 +17,13 @@ export type GenerateManifest = ( * */ export const generateManifest: GenerateManifest = (type, issuer) => { - const credentialType = findCredentialType(type) - - return { - id: credentialType.type, - version: "0.1.0", - issuer: { - id: issuer.did.key, - name: issuer.name - }, - format: { - jwt_vc: { - alg: ["EdDSA"] - }, - jwt_vp: { - alg: ["EdDSA"] - } - }, - output_descriptors: [OUTPUT_DESCRIPTORS[type]], - presentation_definition: PRESENTATION_DEFINITION - } + apiDebug(`generateManifest => getAttestationInformation(${JSON.stringify(type)})`) + const outputDescriptors = getOutputDescriptors(issuer.name, type) + return new CredentialManifestBuilder(type.type).issuer( { + id: issuer.did.key, + name: issuer.name + }) + .output_descriptors(outputDescriptors) + .presentation_definition(PRESENTATION_DEFINITION) + .build() } diff --git a/lib/manifest/output-descriptors/address.ts b/lib/manifest/output-descriptors/address.ts index e76c600..5dd16c0 100644 --- a/lib/manifest/output-descriptors/address.ts +++ b/lib/manifest/output-descriptors/address.ts @@ -1,41 +1,35 @@ -import { fullURL } from "lib/url-fns" +import { LabeledDisplayMappingBuilder, OutputDescriptor, STRING_SCHEMA } from "verite" -export const descriptor = { - id: "address-output-descriptor", - schema: [ +import { AttestationTypes } from "lib/constants" + +export function getOutputDescriptors(issuerName: string, type: AttestationTypes): OutputDescriptor[] { + + const properties = [ + new LabeledDisplayMappingBuilder("Chain", STRING_SCHEMA).path([`$.${type.type}.chain`]).build(), + new LabeledDisplayMappingBuilder("Address", STRING_SCHEMA).path([`$.${type.type}.address`]).build(), + new LabeledDisplayMappingBuilder("Proof", STRING_SCHEMA).path([`$.${type.type}.proof`]).build(), + ] + const outputs = [ { - uri: fullURL("/api/schemas/AddressOwner") - } - ], - name: "Proof of Address Ownership", - description: "Attestation that the subject owns a given address", - display: { - title: { - text: "Address Ownership" - }, - subtitle: { - path: ["$.AddressOwner.address"], - fallback: "Includes address" - }, - description: { - text: "Expresses proof of ownership of an address for any chain that uses public-private key cryptography in address ownership." - }, - properties: [ - { - label: "Chain", - path: ["$.AddressOwner.chain"], - schema: { type: "string" } - }, - { - label: "Address", - path: ["$.AddressOwner.address"], - schema: { type: "string" } - }, - { - label: "Proof", - path: ["$.AddressOwner.proof"], - schema: { type: "string" } + id: `${type.type}`, + schema: type.definition.schema, + name: "Proof of Address Ownership", + description: "Attestation that the subject owns a given address", + display: { + title: { + text: "Address Ownership" + }, + subtitle: { + path: [`$.${type.type}.address`], + fallback: "Includes address" + }, + description: { + text: "Expresses proof of ownership of an address for any chain that uses public-private key cryptography in address ownership." + }, + properties: properties } - ] - } -} + } + ] + return outputs + +} \ No newline at end of file diff --git a/lib/manifest/output-descriptors/counterparty.ts b/lib/manifest/output-descriptors/counterparty.ts index 52e6097..5a77859 100644 --- a/lib/manifest/output-descriptors/counterparty.ts +++ b/lib/manifest/output-descriptors/counterparty.ts @@ -1,37 +1,35 @@ -import { fullURL } from "lib/url-fns" +import { LabeledDisplayMappingBuilder, OutputDescriptor, STRING_SCHEMA } from "verite" -export const descriptor = { - id: "counterparty-output-descriptor", - schema: [ +import { AttestationTypes } from "lib/constants" + + +export function getOutputDescriptors(issuerName: string, type: AttestationTypes): OutputDescriptor[] { + + const properties = [ + new LabeledDisplayMappingBuilder("Legal Name", STRING_SCHEMA).path([`$.${type.type}.legalName`]).build(), + new LabeledDisplayMappingBuilder("Account Holder", STRING_SCHEMA).path([`$.${type.type}.accountHolder`]).build(), + ] + + const outputs = [ { - uri: fullURL("/api/schemas/CounterpartyAccountHolder") - } - ], - name: "Counterparty PII Compliance", - description: - "Attestation of Counterparty PII used for originators and beneficiaries of transactions that trigger counterparty exchange requirements", - display: { - title: { - text: "Counterparty Compliance" - }, - subtitle: { - path: ["$.CounterpartyAccountHolder.legalName"], - fallback: "Includes legal name" - }, - description: { - text: "Describes an attestation of Counterparty PII used for originators and beneficiaries of transactions that trigger counterparty exchange requirements, such as the US Travel Rule and the FATF InterVASP message requirements." - }, - properties: [ - { - label: "Legal Name", - path: ["$.CounterpartyAccountHolder.legalName"], - schema: { type: "string" } - }, - { - label: "Account Number", - path: ["$.CounterpartyAccountHolder.accountNumber"], - schema: { type: "string" } + id: `${type.type}`, + schema: type.definition.schema, + name: "Counterparty PII Compliance", + description: + "Attestation of Counterparty PII used for originators and beneficiaries of transactions that trigger counterparty exchange requirements", + display: { + title: { text: "Counterparty Compliance" }, + subtitle: { + path: [`$.${type.type}.legalName`], + fallback: "Includes legal name" + }, + description: { + text: "Describes an attestation of Counterparty PII used for originators and beneficiaries of transactions that trigger counterparty exchange requirements, such as the US Travel Rule and the FATF InterVASP message requirements." + }, + properties: properties } - ] - } + } + ] + + return outputs } diff --git a/lib/manifest/output-descriptors/index.ts b/lib/manifest/output-descriptors/index.ts index 54f2d42..b6c2299 100644 --- a/lib/manifest/output-descriptors/index.ts +++ b/lib/manifest/output-descriptors/index.ts @@ -1,15 +1,22 @@ + import { OutputDescriptor } from "verite" -import { descriptor as address } from "./address" -import { descriptor as counterparty } from "./counterparty" -import { descriptor as kybaml } from "./kybaml" -import { descriptor as kycaml } from "./kycaml" +import { getOutputDescriptors as addressOutputs } from "./address" +import { getOutputDescriptors as counterpartyOutputs } from "./counterparty" +import { getOutputDescriptors as kybamlOutputs } from "./kybaml" +import { getOutputDescriptors as kycamlOutputs } from "./kycaml" -import { CredentialType } from "lib/constants" +import { AttestationKeys, AttestationTypes } from "lib/constants" -export const OUTPUT_DESCRIPTORS = { - kycaml, - kybaml, - address, - counterparty -} as Record +export function getOutputDescriptors(issuerName: string, type: AttestationTypes) : OutputDescriptor[] { + if (type.id === AttestationKeys.kycaml) { + return kycamlOutputs(issuerName, type) + } else if (type.id === AttestationKeys.kybpaml) { + return kybamlOutputs(issuerName, type) + } else if (type.id === AttestationKeys.address) { + return addressOutputs(issuerName, type) + } else if (type.id === AttestationKeys.counterparty) { + return counterpartyOutputs(issuerName, type) + } + throw Error(`Unrecognized attestation type ${type}`) +} \ No newline at end of file diff --git a/lib/manifest/output-descriptors/kybaml.ts b/lib/manifest/output-descriptors/kybaml.ts index 5aac7bb..811c0ea 100644 --- a/lib/manifest/output-descriptors/kybaml.ts +++ b/lib/manifest/output-descriptors/kybaml.ts @@ -1,40 +1,30 @@ -import { fullURL } from "lib/url-fns" +import { DataDisplayBuilder, OutputDescriptor } from "verite" -export const descriptor = { - id: "kybaml-output-descriptor", - schema: [ - { - uri: fullURL("/api/schemas/KYBPAMLAttestation") - } - ], - name: "Proof of Know Your Business Partner and Anti-Money Laundering", - description: - "Attestation that the issuer has completed KYBP/AML verification for this subject", - display: { +import { AttestationTypes } from "lib/constants" + +export function getOutputDescriptors(issuerName: string, type: AttestationTypes): OutputDescriptor[] { + + const display = new DataDisplayBuilder({ title: { - text: "KYBP / AML Attestation" + text: `${issuerName} KYBP/AML Attestation` }, subtitle: { - path: ["$.KYBPAMLAttestation.approvalDate"], + path: [`$.${type.type}.approvalDate`, `$.vc.${type.type}.approvalDate`], fallback: "Includes date of approval" }, description: { text: "The KYBP authority processes Know Your Business Partner and Anti-Money Laundering analysis, potentially employing a number of internal and external vendor providers." }, - properties: [ - { - label: "Process", - path: ["$.KYBPAMLAttestation.process"], - schema: { type: "string" } - }, - { - label: "Approved At", - path: ["$.KYBPAMLAttestation.approvalDate"], - schema: { - type: "string", - format: "date-time" - } - } - ] - } + }).addStringProperty("Process", b => b.path([`$.${type.type}.process`])) + .addDateTimeProperty("Approved At", b => b.path([`$.${type.type}.approvalDate`])) + + return [ + { + id: `${type.id}`, + schema: type.definition.schema, + name: `Proof of Know Your Business Partner and Anti-Money Laundering from ${issuerName}`, + description: `Attestation that ${issuerName} has completed KYBP/AML verification for this subject`, + display: display.build() + } + ] } diff --git a/lib/manifest/output-descriptors/kycaml.ts b/lib/manifest/output-descriptors/kycaml.ts index 49d132f..3e4c531 100644 --- a/lib/manifest/output-descriptors/kycaml.ts +++ b/lib/manifest/output-descriptors/kycaml.ts @@ -1,40 +1,30 @@ -import { fullURL } from "lib/url-fns" +import { DataDisplayBuilder, OutputDescriptor } from "verite" -export const descriptor = { - id: "kycaml-output-descriptor", - schema: [ - { - uri: fullURL("/api/schemas/KYCAMLAttestation") - } - ], - name: "Proof of Know Your Customer and Anti-Money Laundering", - description: - "Attestation that the issuer has completed KYC/AML verification for this subject", - display: { +import { AttestationTypes } from "lib/constants" + +export function getOutputDescriptors(issuerName: string, type: AttestationTypes): OutputDescriptor[] { + + const display = new DataDisplayBuilder({ title: { - text: "KYC / AML Attestation" + text: `${issuerName} KYC Attestation` }, subtitle: { - path: ["$.KYCAMLAttestation.approvalDate"], + path: [`$.${type.type}.approvalDate`, `$.vc.${type.type}.approvalDate`], fallback: "Includes date of approval" }, description: { text: "The KYC authority processes Know Your Customer and Anti-Money Laundering analysis, potentially employing a number of internal and external vendor providers." }, - properties: [ - { - label: "Process", - path: ["$.KYCAMLAttestation.process"], - schema: { type: "string" } - }, - { - label: "Approved At", - path: ["$.KYCAMLAttestation.approvalDate"], - schema: { - type: "string", - format: "date-time" - } - } - ] - } + }).addStringProperty("Process", b => b.path([`$.${type.type}.process`])) + .addDateTimeProperty("Approved At", b => b.path([`$.${type.type}.approvalDate`])) + + return [ + { + id: `${type.id}`, + schema: type.definition.schema, + name: `Proof of KYC from ${issuerName}`, + description: `Attestation that ${issuerName} has completed KYC/AML verification for this subject`, + display: display.build() + } + ] } diff --git a/lib/verification-fns.ts b/lib/verification-fns.ts index 9639286..ebd07ab 100644 --- a/lib/verification-fns.ts +++ b/lib/verification-fns.ts @@ -1,13 +1,14 @@ +import { getPresentationDefinition } from "./verification/presentation-definitions" + import { - CredentialType, + AttestationTypes, CREDENTIAL_VERIFIERS, VerificationStatus } from "lib/constants" import { fullURL } from "lib/url-fns" -import { VERIFICATION_PRESENTATION_DEFINITIONS } from "lib/verification/presentation-definitions" type GenerateVerificationOfferParams = { - credentialType: CredentialType + attestationType: AttestationTypes trustedIssuers: string[] statusEndpointResult: VerificationStatus subjectAddress?: string @@ -18,36 +19,17 @@ type GenerateVerificationOfferParams = { * Generate a Presentation Definition for a verification request */ export const generateVerificationOffer = ({ - credentialType, + attestationType: attestationType, trustedIssuers, statusEndpointResult, subjectAddress, chainId }: GenerateVerificationOfferParams) => { - // Deep clone the Presentation Definition - const presentationDefinition = JSON.parse( - JSON.stringify(VERIFICATION_PRESENTATION_DEFINITIONS[credentialType.id]) - ) - const trustedIssuerConstraint = { - path: ["$.issuer.id", "$.issuer", "$.vc.issuer", "$.iss"], - purpose: "We can only verify credentials attested by a trusted authority.", - filter: { - type: "string", - pattern: trustedIssuers.map((issuer) => `^${issuer}$`).join("|") - } - } - /** - * Ensure the issuer is listed as a trusted issuer here - */ - if (trustedIssuers.length) { - presentationDefinition.input_descriptors[0].constraints?.fields?.push( - trustedIssuerConstraint - ) - } + const pd = getPresentationDefinition(attestationType, trustedIssuers) const params = new URLSearchParams({ - type: credentialType.id, + type: attestationType.id, status: statusEndpointResult.id }) @@ -64,7 +46,7 @@ export const generateVerificationOffer = ({ } return { - id: credentialType.id, + id: attestationType.id, type: "VerificationRequest", from: CREDENTIAL_VERIFIERS[0].did.key, created_time: new Date().toISOString(), @@ -73,7 +55,7 @@ export const generateVerificationOffer = ({ body: { status_url: fullURL(`/api/verifications/status?${params.toString()}`), challenge: "random-challenge", - presentation_definition: presentationDefinition + presentation_definition: pd } } } diff --git a/lib/verification/presentation-definitions/address.ts b/lib/verification/presentation-definitions/address.ts deleted file mode 100644 index 8b13715..0000000 --- a/lib/verification/presentation-definitions/address.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { fullURL } from "lib/url-fns" - -export const definition = { - id: "AddressPresentationDefinition", - input_descriptors: [ - { - id: "address_input", - name: "Proof of Address Ownership", - purpose: "Please provide a valid credential from a truested issuer", - schema: [ - { - uri: fullURL("/api/schemas/AddressOwner"), - required: true - } - ], - constraints: { - statuses: { - active: { - directive: "required" - } - }, - is_holder: [ - { - field_id: ["subjectId"], - directive: "required" - } - ], - fields: [ - { - path: [ - "$.credentialSubject.AddressOwner.chain", - "$.vc.credentialSubject.AddressOwner.chain", - "$.AddressOwner.chain" - ], - purpose: "The Attestation requires the field: 'chain'.", - predicate: "required", - filter: { - type: "string" - } - }, - { - path: [ - "$.credentialSubject.AddressOwner.address", - "$.vc.credentialSubject.AddressOwner.address", - "$.AddressOwner.address" - ], - purpose: "The Attestation requires the field: 'address'.", - predicate: "required", - filter: { - type: "string" - } - }, - { - path: [ - "$.credentialSubject.AddressOwner.proof", - "$.vc.credentialSubject.AddressOwner.proof", - "$.AddressOwner.proof" - ], - purpose: "The Attestation requires the field: 'proof'.", - predicate: "required", - filter: { - type: "string" - } - } - ] - } - } - ] -} diff --git a/lib/verification/presentation-definitions/counterparty.ts b/lib/verification/presentation-definitions/counterparty.ts deleted file mode 100644 index 27d5de8..0000000 --- a/lib/verification/presentation-definitions/counterparty.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { fullURL } from "lib/url-fns" - -export const definition = { - id: "CounterpartyAccountHolderPresentationDefinition", - input_descriptors: [ - { - id: "counterparty_input", - name: "Counterparty PII Compliance", - purpose: "Please provide a valid credential from a trusted issuer", - schema: [ - { - uri: fullURL("/api/schemas/CounterpartyAccountHolder"), - required: true - } - ], - constraints: { - statuses: { - active: { - directive: "required" - } - }, - is_holder: [ - { - field_id: ["subjectId"], - directive: "required" - } - ], - fields: [ - { - path: [ - "$.credentialSubject.CounterpartyAccountHolder.legalName", - "$.vc.credentialSubject.CounterpartyAccountHolder.legalName", - "$.CounterpartyAccountHolder.legalName" - ], - purpose: "The Attestation requires the field: 'legalName'.", - predicate: "required", - filter: { - type: "string" - } - }, - { - path: [ - "$.credentialSubject.CounterpartyAccountHolder.accountNumber", - "$.vc.credentialSubject.CounterpartyAccountHolder.accountNumber", - "$.CounterpartyAccountHolder.accountNumber" - ], - purpose: "The Attestation requires the field: 'accountNumber'.", - predicate: "required", - filter: { - type: "string" - } - } - ] - } - } - ] -} diff --git a/lib/verification/presentation-definitions/index.ts b/lib/verification/presentation-definitions/index.ts index 594dad2..845a996 100644 --- a/lib/verification/presentation-definitions/index.ts +++ b/lib/verification/presentation-definitions/index.ts @@ -1,15 +1,65 @@ -import { PresentationDefinition } from "verite" +import { + createBasicPresentationDefinitionForProcessAttestation, + PresentationDefinition, + PresentationDefinitionBuilder, + stringValueConstraint, + withDefaults +} from "verite" -import { definition as address } from "./address" -import { definition as counterparty } from "./counterparty" -import { definition as kybaml } from "./kybaml" -import { definition as kycaml } from "./kycaml" +import { AttestationKeys, AttestationTypes } from "lib/constants" -import { CredentialType } from "lib/constants" -export const VERIFICATION_PRESENTATION_DEFINITIONS = { - kycaml, - kybaml, - address, - counterparty -} as Record +export function getPresentationDefinition( + type: AttestationTypes, + trustedAuthorities: string[] = [] +): PresentationDefinition { + + const attestationInfo = type.definition + const prefix = `${type.type}.` + + if (type.id === AttestationKeys.kybpaml) { + return createBasicPresentationDefinitionForProcessAttestation( + "KYBPAMLPresentationDefinition", + type.definition, + "KYBPAMLCredential", + "Proof of KYBP", + "Please provide a valid credential from a KYBP/AML issuer", + trustedAuthorities + ) + } else if (type.id === AttestationKeys.kycaml) { + return createBasicPresentationDefinitionForProcessAttestation( + "KYCAMLPresentationDefinition", + type.definition, + "KYCAMLCredential", + "Proof of KYC", + "Please provide a valid credential from a KYC/AML issuer", + trustedAuthorities + ) + } else if (type.id === AttestationKeys.counterparty) { + return new PresentationDefinitionBuilder({id: "CounterpartyAccountHolderPresentationDefinition"}) + .addInputDescriptor("CounterpartyAccountHolderCredential", (c) => { + c.name("Counterparty PII Compliance") + .purpose("Please provide a valid credential from a trusted issuer") + .withConstraints((b) => { + b.addField(stringValueConstraint("legalName", prefix)) + .addField(stringValueConstraint("accountNumber", prefix)) + }) + .withConstraints(withDefaults(attestationInfo.schema, trustedAuthorities)) + }).build() + + } else if (type.id === AttestationKeys.address) { + return new PresentationDefinitionBuilder({id: "AddressPresentationDefinition"}) + .addInputDescriptor("AddressOwnerCredential", (c) => { + c.name("Proof of Address Ownership") + .purpose("Please provide a valid credential from a truested issuer") + .withConstraints((b) => { + b.addField(stringValueConstraint("chain", prefix)) + .addField(stringValueConstraint("address", prefix)) + .addField(stringValueConstraint("proof", prefix)) + }) + .withConstraints(withDefaults(attestationInfo.schema, trustedAuthorities)) + }).build() + } + throw new Error(`Unrecognized presentation defniition id ${type.id}`) + +} diff --git a/lib/verification/presentation-definitions/kybaml.ts b/lib/verification/presentation-definitions/kybaml.ts deleted file mode 100644 index 96a1634..0000000 --- a/lib/verification/presentation-definitions/kybaml.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { fullURL } from "lib/url-fns" - -export const definition = { - id: "KYBAMLPresentationDefinition", - input_descriptors: [ - { - id: "kybaml_input", - name: "Proof of KYBP", - purpose: "Please provide a valid credential from a KYBP/AML issuer", - schema: [ - { - uri: fullURL("/api/schemas/KYBPAMLAttestation"), - required: true - } - ], - constraints: { - statuses: { - active: { - directive: "required" - } - }, - is_holder: [ - { - field_id: ["subjectId"], - directive: "required" - } - ], - fields: [ - { - path: [ - "$.credentialSubject.KYBPAMLAttestation.process", - "$.vc.credentialSubject.KYBPAMLAttestation.process", - "$.KYBPAMLAttestation.process" - ], - purpose: "The KYB/AML Attestation requires the field: 'process'.", - predicate: "required", - filter: { - type: "string" - } - }, - { - path: [ - "$.credentialSubject.KYBPAMLAttestation.approvalDate", - "$.vc.credentialSubject.KYBPAMLAttestation.approvalDate", - "$.KYBPAMLAttestation.approvalDate" - ], - purpose: - "The KYC/AML Attestation requires the field: 'approvalDate'.", - predicate: "required", - filter: { - type: "string" - } - } - ] - } - } - ] -} diff --git a/lib/verification/presentation-definitions/kycaml.ts b/lib/verification/presentation-definitions/kycaml.ts deleted file mode 100644 index 8c34c8f..0000000 --- a/lib/verification/presentation-definitions/kycaml.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { fullURL } from "lib/url-fns" - -export const definition = { - id: "KYCAMLPresentationDefinition", - input_descriptors: [ - { - id: "kycaml_input", - name: "Proof of KYC", - purpose: "Please provide a valid credential from a KYC/AML issuer", - schema: [ - { - uri: fullURL("/api/schemas/KYCAMLAttestation"), - required: true - } - ], - constraints: { - statuses: { - active: { - directive: "required" - } - }, - is_holder: [ - { - field_id: ["subjectId"], - directive: "required" - } - ], - fields: [ - { - path: [ - "$.credentialSubject.KYCAMLAttestation.process", - "$.vc.credentialSubject.KYCAMLAttestation.process", - "$.KYCAMLAttestation.process" - ], - purpose: "The KYC/AML Attestation requires the field: 'process'.", - predicate: "required", - filter: { - type: "string" - } - }, - { - path: [ - "$.credentialSubject.KYCAMLAttestation.approvalDate", - "$.vc.credentialSubject.KYCAMLAttestation.approvalDate", - "$.KYCAMLAttestation.approvalDate" - ], - purpose: - "The KYC/AML Attestation requires the field: 'approvalDate'.", - predicate: "required", - filter: { - type: "string" - } - } - ] - } - } - ] -} diff --git a/package-lock.json b/package-lock.json index e59077d..b77548b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "react": "18.1.0", "react-dom": "18.1.0", "react-hot-toast": "^2.2.0", - "verite": "^0.0.3" + "verite": "^0.0.4" }, "devDependencies": { "@tailwindcss/forms": "^0.5.2", @@ -8488,9 +8488,9 @@ "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==" }, "node_modules/verite": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/verite/-/verite-0.0.3.tgz", - "integrity": "sha512-Efbro9lKa/aUctg5Y+rOM/h8bLsgMmyCEluWlPEZ5v5FysSzRO+Hxs0vaMy962wEe5ToVbenQgT1mYSjUa1Zeg==", + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/verite/-/verite-0.0.4.tgz", + "integrity": "sha512-5yqc5EllCMs88B6FxeZPVkitjfX76HQegWgIfvU28q10RLzcYHvCsuWI8lLOIkCsbMtLz0e9v6GBUbvCwXZOaA==", "dependencies": { "@stablelib/ed25519": "^1.0.2", "ajv": "^8.6.3", @@ -14825,9 +14825,9 @@ "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==" }, "verite": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/verite/-/verite-0.0.3.tgz", - "integrity": "sha512-Efbro9lKa/aUctg5Y+rOM/h8bLsgMmyCEluWlPEZ5v5FysSzRO+Hxs0vaMy962wEe5ToVbenQgT1mYSjUa1Zeg==", + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/verite/-/verite-0.0.4.tgz", + "integrity": "sha512-5yqc5EllCMs88B6FxeZPVkitjfX76HQegWgIfvU28q10RLzcYHvCsuWI8lLOIkCsbMtLz0e9v6GBUbvCwXZOaA==", "requires": { "@stablelib/ed25519": "^1.0.2", "ajv": "^8.6.3", diff --git a/package.json b/package.json index 210c4b0..847e561 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "react": "18.1.0", "react-dom": "18.1.0", "react-hot-toast": "^2.2.0", - "verite": "^0.0.3" + "verite": "^0.0.4" }, "devDependencies": { "@tailwindcss/forms": "^0.5.2", diff --git a/pages/api/credentials/offer.ts b/pages/api/credentials/offer.ts index 4216f18..6ea3e9c 100644 --- a/pages/api/credentials/offer.ts +++ b/pages/api/credentials/offer.ts @@ -1,11 +1,12 @@ import { buildCredentialOffer } from "verite" import { handler } from "lib/api-fns" +import { AttestationKeys } from "lib/constants" import { findChainId, findCredentialIssuer, findCredentialStatus, - findCredentialType + findAttestationType } from "lib/credential-fns" import { apiDebug } from "lib/debug" import { generateManifest } from "lib/manifest-fns" @@ -19,16 +20,18 @@ import { fullURL } from "lib/url-fns" * offer for the client mobile wallet to scan. */ const endpoint = handler((req, res) => { - const type = findCredentialType(req.query.type as string) + const type = findAttestationType(req.query.type as string) + apiDebug(`Looking up attestation type=${type}`) const issuer = findCredentialIssuer(req.query.issuer as string) const status = findCredentialStatus(req.query.status as string) const chainId = - type.id === "address" ? findChainId(req.query.chain as string) : undefined + type.id === AttestationKeys.address ? findChainId(req.query.chain as string) : undefined const id = [type.id, issuer.id, status.id, chainId?.type] .filter(Boolean) .join("-") - const manifest = generateManifest(type.id, issuer) + const manifest = generateManifest(type, issuer) + apiDebug(`Manifest id=${manifest.id}`) // Wrap the manifest with additional metadata, such as the URL to post the // request to, so the mobile wallet knows how to request the credential. diff --git a/pages/api/credentials/submit.ts b/pages/api/credentials/submit.ts index e297e03..1288804 100644 --- a/pages/api/credentials/submit.ts +++ b/pages/api/credentials/submit.ts @@ -2,19 +2,22 @@ import { buildAndSignFulfillment, buildIssuer, decodeCredentialApplication, - decodeVerifiablePresentation + decodeVerifiablePresentation, + getCredentialSchemaAsVCObject } from "verite" import { handler } from "lib/api-fns" -import { generateAttestation } from "lib/attestation-fns" +import { generateAttestation, getCredentialType } from "lib/attestation-fns" +import { AttestationKeys } from "lib/constants" import { - findCredentialType, + findAttestationType, findCredentialIssuer, findCredentialStatus, expirationDateForStatus, findChainId } from "lib/credential-fns" import { apiDebug } from "lib/debug" +import { generateManifest } from "lib/manifest-fns" import { generateRevocationListStatus } from "lib/revocation-fns" /** @@ -29,11 +32,11 @@ const endpoint = handler(async (req, res) => { return res.status(405).json({ error: "Method not allowed" }) } - const type = findCredentialType(req.query.type as string) + const type = findAttestationType(req.query.type as string) const issuerInfo = findCredentialIssuer(req.query.issuer as string) const status = findCredentialStatus(req.query.status as string) const chainId = - type.id === "address" ? findChainId(req.query.chain as string) : undefined + type.id === AttestationKeys.address ? findChainId(req.query.chain as string) : undefined /** * Get signer (issuer) @@ -46,6 +49,9 @@ const endpoint = handler(async (req, res) => { Buffer.from(issuerInfo.did.secret, "hex") ) + apiDebug(`Issuer generating manifest for type.id=${type.id}`) + const manifest = generateManifest(type, issuerInfo) + /** * Using Presentation Exchange, the client will submit a credential * application. Since we are using JWTs to format the data, we first must @@ -56,10 +62,11 @@ const endpoint = handler(async (req, res) => { /** * Generate the attestation. */ - const attestation = await generateAttestation(type.id, { + const attestation = await generateAttestation(type, { chain: chainId?.type }) - apiDebug("Attestation", JSON.stringify(attestation, null, 2)) + apiDebug(`Generated attestation type=${attestation.type}`) + apiDebug("Generated attestation", JSON.stringify(attestation, null, 2)) // Build a revocation list and index. const revocationListStatus = await generateRevocationListStatus( @@ -67,14 +74,20 @@ const endpoint = handler(async (req, res) => { status.id === "revoked" ) + const credentialType = getCredentialType(attestation.type) + apiDebug(`Issuing credential of type=${credentialType}`) + // Generate the Verifiable Presentation const presentation = await buildAndSignFulfillment( issuer, - application, + application.holder, + manifest, attestation, + credentialType, { credentialStatus: revocationListStatus, - expirationDate: expirationDateForStatus(status) + expirationDate: expirationDateForStatus(status), + credentialSchema: getCredentialSchemaAsVCObject(type.definition) } ) diff --git a/pages/api/verifications/offer.ts b/pages/api/verifications/offer.ts index e3f1338..59c215b 100644 --- a/pages/api/verifications/offer.ts +++ b/pages/api/verifications/offer.ts @@ -1,5 +1,5 @@ import { handler } from "lib/api-fns" -import { findCredentialType, findVerificationStatus } from "lib/credential-fns" +import { findAttestationType, findVerificationStatus } from "lib/credential-fns" import { apiDebug } from "lib/debug" import { generateVerificationOffer } from "lib/verification-fns" @@ -8,7 +8,9 @@ import { generateVerificationOffer } from "lib/verification-fns" * */ const endpoint = handler((req, res) => { - const credentialType = findCredentialType(req.query.type as string) + const requestedType = req.query.type as string + apiDebug(`requested verification type=${requestedType}`) + const attestationType = findAttestationType(requestedType) const subjectAddress = req.query.subjectAddress as string const chainId = req.query.chainId as string const statusEndpointResult = findVerificationStatus( @@ -19,7 +21,7 @@ const endpoint = handler((req, res) => { : [] const offer = generateVerificationOffer({ - credentialType, + attestationType: attestationType, trustedIssuers, statusEndpointResult, subjectAddress, diff --git a/pages/api/verifications/submit.ts b/pages/api/verifications/submit.ts index 4936f48..c7e53e9 100644 --- a/pages/api/verifications/submit.ts +++ b/pages/api/verifications/submit.ts @@ -6,7 +6,8 @@ import { import { handler } from "lib/api-fns" import { CHAIN_IDS, CREDENTIAL_VERIFIERS } from "lib/constants" -import { findCredentialType, findVerificationStatus } from "lib/credential-fns" +import { findAttestationType, findVerificationStatus } from "lib/credential-fns" +import { apiDebug } from "lib/debug" import { SCHEMAS } from "lib/schemas" import { fullURL } from "lib/url-fns" import { generateVerificationOffer } from "lib/verification-fns" @@ -16,7 +17,7 @@ import { generateVerificationOffer } from "lib/verification-fns" * */ const endpoint = handler(async (req, res) => { - const credentialType = findCredentialType(req.query.type as string) + const credentialType = findAttestationType(req.query.type as string) const knownSchemas = { [fullURL(`/api/schemas/${credentialType.type}`)]: SCHEMAS[ credentialType.type @@ -35,13 +36,16 @@ const endpoint = handler(async (req, res) => { // find the verification offer const verificationOffer = generateVerificationOffer({ - credentialType, + attestationType: credentialType, trustedIssuers, statusEndpointResult, subjectAddress, chainId }) + apiDebug(`Generated VerificationOffer with id=${verificationOffer.id}`) + apiDebug(JSON.stringify(verificationOffer)) + if (!verificationOffer) { // unable to find a verification offer with these params res.status(404).send("") @@ -49,6 +53,8 @@ const endpoint = handler(async (req, res) => { } // verify offer + // TODO: may need to fix this + apiDebug(`verificationSubmission=${JSON.stringify(submission)}`) try { await validateVerificationSubmission( submission, diff --git a/pages/index.tsx b/pages/index.tsx index 2dff012..4aeaead 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -10,10 +10,11 @@ import { CHAIN_IDS, CredentialIssuer, CredentialStatus, - CredentialType, + AttestationTypes, CREDENTIAL_ISSUERS, CREDENTIAL_STATUSES, - CREDENTIAL_TYPES + ATTESTATION_TYPES, + AttestationKeys } from "lib/constants" import { fullURL } from "lib/url-fns" @@ -93,8 +94,8 @@ const faqs: FAQType[] = [ const Page: NextPage = () => { const [chainId, setChainId] = useState(CHAIN_IDS[0]) - const [customType, setCustomType] = useState( - CREDENTIAL_TYPES[0] + const [customType, setCustomType] = useState( + ATTESTATION_TYPES[0] ) const [customIssuer, setCustomIssuer] = useState( CREDENTIAL_ISSUERS[0] @@ -110,7 +111,7 @@ const Page: NextPage = () => { status: customStatus.id }) - if (customType.id === "address") { + if (customType.id === AttestationKeys.address) { params.append("chain", chainId.type) } @@ -139,13 +140,13 @@ const Page: NextPage = () => { - {customType.id === "address" && ( + {customType.id === AttestationKeys.address && ( <>
{ CREDENTIAL_ISSUERS.filter((i) => i.isTrusted).map((i) => i.did.key) ) const [customTrustedIssuer, setCustomTrustedIssuer] = useState("") - const [customType, setCustomType] = useState( - CREDENTIAL_TYPES[0] + const [customType, setCustomType] = useState( + ATTESTATION_TYPES[0] ) const [verificationStatus, setVerificationStatus] = useState(VERIFICATION_STATUSES[0]) @@ -165,7 +165,7 @@ const VerifierPage: NextPage = () => {