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
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);
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: 'ES256',
name: 'holder-key-hw',
organisationId: organisationId,
storageParams: {},
storageType: 'SECURE_ELEMENT',
});
Create a DID by which the wallet will identify itself as an agent. Choose a DID
method and assign the keyId
from (2) for all verification methods in the keys
object. The system supports the creation of as many DIDs as is needed, but only
one DID is needed to start accepting credentials and submitting proofs.
const didId = await core.createDid({
didMethod: 'KEY',
keys: {
assertionMethod: [keyId],
authentication: [keyId],
capabilityDelegation: [keyId],
capabilityInvocation: [keyId],
keyAgreement: [keyId],
},
name: 'holder-did-hw-key',
organisationId: organisationId,
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
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 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);
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
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 the proof request details so the wallet holder can see what is being requested.
const { interactionId, proofId } = invitation;
const proofDetails = await core.getProof(proofId);
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));
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);