Aries playground
Introduction
The Aries playground environment is setup for developers to perform API call flows during a verified data exchange process using Hyperledger Indy as the distributed ledger registry and Hyperldger Aries agent as the client application.
For developers to try out the Aries agent, iGrant.io Aries agents are pointed to the indy ledger hosted at https://indy.igrant.io. This can be changed to any ledger of choice by updating GENESIS_URL environment variable in docker-compose.yml file.
Reference system
In order to make it easier to comprehend SSI concepts, a reference system is defined. In this reference system, we have the individual (Alice) holding her health data in her wallet app (Data4Life) issued by a Health Test Center. This health data could be used by Alice to prove her health status to a travel company, for example. The legal entity ensures that the test center follows the schema and governance.
Use case scenario
Alice is a travel vlogger. Her next destination is Dubai but, due to the Covid pandemic, she has to prove to a travel agent, that her result is negative so that she will be allowed to travel.
Alice listed out the things she has to do as illustrated in the figure below:
- Visit a Covid test center and identifies herself
- Test center should follow a schema defined by a legal entity to issue the certificate. Refer Prepare public schema
- Test center and alice creates a connection to exchange information. Refer Establish connection between Issuer and Holder
- The Test center will test alice's samples and issues a certificate. Refer Issue credential (Automated) or Issue credential (Manual)
- Store the issued credentials to her wallet.
- Present proof to travel agent. Refer Verfiier proof workflow
Prepare reference system environment
In order to test with the aries playground, the first step is to clone the repository.
git clone https://github.com/decentralised-dataexchange/aries-playground.git
The test environment is collection of docker containers and uses docker-compose for managing the containers.
cd aries-playground
docker-compose up
Selection of ledger is set in docker-compose.yml file. While this demo uses iGrant.io Indy Sandbox it is easy to switch to a Sovrin ledger or any other indy ledger (as of today). Replace the GENESIS_URL row in docker-compose.yml file with this line in order to use Sovrin.
GENESIS_URL: https://github.com/sovrin-foundation/sovrin/blob/master/sovrin/pool_transactions_sandbox_genesis
As an example, you can use this command for setting up docker-compose with Sovrin.
docker-compose -f docker-compose-sovrin.yml up
Once the reference system is up and running you can start 3 terminals and monitor their logs by executing the docker container logs given below:
Agent | Swagger API endpoint admin UI |
---|---|
Test Center | docker exec -it test-center.webhook tail -f demo.log |
Alice (Data4Life User) | docker exec -it data4Life-user.webhook tail -f demo.log |
Travel Company | docker exec -it travel-company.webhook tail -f demo.log |
In your browser you can start three tabs to execute the APIs using swagger. To test any request click "Try it out" and "Execute" with any required input. Note for your convenience swagger links also provided with each request.
Agent | Swagger API endpoint admin UI |
---|---|
Test Center | test-center.swagger.localhost |
Alice (Data4Life User) | data4life-user.swagger.localhost |
Travel Company | travel-company.swagger.localhost |
Verifiable Credential work-flow
The work flow shows 4 steps when creating a verifiable credential (or agreement) as depicted in below diagram.
- Prepare public schema including registering issuer DID
- Establish connection between Issuer and Holder (Alice)
- Issue credential (Automated Flow) represented as an automated flow
- Issue credential (Manual flow) with a manual flow
http://localhost:3000/ssi/ssi-apg#issue-credential-automated-flow
The last step (4) allows the Holder (Alice) to review the offer from the Issuer before accept it. Note step 3 is automated to skip the negotiation. In order to allow to negotiate The file startup.sh in folder aries-playground/cloud/agent needs to be renamed for startup_temp.sh.
For further details on the diagram refer to Hyperledger Aries RFC 0167 Consent Lifecycle
Prepare public schema
Before creating schema it is necessary to create a DID.
Create DID in a wallet
The steps below to create DID in a wallet is a pre-requesite for any agent before acquiring agent roles.
Create a local DID for the agent using
POST /wallet/did/create
. This generates the DID and verification key localhost endpointResponse body
{
"result": {
"did": "TqQDq6vrPoW7hsf4JvqPaR",
"verkey": "FdHtGAaod2xoZ7wJfXU4yCEEuTt6hSf38EpdKkX4UHgK",
"posture": "wallet_only"
}
}For organisations, after creating the local DID, send a support request to support@igrant.io to register the DID and verkey to the ledger.
After registering with the Indy ledger, call POST /wallet/did/public
(localhost endpoint).
Optional: To register with Sovrin Staging network use this Sovrin Indy network link and complete details as shown in diagram. Important that docker-compose.yml has been updated prior to starting docker-compose.
In case the ledger requires a transaction author agreement (TAA) to be accepted before writing to the ledger, make sure to execute the APIs for the same.
For e.g.
Endpoint for fetching the TAA: GET /ledger/taa
The response lists the available acceptance mechanisms for this particular ledger. E.g. "product_eula", "click_agreement" etc.
Endpoint for accepting the TAA by providing one of the acceptance mechanism: POST /ledger/taa/accept
Assign DID as public
This is used to let the wallet know that the DID is public by using POST /wallet/did/public
Create Schema
To make it easier, we have used the Test Center Agent to register the schema. Ideally this is defined by a legal entity or a standardisation body.
You can execute the schema definiton API to register the schema in the ledger.(localhost endpoint)
Sends a schema to the ledger with the API POST: /schemas
with the json body as given:
{
"schema_version": "1.0",
"schema_name": "Covid-19 Test Results",
"attributes": [
"testResult",
"testDate"
]
}
Response body
{
"schema_id": "3jbQ51y4RwCb4U45rkeKY1:2:Covid-19 Test Results:1.0",
"schema": {
"ver": "1.0",
"id": "3jbQ51y4RwCb4U45rkeKY1:2:Covid-19 Test Results:1.0",
"name": "Covid-19 Test Results",
"version": "1.0",
"attrNames": [
"testResult",
"testDate"
],
"seqNo": 370
}
}
Create credential definition by the Test Center
Prior to making any verifiable credential a credential definition has to be created which is means of performing proofs against the credential. POST method /credential-definitions
stores a credential definition against the ledger. (localhost endpoint)
Add following body to the request.
{
"revocation_registry_size": 4,
"support_revocation": false,
"schema_id": "PWr9PACurgoMwowC5Bx8RD:2:Covid-19 Test Results:1.0",
"tag": "default"
}
Response body
{
"credential_definition_id": "3jbQ51y4RwCb4U45rkeKY1:3:CL:370:default"
}
Ready to issue verifiable credentials.
Establish connection between Issuer and Holder
Here, the Test Center and Data4Life-User agents establishes connection with each other. The following is the API call sequence:
Create a new invitation (by Test Center)
Test Center Agent:
POST /connections/create-invitation
. Use default values.(localhost endpoint)This generates the
connection_id
andinvitation
.Response body
{
"connection_id": "521f106f-b10e-472a-b7ad-219a812e31e1",
"invitation": {
"@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/connections/1.0/invitation",
"@id": "c3055fe4-9bca-4585-918c-67bbdb07bc10",
"label": "Test-Center",
"serviceEndpoint": "http://test-center.localhost",
"recipientKeys": [
"7myawP9fdA1y237bi3Xdfukuv2rX8YRNzt2s2xEdpJh3"
]
},
"invitation_url": "http://test-center.localhost?c_i=eyJAdHlwZSI6ICJkaWQ6c292OkJ6Q2JzTlloTXJqSGlxWkRUVUFTSGc7c3BlYy9jb25uZWN0aW9ucy8xLjAvaW52aXRhdGlvbiIsICJAaWQiOiAiYzMwNTVmZTQtOWJjYS00NTg1LTkxOGMtNjdiYmRiMDdiYzEwIiwgImxhYmVsIjogIlRlc3QtQ2VudGVyIiwgInNlcnZpY2VFbmRwb2ludCI6ICJodHRwOi8vdGVzdC1jZW50ZXIubG9jYWxob3N0IiwgInJlY2lwaWVudEtleXMiOiBbIjdteWF3UDlmZEExeTIzN2JpM1hkZnVrdXYyclg4WVJOenQyczJ4RWRwSmgzIl19"
}Receive connection invitation by Data4Life-User (Alice)
Alice Agent:
POST /connections/receive-invitation
with the invitation extracted from the json body generated in step 1. (localhost endpoint)Response body
{
"connection_id": "54b9fdd1-99b4-4ecf-a388-5cb04ebffa9f",
"initiator": "external",
"invitation_key": "7myawP9fdA1y237bi3Xdfukuv2rX8YRNzt2s2xEdpJh3",
"state": "invitation",
"created_at": "2020-12-23 15:49:24.936970Z",
"routing_state": "none",
"invitation_mode": "once",
"their_label": "Test-Center",
"updated_at": "2020-12-23 15:49:24.936970Z",
"accept": "manual"
}Accept a received connection invitation from Test Center by the Data4Life-user (Alice)
Alice Agent:
POST /connections/{conn_id}/accept-invitation
passing theconnection_id
as input. (localhost endpoint)Response body
{
"connection_id": "54b9fdd1-99b4-4ecf-a388-5cb04ebffa9f",
"initiator": "external",
"invitation_key": "7myawP9fdA1y237bi3Xdfukuv2rX8YRNzt2s2xEdpJh3",
"state": "request",
"created_at": "2020-12-23 15:49:24.936970Z",
"request_id": "2bbcb177-83b1-4f6a-819c-d3b585c2b80d",
"routing_state": "none",
"my_did": "TiXWip9b6KCYdmfa4MJRng",
"invitation_mode": "once",
"their_label": "Test-Center",
"updated_at": "2020-12-23 15:55:26.730952Z",
"accept": "manual"
}Check status
Check both Test Center Agent and Alice Agent by
GET /connections
and both are inActive
status.Test Center agent status check (localhost endpoint)
Alice agent status check (localhost endpoint)
After the secured connection is established between the two agents, the Test Center can now issue credential to Data4Life user (Alice) with her personal data. Alice then is able to see the credential in her data wallet.
Issue credential (Automated Flow)
A credential is issued by the Test Center based on a standard schema earlier defined (e.g. by the legal entity). Test Center now issues the credential to the holder Alice (Data4Life-user).
Test Center Agent: POST /issue-credential/send
with auto_remove
set to FALSE. (localhost endpoint)
In the json body below, the schema_issuer_did
and the credential issuer_did
used are the same. In reality, they could be different as schema definitions are owned by a legal entity.
{
"schema_name": "Covid-19 Test Results",
"schema_version": "1.0",
"cred_def_id": "PWr9PACurgoMwowC5Bx8RD:3:CL:19:default",
"auto_remove": false,
"comment": "string",
"connection_id": "a8ea680f-0704-4327-99b8-02e5e3d03ea4",
"trace": false,
"schema_issuer_did": "PWr9PACurgoMwowC5Bx8RD",
"schema_id": "PWr9PACurgoMwowC5Bx8RD:2:Covid-19 Test Results:1.0",
"issuer_did": "PWr9PACurgoMwowC5Bx8RD",
"credential_proposal": {
"@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/1.0/credential-preview",
"attributes": [
{
"name": "testDate",
"mime-type": "text/plain",
"value": "22-Aug-2020"
},
{
"name": "testResult",
"mime-type": "text/plain",
"value": "negative"
}
]
}
}
In the case of automated flow, Data4Life user (Alice), the credential is automatically stored into the wallet. In the case of manual flow, this need to be done explicitly.
Data4Life user (Alice) can fetch the credentials from the wallet by GET /credentials
Issue credential (Manual Flow)
The following steps describes the manual flow.
Issuer (Test Center) sends a offer with the result of the test.
Test Center Agent:
POST/issue-credential/send-offer
(localhost endpoint)Note: In request body auto issue and auto remove should be false
{
"cred_def_id": "WgWxqztrNooG92RXvxSTWv:3:CL:20:tag",
"credential_preview": {
"@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/1.0/credential-preview",
"attributes": [
{
"name": "testDate",
"mime-type": "text/plain",
"value": "22-Aug-2020"
},
{
"name": "testResult",
"mime-type": "text/plain",
"value": "negative"
}
]
},
"connection_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"auto_issue": false,
"comment": "string",
"trace": false,
"auto_remove": false
}Now the state will be offer_sent for Issuer (Test Center)
Note:
GET /issue-credential/records
to view the state. (localhost endpoint)Alice will call
GET /issue-credential/records
The state will be offer_received and from this response copy the credential exchange id. (localhost endpoint)
Alice will send request to issue the credential using credential exchange id
Alice Agent:
POST /issue-credential/records/{cred_ex_id}/send-request
(localhost endpoint)Now the state will be request_sent for Alice
Issuer (Test Center) will call
GET/issue-credential/records
The state will be request_received and from this response copy the credential exchange id. (localhost endpoint)
Issuer (Test Center) issues the certificate using credential exchange id
Test Center Agent:
POST /issue-credential/records/{cred_ex_id}/issue
(localhost endpoint)Now, the credential will be issued to Alice and state will change to credential_issued
Stores credential into a personal wallet
Personal wallet here is provided by Data4Life app. In the case of automated flow, the credential is automatically stored into the wallet. In the case of manual flow, this need to be done explicitly.
For manual flow, Alice, the Data4Life-User, now stores the received credentials
Alice Agent:
POST /issue-credential/records/{cred_ex_id}/store
(localhost endpoint)Data4Life user can fetch the credentials from the wallet by
GET /credentials
Verifier/Proof work-flow
This is from the Holder app (Data4Life) to Verifier (Travel Company)
Before any communication happens between Alice (Data4Life-User) and the verifier, a secured connection is established between two agents. After that Travel Company issues a proof request to Alice, showing what type of proof is needed to qualify in order for Alice to travel using the Covid-19 test result. Alice will build the proof based on the credential in her Data4Life wallet. Alice then sends the proof to the travel company which will observe the result. The proof response can be done automatically or through manual input. In order to run manual modify startup.sh file as described in section Work flow.
The following diagram provides an overview of all the steps taking place under the hood. For further details refer to Hyperledger Aries RFC 0167 Consent Lifecycle
The following sections are broken down into the following areas:
- Establish connection between Verifier and Holder
- Automated Holder response
- Manual Holder response
Establish connection between Verifier and Holder
Establishing connection is identical to the flow in Issuer and holder as described in section Establish connection between Issuer and Holder
Establish connection
Travel Company Agent:
POST /connections/create-invitation
, from the response get the invitation object (from{ to }
) as shown earlier during the connection between Test Center and Alice. (localhost endpoint)Alice Agent:
POST /connections/receive-invitation
with the invitation object. (localhost endpoint)Accept a stored connection invitation by Alice (Data4Life user)
Alice Agent:
POST /connections/{conn_id}/accept-invitation
passing theconnection_id
as input. (localhost endpoint)Check both Travel Company Agent and Alice Agent by
GET /connections
and both are inActive
statusAfter the secured connection is established between the two agents, the Travel Center request a proof from Alice as per the credenital defintion. The following steps cover that flow.
Make a proof request
In this demo, the Proof request details the Travel Center is asking for is
testResult
testDate
The requested attributes follows the Credential Definition specified by the ID
Travel Center Agent: POST /present-proof/send-request
is called with the following payload. (localhost endpoint)
The body in the request shall have the following details. Modify the connection_id
and cred_def_id
(For e.g. the Travel company can insist to reuse the same credential definition ID as issued by the Test Center).
{
"connection_id": "a8ea680f-0704-4327-99b8-02e5e3d03ea4",
"comment": "Ready to travel",
"proof_request": {
"name": "Proof of COVID19 Negative",
"version": "1.0",
"requested_attributes": {
"0_testresult_uuid": {
"name": "testResult",
"restrictions": [
{
"cred_def_id": "PWr9PACurgoMwowC5Bx8RD:3:CL:19:default"
}
]
},
"0_testdate_uuid": {
"name": "testDate",
"restrictions": [
{
"cred_def_id": "PWr9PACurgoMwowC5Bx8RD:3:CL:19:default"
}
]
}
},
"requested_predicates": {}
}
}
Using the requested_predicates, you can do some assertions, example, the testDate shall be less than 2 days from today.
In the webhook interceptor, you can view the sequence of events happening:
- receiving the proof request
- checking credentials
- generating proof
- sending proof to Travel Company
From Travel Company, we can use GET /present-proof/records
to see the proof sent by Alice. The presentation_exchange_id
is the identifier of the presentation proof and state will tell you the current status of the presented proof. (localhost endpoint)
Send proof and verify
Instead of an automatic response from the Holder the proof can be manually entered.
Holder (Alice) has to get the presentation exchange id
Alice Agent:GET /present-proof/records
(localhost endpoint)Holder (Alice) will send the presentation
Alice Agent:
POST /present-proof/records/{pres_ex_id}/send-presentation
is called with the following payload. (localhost endpoint)The body in the request shall have the following details. The
pres_ex_id
is copied from previous step.noteThe
cred_id
below is thereferent
value in the holders wallet by using the endpoint:GET /credentials
. Also therequested_attribute
fields keys should be same as in the body ofPOST /present-proof/send-request
.{
"requested_predicates": {
},
"trace": false,
"self_attested_attributes": {
},
"requested_attributes": {
"0_testresult_uuid": {
"cred_id": "b5df5eac-047f-4ad0-98cc-7e6138a2f339",
"revealed": true
},
"0_testdate_uuid": {
"cred_id": "b5df5eac-047f-4ad0-98cc-7e6138a2f339",
"revealed": true
}
}
}Finally Travel Company use
POST /present-proof/{pres-ex-id}/verify-presentation
to see Alice’s proof presentation. (localhost endpoint)