Wallet workflow
This page provides a walkthrouth of a simple wallet workflow with the Procivis One SDK.
Process overview
Prerequisites
The SDK is installed and initialized.
Initialize SDK
import { initializeHolderCore } from '@procivis/react-native-one-core';
const core = await initializeHolderCore();
A. Become an agent
Before the wallet can accept credentials and present proofs, it first needs to become an agent:
Create an organization → Generate a key → Create a DID = Agency
Create an organization
The fundamental unit of agency in the Procivis One Core is the organization: all entities created and actions taken belong to only one organization. Though the system supports the creation of as many organizations as is needed, the typical digital credentials wallet requires only one organization as it will only act as a single agent.
const organisationId = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee';
await core.createOrganisation(organisationId);
Generate a key
The goal is to create a decentralized identifier (DID) for the wallet to use in interactions with issuers and verifiers, and DIDs rely on asymmetric encryption. Generating a key creates a public/private key pair that will later be associated with a DID and used for authentication of the DID subject.
Only one key pair needs to be generated.
const keyId = await core.generateKey({
keyParams: {},
keyType: 'ECDSA',
name: 'holder-key-hw',
organisationId: organisationId,
storageParams: {},
storageType: 'SECURE_ELEMENT',
});
Create an identifier
Create an identifier by which the wallet will identify itself as an agent.
The system supports the use of keys, DIDs, and certificates as identifiers.
For this workflow we will use a DID. Choose a DID method and assign the
keyId
from (2) for all verification methods in the key
object.
const identifierId = await core.createIdentifier({
organisationId: organisationId,
name: 'holder-identifier-hw-key',
keyId: keyId,
did: {
name: 'holder-did-hw-key',
method: 'KEY',
keys: {
authentication: [keyId],
assertionMethod: [keyId],
keyAgreement: [keyId],
capabilityInvocation: [keyId],
capabilityDelegation: [keyId],
},
params: {},
},
});
B. Accept a credential
Now that the wallet is an agent, it can take part in an issuance workflow:
Handle invitation → Get credential → Accept the issuance
Handle invitation
Whether responding to an offer of credentials or a request for proof, the
interaction starts with handling the invitation. Use the URL from the
credential offer, typically encoded as a QR code or a deep link, and the
didId
value from (3). The interactionId
is returned along with the
offered credentials.
const invitationUrl = 'https://example.com/invitation' // Get invitation URL from QR code or deep link
const invitation = await core.handleInvitation(invitationUrl, organisationId),
Get credential
Get the credential details so the wallet holder can decide whether to accept the credential or reject it.
const { interactionId, credentialIds } = invitation;
const credentialId = credentialIds[0];
const credential = await core.getCredential(credentialId);
Accept the credential
Use this function to accept the offered credential.
await core.holderAcceptCredential(interactionId, didId, keyId);
Of course, the holder can also reject an offered credential.
await core.holderRejectCredential(interactionId);
C. Submit a proof
Now that the wallet has a credential, it can submit a proof to a verifier:
Handle invitation → Get proof request → Get presentation definition → Submit proof
Handle invitation
Use the URL from the request, typically encoded as a QR code or a deep link,
and the didId
value from (3). The interactionId
is returned along with the
proof request.
const invitationUrl = 'https://example.com/invitation' // Get invitation URL from QR code or deep link
const invitation = await core.handleInvitation(invitationUrl, organisationId),
Get proof request
Get the proof request details so the wallet holder can see what is being requested.
const { interactionId, proofId } = invitation;
const proofDetails = await core.getProof(proofId);
Get presentation definition
Pass the proofId
as a parameter for this function to filter the wallet,
returning credentials which qualify to be submitted to the request.
const presentationDefinition = await core.getPresentationDefinition(proofId);
// refresh revocation status of the applicable credentials
const credentialIds = new Set<string>(
definition.requestGroups.flatMap(({ requestedCredentials }) =>
requestedCredentials.flatMap(
({ applicableCredentials }) => applicableCredentials,
),
),
);
await core.checkRevocation(Array.from(credentialIds));
Submit proof
Use this function to submit the chosen set of credentials to the verifier.
const credentials: Record<
PresentationDefinitionRequestedCredential['id'],
PresentationSubmitCredentialRequest | undefined
> = {};
presentationDefinition.requestGroups.forEach((group) =>
group.requestedCredentials.forEach((credential) => {
const credentialId =
allCredentials.find(
({ id, state }) =>
state === CredentialStateEnum.ACCEPTED &&
credential.applicableCredentials.includes(id),
)?.id ?? credential.applicableCredentials[0];
if (!credentialId) {
credentials[credential.id] = undefined;
return;
}
const requiredClaims = credential.fields
.filter((field) => field.required)
.map((field) => field.id);
credentials[credential.id] = {
credentialId,
submitClaims: requiredClaims,
};
}),
);
await core.holderSubmitProof(interactionId, credentials, didId, keyId);
Of course, the holder can also reject the proof request.
await core.holderRejectProof(interactionId);