Skip to content

Commit

Permalink
feat(w3c): add custom document loader option (#1159)
Browse files Browse the repository at this point in the history
Signed-off-by: Timo Glastra <[email protected]>
TimoGlastra authored Dec 16, 2022
1 parent 5e48696 commit ff6abdf
Showing 14 changed files with 213 additions and 83 deletions.
11 changes: 5 additions & 6 deletions packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts
Original file line number Diff line number Diff line change
@@ -5,7 +5,6 @@ import {
VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018,
KeyType,
JsonTransformer,
DidResolverService,
DidKey,
Key,
SigningProviderRegistry,
@@ -24,6 +23,7 @@ import {
} from '@aries-framework/core'

import { SignatureSuiteRegistry } from '../../core/src/modules/vc/SignatureSuiteRegistry'
import { W3cVcModuleConfig } from '../../core/src/modules/vc/W3cVcModuleConfig'
import { customDocumentLoader } from '../../core/src/modules/vc/__tests__/documentLoader'
import { getAgentConfig, getAgentContext } from '../../core/tests/helpers'
import { BbsBlsSignature2020, BbsBlsSignatureProof2020, Bls12381g2SigningProvider } from '../src'
@@ -62,7 +62,6 @@ const agentConfig = getAgentConfig('BbsSignaturesE2eTest')
describeSkipNode17And18('BBS W3cCredentialService', () => {
let wallet: IndyWallet
let agentContext: AgentContext
let didResolverService: DidResolverService
let w3cCredentialService: W3cCredentialService
const seed = 'testseed000000000000000000000001'

@@ -74,13 +73,13 @@ describeSkipNode17And18('BBS W3cCredentialService', () => {
agentConfig,
wallet,
})
didResolverService = new DidResolverService(agentConfig.logger, [])
w3cCredentialService = new W3cCredentialService(
{} as unknown as W3cCredentialRepository,
didResolverService,
signatureSuiteRegistry
signatureSuiteRegistry,
new W3cVcModuleConfig({
documentLoader: customDocumentLoader,
})
)
w3cCredentialService.documentLoaderWithContext = () => customDocumentLoader
})

afterAll(async () => {
Original file line number Diff line number Diff line change
@@ -14,18 +14,36 @@ import testLogger from '../../../../../../tests/logger'
import { Agent } from '../../../../../agent/Agent'
import { InjectionSymbols } from '../../../../../constants'
import { Ed25519Signature2018Fixtures } from '../../../../../modules/vc/__tests__/fixtures'
import { W3cVcModule } from '../../../../vc'
import { customDocumentLoader } from '../../../../vc/__tests__/documentLoader'
import { W3cCredential } from '../../../../vc/models/'
import { CredentialEventTypes } from '../../../CredentialEvents'
import { CredentialState } from '../../../models'
import { CredentialExchangeRecord } from '../../../repository'

const faberAgentOptions = getAgentOptions('Faber LD connection-less Credentials V2', {
endpoints: ['rxjs:faber'],
})

const aliceAgentOptions = getAgentOptions('Alice LD connection-less Credentials V2', {
endpoints: ['rxjs:alice'],
})
const faberAgentOptions = getAgentOptions(
'Faber LD connection-less Credentials V2',
{
endpoints: ['rxjs:faber'],
},
{
w3cVc: new W3cVcModule({
documentLoader: customDocumentLoader,
}),
}
)

const aliceAgentOptions = getAgentOptions(
'Alice LD connection-less Credentials V2',
{
endpoints: ['rxjs:alice'],
},
{
w3cVc: new W3cVcModule({
documentLoader: customDocumentLoader,
}),
}
)

let wallet
let credential: W3cCredential
77 changes: 23 additions & 54 deletions packages/core/src/modules/vc/W3cCredentialService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { AgentContext } from '../../agent/context'
import type { Key } from '../../crypto/Key'
import type { Query } from '../../storage/StorageService'
import type { DocumentLoader } from './jsonldUtil'
import type { W3cVerifyCredentialResult } from './models'
import type {
CreatePresentationOptions,
@@ -18,13 +17,13 @@ import { createWalletKeyPairClass } from '../../crypto/WalletKeyPair'
import { AriesFrameworkError } from '../../error'
import { injectable } from '../../plugins'
import { JsonTransformer } from '../../utils'
import { DidResolverService, VerificationMethod } from '../dids'
import { VerificationMethod } from '../dids'
import { getKeyDidMappingByVerificationMethod } from '../dids/domain/key-type'

import { SignatureSuiteRegistry } from './SignatureSuiteRegistry'
import { W3cVcModuleConfig } from './W3cVcModuleConfig'
import { deriveProof } from './deriveProof'
import { orArrayToArray, w3cDate } from './jsonldUtil'
import { getDocumentLoader } from './libraries/documentLoader'
import jsonld from './libraries/jsonld'
import vc from './libraries/vc'
import { W3cVerifiableCredential } from './models'
@@ -35,17 +34,17 @@ import { W3cCredentialRecord, W3cCredentialRepository } from './repository'
@injectable()
export class W3cCredentialService {
private w3cCredentialRepository: W3cCredentialRepository
private didResolver: DidResolverService
private suiteRegistry: SignatureSuiteRegistry
private signatureSuiteRegistry: SignatureSuiteRegistry
private w3cVcModuleConfig: W3cVcModuleConfig

public constructor(
w3cCredentialRepository: W3cCredentialRepository,
didResolver: DidResolverService,
suiteRegistry: SignatureSuiteRegistry
signatureSuiteRegistry: SignatureSuiteRegistry,
w3cVcModuleConfig: W3cVcModuleConfig
) {
this.w3cCredentialRepository = w3cCredentialRepository
this.didResolver = didResolver
this.suiteRegistry = suiteRegistry
this.signatureSuiteRegistry = signatureSuiteRegistry
this.w3cVcModuleConfig = w3cVcModuleConfig
}

/**
@@ -61,7 +60,7 @@ export class W3cCredentialService {
const WalletKeyPair = createWalletKeyPairClass(agentContext.wallet)

const signingKey = await this.getPublicKeyFromVerificationMethod(agentContext, options.verificationMethod)
const suiteInfo = this.suiteRegistry.getByProofType(options.proofType)
const suiteInfo = this.signatureSuiteRegistry.getByProofType(options.proofType)

if (!suiteInfo.keyTypes.includes(signingKey.keyType)) {
throw new AriesFrameworkError('The key type of the verification method does not match the suite')
@@ -90,7 +89,7 @@ export class W3cCredentialService {
credential: JsonTransformer.toJSON(options.credential),
suite: suite,
purpose: options.proofPurpose,
documentLoader: this.documentLoaderWithContext(agentContext),
documentLoader: this.w3cVcModuleConfig.documentLoader(agentContext),
})

return JsonTransformer.fromJSON(result, W3cVerifiableCredential)
@@ -111,7 +110,7 @@ export class W3cCredentialService {
const verifyOptions: Record<string, unknown> = {
credential: JsonTransformer.toJSON(options.credential),
suite: suites,
documentLoader: this.documentLoaderWithContext(agentContext),
documentLoader: this.w3cVcModuleConfig.documentLoader(agentContext),
}

// this is a hack because vcjs throws if purpose is passed as undefined or null
@@ -161,7 +160,7 @@ export class W3cCredentialService {
// create keyPair
const WalletKeyPair = createWalletKeyPairClass(agentContext.wallet)

const suiteInfo = this.suiteRegistry.getByProofType(options.signatureType)
const suiteInfo = this.signatureSuiteRegistry.getByProofType(options.signatureType)

if (!suiteInfo) {
throw new AriesFrameworkError(`The requested proofType ${options.signatureType} is not supported`)
@@ -173,7 +172,7 @@ export class W3cCredentialService {
throw new AriesFrameworkError('The key type of the verification method does not match the suite')
}

const documentLoader = this.documentLoaderWithContext(agentContext)
const documentLoader = this.w3cVcModuleConfig.documentLoader(agentContext)
const verificationMethodObject = (await documentLoader(options.verificationMethod)).document as Record<
string,
unknown
@@ -200,7 +199,7 @@ export class W3cCredentialService {
presentation: JsonTransformer.toJSON(options.presentation),
suite: suite,
challenge: options.challenge,
documentLoader: this.documentLoaderWithContext(agentContext),
documentLoader: this.w3cVcModuleConfig.documentLoader(agentContext),
})

return JsonTransformer.fromJSON(result, W3cVerifiablePresentation)
@@ -229,7 +228,7 @@ export class W3cCredentialService {
}

const presentationSuites = proofs.map((proof) => {
const SuiteClass = this.suiteRegistry.getByProofType(proof.type).suiteClass
const SuiteClass = this.signatureSuiteRegistry.getByProofType(proof.type).suiteClass
return new SuiteClass({
LDKeyClass: WalletKeyPair,
proof: {
@@ -253,7 +252,7 @@ export class W3cCredentialService {
presentation: JsonTransformer.toJSON(options.presentation),
suite: allSuites,
challenge: options.challenge,
documentLoader: this.documentLoaderWithContext(agentContext),
documentLoader: this.w3cVcModuleConfig.documentLoader(agentContext),
}

// this is a hack because vcjs throws if purpose is passed as undefined or null
@@ -268,54 +267,24 @@ export class W3cCredentialService {

public async deriveProof(agentContext: AgentContext, options: DeriveProofOptions): Promise<W3cVerifiableCredential> {
// TODO: make suite dynamic
const suiteInfo = this.suiteRegistry.getByProofType('BbsBlsSignatureProof2020')
const suiteInfo = this.signatureSuiteRegistry.getByProofType('BbsBlsSignatureProof2020')
const SuiteClass = suiteInfo.suiteClass

const suite = new SuiteClass()

const proof = await deriveProof(JsonTransformer.toJSON(options.credential), options.revealDocument, {
suite: suite,
documentLoader: this.documentLoaderWithContext(agentContext),
documentLoader: this.w3cVcModuleConfig.documentLoader(agentContext),
})

return proof
}

public documentLoaderWithContext = (agentContext: AgentContext): DocumentLoader => {
return async (url: string) => {
if (url.startsWith('did:')) {
const result = await this.didResolver.resolve(agentContext, url)

if (result.didResolutionMetadata.error || !result.didDocument) {
throw new AriesFrameworkError(`Unable to resolve DID: ${url}`)
}

const framed = await jsonld.frame(result.didDocument.toJSON(), {
'@context': result.didDocument.context,
'@embed': '@never',
id: url,
})

return {
contextUrl: null,
documentUrl: url,
document: framed,
}
}

// fetches the documentLoader from documentLoader.ts or documentLoader.native.ts depending on the platform at bundle time
const platformLoader = getDocumentLoader()
const loader = platformLoader.apply(jsonld, [])

return await loader(url)
}
}

private async getPublicKeyFromVerificationMethod(
agentContext: AgentContext,
verificationMethod: string
): Promise<Key> {
const documentLoader = this.documentLoaderWithContext(agentContext)
const documentLoader = this.w3cVcModuleConfig.documentLoader(agentContext)
const verificationMethodObject = await documentLoader(verificationMethod)
const verificationMethodClass = JsonTransformer.fromJSON(verificationMethodObject.document, VerificationMethod)

@@ -337,7 +306,7 @@ export class W3cCredentialService {
// Get the expanded types
const expandedTypes = (
await jsonld.expand(JsonTransformer.toJSON(options.credential), {
documentLoader: this.documentLoaderWithContext(agentContext),
documentLoader: this.w3cVcModuleConfig.documentLoader(agentContext),
})
)[0]['@type']

@@ -375,11 +344,11 @@ export class W3cCredentialService {
}

public getVerificationMethodTypesByProofType(proofType: string): string[] {
return this.suiteRegistry.getByProofType(proofType).verificationMethodTypes
return this.signatureSuiteRegistry.getByProofType(proofType).verificationMethodTypes
}

public getKeyTypesByProofType(proofType: string): string[] {
return this.suiteRegistry.getByProofType(proofType).keyTypes
return this.signatureSuiteRegistry.getByProofType(proofType).keyTypes
}

public async findCredentialRecordByQuery(
@@ -400,7 +369,7 @@ export class W3cCredentialService {
}

return proofs.map((proof) => {
const SuiteClass = this.suiteRegistry.getByProofType(proof.type)?.suiteClass
const SuiteClass = this.signatureSuiteRegistry.getByProofType(proof.type)?.suiteClass
if (SuiteClass) {
return new SuiteClass({
LDKeyClass: WalletKeyPair,
11 changes: 11 additions & 0 deletions packages/core/src/modules/vc/W3cVcModule.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { DependencyManager, Module } from '../../plugins'
import type { W3cVcModuleConfigOptions } from './W3cVcModuleConfig'

import { KeyType } from '../../crypto'
import {
@@ -8,16 +9,26 @@ import {

import { SignatureSuiteRegistry, SignatureSuiteToken } from './SignatureSuiteRegistry'
import { W3cCredentialService } from './W3cCredentialService'
import { W3cVcModuleConfig } from './W3cVcModuleConfig'
import { W3cCredentialRepository } from './repository/W3cCredentialRepository'
import { Ed25519Signature2018 } from './signature-suites'

export class W3cVcModule implements Module {
public readonly config: W3cVcModuleConfig

public constructor(config?: W3cVcModuleConfigOptions) {
this.config = new W3cVcModuleConfig(config)
}

public register(dependencyManager: DependencyManager) {
dependencyManager.registerSingleton(W3cCredentialService)
dependencyManager.registerSingleton(W3cCredentialRepository)

dependencyManager.registerSingleton(SignatureSuiteRegistry)

// Register the config
dependencyManager.registerInstance(W3cVcModuleConfig, this.config)

// Always register ed25519 signature suite
dependencyManager.registerInstance(SignatureSuiteToken, {
suiteClass: Ed25519Signature2018,
46 changes: 46 additions & 0 deletions packages/core/src/modules/vc/W3cVcModuleConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { DocumentLoaderWithContext } from './libraries/documentLoader'

import { defaultDocumentLoader } from './libraries/documentLoader'

/**
* W3cVcModuleConfigOptions defines the interface for the options of the W3cVcModuleConfig class.
* This can contain optional parameters that have default values in the config class itself.
*/
export interface W3cVcModuleConfigOptions {
/**
* Document loader to use for resolving JSON-LD objects. Takes a {@link AgentContext} as parameter,
* and must return a {@link DocumentLoader} function.
*
* @example
* ```
* const myDocumentLoader = (agentContext: AgentContext) => {
* return async (url) => {
* if (url !== 'https://example.org') throw new Error("I don't know how to load this document")
*
* return {
* contextUrl: null,
* documentUrl: url,
* document: null
* }
* }
* }
* ```
*
*
* @default {@link defaultDocumentLoader}
*/
documentLoader?: DocumentLoaderWithContext
}

export class W3cVcModuleConfig {
private options: W3cVcModuleConfigOptions

public constructor(options?: W3cVcModuleConfigOptions) {
this.options = options ?? {}
}

/** See {@link W3cVcModuleConfigOptions.documentLoader} */
public get documentLoader() {
return this.options.documentLoader ?? defaultDocumentLoader
}
}
Loading

0 comments on commit ff6abdf

Please sign in to comment.