Connection is a communication channel between two parties. During the connection establishing process, both parts exchange their public keys. This exchange results in both parties recording the other's public key, thereby forming a unique pairwise encryption channel. That keys will be used for messages encryption during later communications between parties. Mobile SDK can form an unlimited number of connections in this way.
There are two roles in the connection establishing process: Inviter and Invitee.
- The Inviter is the party that initiates the connection protocol with an Invitation message and transfer it in some usual way (SMS, QR, etc.). Verity SDK can be used as an Inviter.
- The Invitee is the party that accepts the invitation to establish the connection. Mobile SDK represents the Invitee party. Bellow in this document we will explain which steps need to be taken in order to accept a connection invitation on the Invitee side using Mobile SDK.
NOTE: library should be initialized before using credentials API. See initialization documentation
To complete this section read through the following sections:
Aries Connection and Out-of-Band protocols consist of several messages exchange:
- Inviter prepares
Connection Invitation
message and shares it with an Invitee some ual way (SMS, QR, etc.) - Invitee sends
Connection Request
message to Inviter - Inviter handles
Connection Request
message and sendsConnection Response
message to Invitee - Invitee handles
Connection Response
message and sendsConnection Ack
message (if requested) to Inviter
In order to accept a Connection Invitation
an Invitee (client) need to take the following steps:
- Check type of invitation: aries connection and aries out-of-band formats are supported.
- Ensure that connection has not been established yet.
- If there is no existing connection:
3.1. Depending on the invitation type call corresponding method to create SDK state object. 3.2. Await Connection is completed. - If there is an existing connection:
4.1. Reuse existing connection.
NOTE: The library should be initialized before forming a connection. See initialization documentation
As we mentioned before in the Storage document SDK does not save state objects, so the application must care about it.
It is up to the developer regarding what data to store in the application.
On of the possible formats may match the following structure:
{
"pwDid" - string, // connection pairwise DID - uniquely identifies pairwise connection
"serialized" - string, // serialized representation of SDK Connection state object
// metadata to show on the UI
"name" - string, // inviter name
"logo" - string, // (optional) inviter logo (optional - maybe missed)
"goal" - string, // A text defining the goal of the connection establishing (optional - maybe missed)
// optionally
"timestamp" - int // time of establishing of the connection (it can be shown on the UI and used for sorting)
"status" - string, // connection status (pending / completed)
"invitation" - string // original invitation was used to establish the connection,
It will simply check for already accepted invitations
}
Later in this document, we will show how to get each of these fields.
The value of @type
field must end with connections/1.0/invitation
.
To send this type of invitation from the Verity application, you need to use this API call with this body payload.
{
// type of the message
"@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/connections/1.0/invitation",
// id of the message
"@id": "467f6449-7d1f-4a9f-ada7-09d6444af083",
// name of the Inviter
"label": "Acme",
// URL with the Inviter logo (Optional)
"profileUrl": "https://s3.us-east-2.amazonaws.com/public-demo-artifacts/demo-icons/cbACME.png",
// Endpoint of the Inviter Agnet
"serviceEndpoint": "http://vas.evernym.com:80/agency/msg",
// Public key of the Inviter to encrypt messages
"recipientKeys": [
"9NR9NYh5z5HHu6nLHnQWXczUqRwrdoL4KBUWvo2fE6vj"
],
// Keys defining the Inviter Agent (Optional)
"routingKeys": [
"9NR9NYh5z5HHu6nLHnQWXczUqRwrdoL4KBUWvo2fE6vj",
"3mo3P6XzDzBvuktCgDQarACzzeV7zxrSExnicpuH7t83"
]
}
More information about Aries Connection invitation and related protocol you can find here.
The value of @type
field must end with /out-of-band/1.0/invitation
.
To send this type of invitation from the Verity application without a handshake protocol, you need to use this API call with this body payload.
This type of invitation can also be sent with a handshake protocol from the Verity application by using the out-of-band with a request attached. This is done by adding "by_invitation": tru to a body payload of the Present Proof API call or Issue Credential API call.
{
// type of the message
"@type": "https://didcomm.org/out-of-band/1.0/invitation",
// id of the message
"@id": "9NR9NYh5z5HHu6nLHnQWXczUqRwrdoL4KBUWvo2fE6vj",
// name of the Inviter
"label": "Faber College",
// invitation goal code defined by the Inviter (Optional)
"goal_code": "issue-vc",
// a text defining the goal of the connection establishing (Optional)
"goal": "To issue a Faber College Graduate credential",
// Protocols can be used to establish a connection
"handshake_protocols": [
"https://didcomm.org/didexchange/1.0",
"https://didcomm.org/connections/1.0"
],
// An additional message attached to the invitation (Optional)
"request~attach": [
{
"@id": "request-0",
"mime-type": "application/json",
"data": {
"json": "<json of protocol message>"
}
}
],
// Service of the Inviter Agent
"service": [
{
"id": "#inline",
"type": "did-communication",
// Endpoint of the Inviter Agnet
"serviceEndpoint": "http://vas.evernym.com:80/agency/msg",
// Public key of the Inviter to encrypt messages
"recipientKeys": [
"9NR9NYh5z5HHu6nLHnQWXczUqRwrdoL4KBUWvo2fE6vj"
],
// Keys defining the Inviter Agent (Optional)
"routingKeys": [
"9NR9NYh5z5HHu6nLHnQWXczUqRwrdoL4KBUWvo2fE6vj",
"3mo3P6XzDzBvuktCgDQarACzzeV7zxrSExnicpuH7t83"
]
},
]
}
More information about Aries Out-Of-Band invitation and related protocol you can find here.
The following data can be taken from the Invitation to fill the application Connection object we defined before:
invite["label"]
- Name of the inviter (name
field of application Connection object)invite["profileUrl"]
- URL with inviter logo (optional - maybe missed in the invitation) (logo
field of application Connection object)invite["goal"]
- A text defining the goal of the connection establishing (optional - maybe missed in the invitation) (goal
field of application Connection object)
Both invitation types can also be represented in form of URL:
- Aries Connection invitation - https://<domain>/<path>?c_i=[Base-64 encoded invitation]
- Aries Out-Of-Band invitation - https://<domain>/<path>?oob=[Base-64 encoded invitation]
- Invitation without Base-64 encoded - https://<domain>/<path>
In this case, you need to decode Base-64 string first.
[sdkApi resolveMessageByUrl:invite
completion:^(NSError *error, NSString *parsedInvite) {
// ...
}];
int parsedInvite = UtilsApi.vcxResolveMessageByUrl(invite).get();
The result value should match the formats we described above.
http://vas.evernym.com:80/agency/msg?c_i=eyJsYWJlbCI6IkFjbWUiLCJzZXJ2aWNlRW5kcG9pbnQiOiJodHRwOi8vdmFzLmV2ZXJueW0uY29tOjgwL2FnZW5jeS9tc2ciLCJyZWNpcGllbnRLZXlzIjpbIjNhVkhWZEFxZnBKSmVROG1mdm5UV0Y0MVpoYUxidVVLQXJ3UlVCRldlWjNRIl0sInJvdXRpbmdLZXlzIjpbIjNhVkhWZEFxZnBKSmVROG1mdm5UV0Y0MVpoYUxidVVLQXJ3UlVCRldlWjNRIiwiM21vM1A2WHpEekJ2dWt0Q2dEUWFyQUN6emVWN3p4clNFeG5pY3B1SDd0ODMiXSwicHJvZmlsZVVybCI6Imh0dHBzOi8vczMudXMtZWFzdC0yLmFtYXpvbmF3cy5jb20vcHVibGljLWRlbW8tYXJ0aWZhY3RzL2RlbW8taWNvbnMvY2JBQ01FLnBuZyIsIkB0eXBlIjoiZGlkOnNvdjpCekNic05ZaE1yakhpcVpEVFVBU0hnO3NwZWMvY29ubmVjdGlvbnMvMS4wL2ludml0YXRpb24iLCJAaWQiOiI4YWE1ZTZjOS1mZjZkLTQ0NDUtOWU1Ni1iNDU1MjQxZTVlZGIifQ==
http://vas.evernym.com:80/agency/msg?oob=eyJAdHlwZSI6Imh0dHBzOi8vZGlkY29tbS5vcmcvb3V0LW9mLWJhbmQvMS4wL2ludml0YXRpb24iLCJAaWQiOiI2OTIxMmEzYS1kMDY4LTRmOWQtYTJkZC00NzQxYmNhODlhZjMiLCJsYWJlbCI6IkZhYmVyIENvbGxlZ2UiLCAiZ29hbF9jb2RlIjoiaXNzdWUtdmMiLCJnb2FsIjoiVG8gaXNzdWUgYSBGYWJlciBDb2xsZWdlIEdyYWR1YXRlIGNyZWRlbnRpYWwiLCJoYW5kc2hha2VfcHJvdG9jb2xzIjpbImh0dHBzOi8vZGlkY29tbS5vcmcvZGlkZXhjaGFuZ2UvMS4wIiwiaHR0cHM6Ly9kaWRjb21tLm9yZy9jb25uZWN0aW9ucy8xLjAiXSwic2VydmljZSI6WyJkaWQ6c292OkxqZ3BTVDJyanNveFllZ1FEUm03RUwiXX0
https://vty.im/cnf4m
Successful execution will return a connection handle. This handle points to created Connection state object and should be used for later operations (sending messages through the connection) related to this connection.
-
For
aries connection
invitation:[appDelegate.sdkApi connectionCreateWithInvite: invitationId inviteDetails: inviteDetails completion:^(NSError *error, NSInteger connectionHandle) { // ... }];
-
For
aries out-of-band
invitation:[appDelegate.sdkApi connectionCreateWithOutofbandInvite: invitationId invite: inviteDetails completion:^(NSError *error, NSInteger connectionHandle) { // ... }];
-
For
aries connection
invitation:int connectionHandle = ConnectionApi.vcxCreateConnectionWithInvite(invitationId, invitationDetails).get();
-
For
aries out-of-band
invitation:int connectionHandle = ConnectionApi.vcxCreateConnectionWithOutofbandInvite(invitationId, invitationDetails).get();
The previous step only creates a Connection state object but not actually accept the invitation. Before accepting a connection invitation you need to check that it has not been used yet. In order to check does connection already exists, we need to retrieve invitations that were used during the creation of existing connections and compare them with the received invitation.
-
There are two ways of getting original invitation for existing connections:
-
Using SDK:
1.1 Deserialize connection state object#### iOS ```objC [appDelegate.sdkApi connectionDeserialize:serializedConnection completion:^(NSError *error, NSInteger connectionHandle) { // ... }]; ``` #### Android ```java int connectionHandle = ConnectionApi.connectionDeserialize(serializedConnection).get(); ```
1.2 Get invitation
#### iOS ```objC [appDelegate.sdkApi getConnectionInviteDetails:connectionHandle abbreviated:FALSE completion:^(NSError *error, NSInteger connectionInvite) { // ... }]; ``` #### Android ```java String connectionInvite = ConnectionApi.connectionInviteDetails(handle, abbreviated).get(); ```
-
Using an original invitation stored in the application connection object.
-
-
Compare invitations - invitations match when one of the following fields is the same for both of them:
@id
field (means exactly the same invitation)public_did
field (invitation from same Inviter)recipient_keys[0]
field (invitation from same Inviter)
-
If you find that connection already exists for the received invitation you would better reuse the existing connection rather than establish a new one. You can read how to reuse existing connection in the section.
If there is no existing connection, and you want to accept the invitation to establish a connection you need to create Pairwise Cloud Agent and send Connection Request
message.
-
This Agent will be used for one particular connection, so a new Pairwise Cloud Agent will be created for every connection you establish.
-
Connection Request
message contains:- Public key of the Invitee which Inviter will be used to encrypt all following messages.
- Cloud Agent public endpoint.
- Pairwise Cloud Agent public keys which will be used to encrypt sending messages.
-
All communication between Inviter <-> Invitee and Invite/Inviter <-> Pairwise Cloud Agent happens in encrypted way.
-
The Invitee private keys to decrypt messages from Inviter or Cloud Agent are stored into Mobile SDK Wallet in encrypted form and cannot be read.
-
Pairwise Cloud Agent receives messages in encrypted form, so only the Invitee can read the message content.
-
Send
Connection Request
message[appDelegate.sdkApi connectionConnect: connectionHandle connectionType: connType completion:^(NSError *error, NSInteger inviteDetails) { // ... }];
ConnectionApi.vcxConnectionConnect(connectionHandle, connType).get();
-
Fetch Connection
pwDid
. This field will be unique among all established connections. This field will be also needed during the handling of messages received from the Cloud Agent (in order to associate messages with serialized connections).[appDelegate.sdkApi connectionGetPwDid:connectionHandle completion:^(NSError *error, NSString *pwDid) { // ... }];
String pwDid = ConnectionApi.connectionGetPwDid(connectionHandle).get();
Theoretical, processing of the Connection Request
message on the Inviter side may take a while and User can just close the application before completion.
It's better to store the connection state machine (received after sending Connection Request
) into the applicaiton storage to avoid data loss.
If you wish, you can skip storing for now and do it only after the connection will be completed (after the step 6).
-
Serialize Connection object
[appDelegate.sdkApi connectionSerialize:connectionHandle completion:^(NSError *error, NSString *serializedConnection)) { // ... }];
String serializedConnection = ConnectionApi.connectionSerialize(connectionHandle).get();
-
Save
serialized
sdk connection state object andpwDid
into your application connection object. -
The
status
of the connection is considered aspending
at this step. -
Every time in the future you want to perform some operations using the created connection you firstly need to fetch Connection object from the storage and deserialize SDK object from its serialized representation (receive a new handle).
The connection will be completed when Connection Response
message is received back from the Inviter. This message contains public keys which will be used by the Inviter for messages exchange. Inviter may change original keys put into Invitation as it was transferred using some public way.
-
Fetch Connection record from the storage and deserialize pending Connection state object
[appDelegate.sdkApi connectionDeserialize:serializedConnection completion:^(NSError *error, NSInteger connectionHandle) { // ... }];
int existingConnectionHandle = ConnectionApi.connectionDeserialize(serializedConnection).get();
-
Await connection completion. There are two ways regarding how to await
Connection Response
message depending on the messages receiving strategy you use:-
Polling - call
connectionUpdateState
function in loop until returned state is not equal4
(Accepted
).while(1) { [appDelegate.sdkApi connectionUpdateState:connectionHandle completion:^(NSError *error, NSInteger state) { if (state == 4){ break; } } }
int state = -1; while(state != 4){ state = ConnectionApi.vcxConnectionUpdateState(connectionHandle).get(); }
These functions do the following things internally:
- Downloads all pending messages from the Cloud Agent related to the connection
- Find matching
Connection Response
message - If message found -> updates state machine with it and update message status on the Cloud Agent as read.
-
Push notifications - as push notification doesn't contain the type of received message we can download all messages and analyze them:
-
Use common
downloadPendingMessages
function to download messages. -
Find matching
Connection Response
message. The following conditions must met:- value of
["decryptedPayload"]["@type"]
field must ends withresponse
- value of
pairwiseDID
field must match to thepwDid
received after sendingConnection Request
- value of
-
Update Connection state object with received
Connection Response
message:[appDelegate.sdkApi connectionUpdateStateWithMessage:connectionHandle message:message completion:^(NSError *error, NSInteger state) { // .... }
int state = ConnectionApi.vcxConnectionUpdateStateWithMessage(connectionHandle, message).get();
-
Update status of the message as read. See messages documentation for message update information.
[appDelegate.sdkApi updateMessages:messageStatus pwdidsJson:handledMessage completion:^(NSError *error) { // ... }];
UtilsApi.vcxUpdateMessages(messageStatus, handledMessage).get()
-
-
Once we get the connection completed (it means that internal representation of SDK connection state object changed) we need to update the corresponding record in the application storage (or add a new record if you skipped step 5) to contain the latest serialized connection state.
-
Serialize Connection object
[appDelegate.sdkApi connectionSerialize:connectionHandle completion:^(NSError *error, NSString *serializedConnection)) { // ... }];
String serializedConnection = ConnectionApi.connectionSerialize(connectionHandle).get();
-
Update
serialized
value from the corresponding record stored in the application storage. -
The
status
of the connection is considered asestablished
at this step. -
Every time in the future you want to perform some operations using the created connection you firstly need to fetch Connection object from the storage and next deserialize SDK object from its serialized representation (receive a new handle).
With adding more and more connections, its getting hard for user to remember which connections already established. There is potential that some of the new connections user tries to establish already exists in list of connections (e.g. QR code was scanned multiple types). In this case we should not create new connection and try to reuse existing one.
Aries Connection Invitations match when one of the following fields is the same for a new invitation and one of existing:
@id
field (means exactly the same invitation)public_did
field (invitation from same Inviter)recipient_keys[0]
field (invitation from same Inviter)
A regular Aries connection protocol does not contain reusing or redirection steps. If you detected that connection already exists you can optionally show an information message to User and just use the found connection instead of creating a new one.
In contrast to Aries Connection
, Aries Out-of-Band protocol describes the protocol allowing as establishing new connections and as reusing of existing.
According to Out-Of-Band protocol when we detected a duplicate connection, we need to awake an existing connection and use it instead of establishing a new one.
Aries Out-Of-Band Invitations match when one of the following fields is the same for a new invitation and one of existing:
public_did
field (invitation from same Inviter)recipient_keys[0]
field (invitation from same Inviter)
Note: If
@id
field matched it means that you have exactly the same invitations, but in this case, you more likely not need to followreusing
steps and instead simply show the information message to the User.
The steps need to be taken by the app also depends on the format of received Out-of-Band invitation. If you want to get guidelines on how to handle all possible Out-of-Band invitation cases go to the document
For this section we assume accomplishing the following facts:
- Connection matching to the provided invitation is already exists (either
public_did
orrecipient_keys[0]
are the same). - Out-of-Band invitation contains
handshake_protocols
- Out-of-Band invitation does not
request~attach
or it's an empty array.
-
Deserialize existing Connection state object which matched the invitation
[appDelegate.sdkApi connectionDeserialize:serializedConnection completion:^(NSError *error, NSInteger connectionHandle) { // ... }];
int existingConnectionHandle = ConnectionApi.connectionDeserialize(serializedConnection).get();
-
Send Connection Reuse message. By sending this message you ensure that the connection is still alive and can be used.
[appDelegate.sdkApi connectionSendReuse:existingConnectionHandle invite:newInvite withCompletion:^(NSError *error) { // ... }];
ConnectionApi.connectionSendReuse(existingConnectionHandle, newInvite).get();
-
Await for
handshake-reuse-accepted
message received back from the remote side.-
Use common
downloadPendingMessages
function to download messages. -
Find matching
Handshake Rese Accepted
message. The following conditions must met:- value of
["decryptedPayload"]["@type"]
field must ends withhandshake-reuse-accepted
- value of
-
value of
["decryptedPayload"]["~thread"]["pthid"]
field must match to the value of@id
field from Connection Invitation. -
After receiving of
handshake-reuse-accepted
message, the connection is considered asreused
and can be used instead of creating a new one. -
Update status of the message as read. See messages documentation for message update information.
-
It may happen that after some time a User wants to delete connections that are no longer need to him. In order to delete a connection you need to perform the next steps:
-
Delete a Pairwise Cloud Agent associated with the Connection, so that it cannot be used anymore for sending or receiving messages.
1.1. Deserialize existing Connection state object
#### iOS ```objC [appDelegate.sdkApi connectionDeserialize:serializedConnection completion:^(NSError *error, NSInteger connectionHandle) { // ... }]; ``` #### Android ```java int connectionHandle = ConnectionApi.connectionDeserialize(serializedConnection).get(); ```
1.2. Delete Pairwise Cloud Agent associated with the Connection
#### iOS ```objC [appDelegate.sdkApi deleteConnection:connectionHandle withCompletion:^(NSError *error) { // ... }]; ``` #### Android ```java ConnectionApi.deleteConnection(connectionHandle).get(); ```
-
Delete connection data from the application storage.
It is expected use cases when user on mobile device tries to establish connection which already exists (user already connected before tries again). In that case, we don't need to reset connection (delete existing connection and create new connection with same pair). You can reuse existing connection.
You can find more details about this use case here.
Now your application is able to established connections with other parties. You are ready to read how to receive verifiable credentials.