Issue an AnonCreds credential over DIDComm
In this tutorial we will issue an AnonCreds credential from the Issuer to a Holder over DIDComm. We will start with setting up both their agents with the minimal configuration required to follow this tutorial. It is assumed that there is a connection between the Issuer and the Holder and the Issuer also has a registered schema and credential definition. After initializing the Issuer will send a credential to the holder, and will then accept this credential and automatically store it in their wallet.
Using AnonCreds and the Issue Credential V2 Protocol or the Issue Credential V1 Protocol.
This section assumes that
- You have set-up your development environment.
- You have basic knowledge of the required fields in the Agent Config
- You have completed the Create a Connection tutorial
- You have a registered schema and credential definition. This can be done by following the Registering on a AnonCreds Registry
1. Setting up the agents
First for both agents we must setup and initialize an agent to work with. Depending on your target, React Native or Node.js, it might vary.
In this tutorial the Holder will be in a Node.js environment and the Issuer also in a Node.js environment.
Issuer
For the Issuer the setup is commonly not the same as the Holder. In the example they both live in a server environment, meaning they do not need a mediator. More commonly, the Holder is in a mobile environment where a mediator is required for receiving DIDComm messages.
It is also very important for the Issuer to have a public DID, for the binding with a credential definition, amongst other things. For this demo we will use BCovrin Test. If you want to follow this tutorial, you have to register a public DID here via the Wallet seed field (this must be the same as the seed inside the config under the key publicDidSeed
).
In order to reach the Issuer we have to add a list of endpoints
of the agent that exposes the inboundTransport
to the public. In the example below we add an inboundTransport
and use port 3002
. For development purposes it is recommended to use a tunneling service for this, like Ngrok. Ngrok will allow you to reach your locally exposed endpoint from the public. If a tunneling service is used, make sure to use the HTTPS
variant as mobile environments, by default, do not accept HTTP
anymore.
To install Ngrok and expose the port to the public the following commands can be used:
- yarn
- npm
yarn global add ngrok
ngrok http <PORT>
npm install --global ngrok
ngrok http <PORT>
const issuer = new Agent({
config: issuerConfig,
dependencies: agentDependencies,
modules: {
askar: new AskarModule({
ariesAskar,
}),
indyVdr: new IndyVdrModule({
indyVdr,
networks: [
{
isProduction: false,
indyNamespace: 'bcovrin:test',
genesisTransactions: '<genesis transaction>',
connectOnStartup: true,
},
],
}),
anoncreds: new AnonCredsModule({
registries: [new IndyVdrAnonCredsRegistry()],
anoncreds,
}),
dids: new DidsModule({
registrars: [new IndyVdrIndyDidRegistrar()],
resolvers: [new IndyVdrIndyDidResolver()],
}),
credentials: new CredentialsModule({
credentialProtocols: [
new V2CredentialProtocol({
credentialFormats: [new LegacyIndyCredentialFormatService(), new AnonCredsCredentialFormatService()],
}),
],
}),
},
})
// Register a simple `WebSocket` outbound transport
issuer.registerOutboundTransport(new WsOutboundTransport())
// Register a simple `Http` outbound transport
issuer.registerOutboundTransport(new HttpOutboundTransport())
// Register a simple `Http` inbound transport
issuer.registerInboundTransport(new HttpInboundTransport({ port: 3002 }))
Holder
For the Holder we need to setup a basic agent with a wallet, mediator, outbound transport and a ledger.
If you want to follow this tutorial in a mobile environment:
const holder = new Agent({
config: holderConfig,
dependencies: agentDependencies,
modules: {
askar: new AskarModule({
ariesAskar,
}),
indyVdr: new IndyVdrModule({
indyVdr,
networks: [
{
isProduction: false,
indyNamespace: 'bcovrin:test',
genesisTransactions: '<genesis transaction>',
connectOnStartup: true,
},
],
}),
anoncreds: new AnonCredsModule({
registries: [new IndyVdrAnonCredsRegistry()],
anoncreds,
}),
dids: new DidsModule({
resolvers: [new IndyVdrIndyDidResolver()],
}),
credentials: new CredentialsModule({
credentialProtocols: [
new V2CredentialProtocol({
credentialFormats: [new LegacyIndyCredentialFormatService(), new AnonCredsCredentialFormatService()],
}),
],
}),
},
})
// Register a simple `WebSocket` outbound transport
holder.registerOutboundTransport(new WsOutboundTransport())
// Register a simple `Http` outbound transport
holder.registerOutboundTransport(new HttpOutboundTransport())
// Register a simple `Http` inbound transport
holder.registerInboundTransport(new HttpInboundTransport({ port: 3002 }))
3. Listening for incoming credentials
When we want to accept a credential, we have to listen to incoming credentials and handle accordingly. In this example we do not have any user interaction, but is likely that your application would have a user-interface which would display the credential. When receiving a credential offer you can get the values from credentialExchangeRecord.credentialAttributes
.
holder.events.on<CredentialStateChangedEvent>(CredentialEventTypes.CredentialStateChanged, async ({ payload }) => {
switch (payload.credentialRecord.state) {
case CredentialState.OfferReceived:
console.log('received a credential')
// custom logic here
await holder.credentials.acceptOffer({ credentialRecordId: payload.credentialRecord.id })
break
case CredentialState.Done:
console.log(`Credential for credential id ${payload.credentialRecord.id} is accepted`)
// For demo purposes we exit the program here.
process.exit(0)
}
})
4. Issuing a credential
Now that everything is setup on both sides, the Issuer can now offer a credential to the Holder.
In this example we do not instantiate a connection and assume that there is one. Please refer to this guide Create a connection to get a connection and connectionId.
- Indy
- AnonCreds
const indyCredentialExchangeRecord = await issuer.credentials.offerCredential({
protocolVersion: 'v2',
connectionId: '<connection id>',
credentialFormats: {
indy: {
credentialDefinitionId: '<credential definition id>',
attributes: [
{ name: 'name', value: 'Jane Doe' },
{ name: 'age', value: '23' },
],
},
},
})
const anonCredsCredentialExchangeRecord = issuer.credentials.offerCredential({
protocolVersion: 'v2',
connectionId: '<connection id>',
credentialFormats: {
anoncreds: {
credentialDefinitionId: '<credential definition id>',
attributes: [
{ name: 'name', value: 'Jane Doe' },
{ name: 'age', value: '23' },
],
},
},
})