1. Introduction

This document provides information to the developers who want to integrate the Nixar api to their development environment using the C Interface wrappers. The C Interface wrappers are written for Java, Swift, Python.

2. Who Should Read This Document

If you study on SSID and Distributed ID blockchains, have interest in creating blockchain agnostic SSID solutions, you already know about SSID and you are interested in developing business applications using the nixar-core library, this document is for you.

3. Who Should NOT Read This Document

Well it wouldn’t be so polite to ask people not to read this document; however, if you have one of the following aims: i) you seek for theoretical information about the SSID, ii) you want to learn more about blockchain, ii) you need to elaborate and research on the business use cases of SSID, then this document is NOT for you.

4. A tiny intro to SSID

The most fundamental use case of the SSID is given below. The main actors of the use case are Issuer, * Holder/Prover* and Verifier.

Fundemental User Case

In most plain words, an Issuer creates credentials (i.e. identity information for an entity) for the Holder who in return saves these credentials in her wallet.

In the next step the Holder who holds her credentials becomes a , and proves (as a Prover) these credentials to a Verifier (e.g. a Service Provider)

While in the above scenarios the roles Holder and Prover are used interchangeably, in fact an entity in the SSID context might have multiple roles simultaneously (e.g. an issuer might become a prover in another business scenario).

We will use the term Prover for both Holder and Prover roles in the rest of the document#

An entity in SSID is context is represented with an Agent, which is mostly a piece of software that can act on behalf of the entity. For example, Lucy 's digital agent, on her favorite android device, might create proof from her digital wallet and send it to her schools agent (verifier) in order to submit her homework to the homework submission system.

5. Prerequisites

5.1. Downloading the binaries

The nixar-core library can be built for all popular platforms. The list of the existing precompiled binaries and where to find them is given below

Platform/Resource Link

Api Docs (Kickstart (this document) and c interfaces)

https://proje.bag.org.tr/nixar_shared/nixar_api_docs

OsX

https://proje.bag.org.tr/nixar_shared/nixar_api_osx

IOS XCFramework

https://proje.bag.org.tr/nixar_shared/cnixarapi

IOS Swift Wrapper

https://proje.bag.org.tr/nixar_shared/nixar_api_ios

Linux

https://proje.bag.org.tr/nixar_shared/nixar_api_linux *

Android

https://proje.bag.org.tr/nixar_shared/nixar_api_android

Win64

https://proje.bag.org.tr/nixar_shared/nixar_api_win_x86_64 *

The binaries need to be discoverable to your development environment in order for your application to work with the nixar-core library. For example on OsX , the binaries libnixar_core.dylib, libcrypto.1.1.dylib, libssl.1.1.dylib, libzmq.5.dylib. Please not that you can also use compatible versions of the dependent libraries (i.e. OpenSSL, LibZmq and LibSodium) that preinstalled on your environment. See following sections for more details.

5.2. Android: Notes for users

5.2.1. How to include in Android Projects

Please clone or download the java library and install using maven and JDK8.

> mvn -DskipTests=true clean install

Then to include the library in your gradle project, please don’t forget to add mavenLocal() to your build.gradle file.

allprojects {
  repositories {
    mavenLocal()
    //others...
    google()
    jcenter()
  }
}
There are two places that mavenLocal() can be written, one of them is buildscript and the other is allprojects. Please ignore the one for buildscript and write mavenLocal to allprojects as shown above.

5.2.2. How To Include Native Libs

In order to include libnixar_core.so in your project clone or download this repository. Copy files to your Apk’s source path as follows:

app/
  build
  build.gradle
  proguard-rules.pro
  src/
    main/
     jniLibs/
      java/
        ...
      armv64-v8a/
        libc++_shared.so
        libjnidispatch.so
        libnixar_core.so
      armeabi-v7a/
        libc++_shared.so
        libjnidispatch.so
        libnixar_core.so

5.2.3. Enabling nixar-core logs

By default nixar-core will be able to log INFO, WARN and ERROR. In order to enable DEBUG and VERBOSE please set this property via adb as below.

  #For verbose logging
  adb shell setprop log.tag.libnixar-core VERBOSE
  #For verbose logging
  adb shell setprop log.tag.libnixar-core DEBUG

5.2.4. Choosing Nixar-Home Directory

In the most recent android versions, writing to certain locations has been highly restricted. Therefore, developers might get file permission errors.

By default, Nixar will try to write its content to EXTERNAL_STORAGE. But it is a bit confusing and complicated to allow the same access on all devices.

Due to this fact, the most recent version of Nixar includes a new api that allows the end users to set the Nixar home directory.

The sample code snippet below shows how to use that:

NixarAgentFactory.initLogging(Level.TRACE);
//set nixar home path manually to data dir.
NixarAgentFactory.setNixarHomePath(getApplicationContext().getDataDir().getAbsolutePath());
NixarAgent nixarAgent = NixarAgent.createAgent(...

Please note that this call must take place before any agent manipulation.

5.2.5. Android Kickstart

An android kickstart project has been created here:

5.2.6. Enable Cleartext traffix to http networks

  1. In order to connect to bcovrin network and download the genesis file automatically from http://test.bcovrin.vonx.io/genesis, you need to enable cleartext traffic:

    AndroidManifest.xml
     <application
            ...
            android:usesCleartextTraffic="true"
            ...>

Otherwise you can manually download the genesis file and feed to the app.

5.3. Building from source (Optional)

The nixar-core library needs Rust, libzmq, OpenSSL and LibSodium if you want to build from source.

5.3.1. Rust and Cargo

Please install Rust [Rust](https://www.rust-lang.org/tools/install) and [Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) in order to be able to build the codes from the source. You need to access the repository addresses of the nixar-core source code as well as its dependent

5.3.2. OpenSSL

The library needs [OpenSSL](https://www.openssl.org/) V1.1 which is already included in the repository (libssl.1.1.dylib and libcrypto.1.1.dylib). Feel free to use it or install it your system via brew

5.3.3. ZeroMQ

[ZeroMQ](https://zeromq.org/) is used for communicating with the Hyperledger Indy networks. We have included a precompiled dylib (libzmq.5.dylib), but you can also obtain it from outside

5.3.4. Libsodium

Please follow Libsodium installation instructions are provided [here](https://doc.libsodium.org/installation). Nixar-core is compatible with [libsodium 1.0.18 -RELEASE](https://github.com/jedisct1/libsodium/tree/1.0.18-RELEASE) or newer.

6. Library Samples

In this chapter we provide samples for the agent creation, connection manipulation and generic anoncreds use cases (e.g create agent, issue, store and verify)

You can see the full source codes for the wrappers and example codes from the below links:
Language Address

Rust

TBD

Java

https://proje.bag.org.tr/nixar_shared/nixar_api_java

Python

https://proje.bag.org.tr/nixar_shared/nixar_api_python

Swift

https://proje.bag.org.tr/nixar_shared/nixar_api_ios

6.1. Create Agent

The main actor of the nixar-core library is an agent object. In order to start using the library you need to create an agent object.

Java
AgentInitParams agentInitParams = new AgentInitParams("java_issuer55", "ENDORSER", "000000000000000000000TUBISSUER10", genesisPath);

try {
  issuer = NixarAgent.createAgent(agentInitParams, () -> "123456".toCharArray());

  LOGGER.info("Issuer agent created");
} catch (AgentNotRegisteredException ex) {
  String did = ex.did;
  String verkey = ex.verkey;


  registerAgentToLedger(agentInitParams, did, verkey);

  issuer = NixarAgent.openAgent(agentInitParams.getAlias(), agentInitParams.getGenesisPath(), () -> "123456".toCharArray());
  LOGGER.info("Issuer agent created");
}
Python
issuer = NixarApi('genesis-local.txn')
try:
    issuer.create_agent('python_issuer', 'ENDORSER', '12345', '001090101901001100000TUBISSUER90')
except NixarError as err:
    logging.warning(err)
    # register agent to ledger
    if 'AgentNotRegistered' == err.code:
        registration_info = json.loads(err.message)
        registration_info['alias'] = 'python_issuer'
        registration_info['role'] = 'ENDORSER'
        logging.warning(json.dumps(registration_info))
        trustee.register_agent_to_ledger(json.dumps(registration_info))

# issuer.open_agent('python_issuer', '12345')
logging.info('Issuer agent created')
Swift

In the above code, we first create and AgentInitParams object/json string. We have to provide an agent_name that will be the name of our agent. We shall then refer to our agent (e.g. open it) with the same name. A role is optional for an agent. Mostly mobile device users (i.e. Prover) will not need a role; because one wouldn’t need a role in order to perform READ operations on the ledger.

However, if you are an issuer your role has to at least be an ENDORSER in order to add schemas and credential definitions to the ledger as well as revoking credentials and so on. In other words, to perform WRITE operations on the ledger you need to have your NYM registered to the ledger and you need a role. In that case another agent with e TRUSTEE or ENDORSER role must have added your agent to the ledger.

Otherwise, you can skip the role and set it to None/null.

6.2. Manipulating Connections Between Agents

In order for agents to communicate via a did channel, their connection information must previously have been established. This scenario is called creating a connection between agents. The steps are given below.

In below samples we assume that we want to connect two agents, namely Agent1 and Agent2

6.2.1. Agent 2 Create Connection Invitation

Assuming that your agent is agent1 and you want to connecto to agent2, you need an invitation that has been created by agent2. For that agent2 should create an invitation.

There are two types of invitations, i) public invitation, ii) private invitation. In the first case, the invitation contains the information about the did of the agent. In the second case, the invitation contains additinally the endpoint (mostly HTTP) of the agent so that the other agent can discover its location and connects to it. In the case of a public invitation, the connecting agent needs to resolve the endpoint and connection keys from the ledger.

Below is the sample code for creating a connection invitation or getting the public invitation. The owner of the agent is supposed to share this information with the clients, via means like publishing on the web, QR codes and so on.

Java
ConnectionTypes.ConnectionInvitation publicInvitation = issuer.getPublicInvitation();
Python
inv_seed = "00000000000000000000000000AGENTA"
conn_inv = issuer.connection_create_local_invitation("agent A", "http://localhost:8080", inv_seed)
logging.info("Agent A create connection invitation, conn_inv" +
             json.dumps(conn_inv))
Swift

6.2.2. Agent-1 Create Connection Request

Having received the invitation from agent2, our agent (agent1) needs to process this invitation and create a connection request from it. As stated previously, if the invitation is public, the endpoint and recipient keys of the agent2 are resolved from the ledger by nixar-core automatically.

Java
//If the seed is provided, it has to be 16 bytes, ASCII characters. Please don't use accented non ascii chars
String seed = "000000000000000000MYCONNECTION01";
final ConnectionTypes.EncryptedConnectionRequest encryptedConnectionRequest = prover.createConnectionRequest(publicInvitation, seed);
Python
# Agent B receive invitation and create connection request
req_seed = "00000000000000000000000000AGENTB"
conn_req = prover.connection_create_request(conn_inv, req_seed)
logging.info("Agent B create connection request, conn_req" +
             json.dumps(conn_req))
Swift

When an invitation is processed for creating a connection request, a seed value my optionally be provided. While we discourage its usage due to privacy, when you provide it, the pairwise did for this connection will be generated from this value.

The resulting request_message is the encrypted connection request message created by agent1. The encryption is performed using did-comm and only agent2 can open the message.

6.2.3. Agent-2 Accept Connection Request

Agent 2 receives the encrypted connection request via means decided by the business layer (e.g. HTTP, e-mail, AS4 …​). Here we assume that agent2 has received the message; subsequently processes it, accepts the connection request and creates an encrypted connection response.

Java
final ConnectionTypes.EncryptedConnectionResponse encryptedConnectionResponse = issuer.acceptConnectionRequest(encryptedConnectionRequest, proverAlias);
Python
# Agent A receive connection request and create connection response
conn_res = issuer.connection_accept_request(conn_req, None)
logging.info("Agent A create connection response, conn_res" +
             json.dumps(conn_res))
Swift

The second parameter is an alias that agent2 can optionally set to the underlying connection object.

The response_message created by agent2 is also encrypted and can only be opened by agent1.

6.2.4. Agent-1 Accept Connection Response

As the last step of connection creation, agent1 needs to process the response_message created by agent2 in the previous step. Below is the code.

Java
prover.acceptConnectionResponse(encryptedConnectionResponse);
Python
# Agent B receive connection response and finalize connection creation
prover.connection_accept_response(conn_res)
logging.info("Connection created between Agent A and Agent  B")
Swift

At the end of this sequence of messages agent1 and agent2 have a common pairwise did and are ready to communicate further via this connection using DidComm protocol

6.2.5. Encrypting - Decrypting Messages Between Agents

After the creation of connection between two agents, the rest of the communication for sensitive messages needs to be performed via a secure channel based on DidCOMM. A sample code for encrypting and decrypting a credential is given below:

Java
//send the signature and the data to issuer (via didcomm)
Map<String, String> packedSignature = new HashMap<>();
packedSignature.put("signedData", Base64.getEncoder().encodeToString(signedData));
packedSignature.put("signature", base64EncodedSignature);

String messageString = JsonHelper.toJson(packedSignature);

encryptedMessage = prover.encryptMessage(issuerConnection.getMyDid(), issuerConnection.getTheirDid(),
    NixarNativeInterface.NixarMessageType.MessageTypeSimple, messageString);
Python
packed_signature = {}
packed_signature['signedData'] = base64.b64encode(signed_data).decode('utf-8')
packed_signature['signature'] = base64_encoded_signature
logging.info("Packed signature {}".format(packed_signature))
encrypted_message = prover.connection_encrypt(from_did,
                                              their_did,
                                              NixarMessageType.MESSAGE_TYPE_SIMPLE,
                                              packed_signature)
logging.info("Prover Agent create message of signature info, encrypted_message: {}".format(encrypted_message))
Swift
//send the signature and the data to issuer (via didcomm)
var packedSignature : [String: String] = [:]
packedSignature["signedData"] = Data(signedData).base64EncodedString()
packedSignature["signature"] = base64EncodedSignature;

let messageString = try JSONHelper.encode(packedSignature);

let encryptedMessage = try agentb.encryptMessage(fromDid: connection.myDid, toDid: connection.theirDid,
                                             messageType: SwiftNixarMessageType.MessageTypeSimple, content: messageString);

6.3. Sign and Verify

Nixar API provides means to sign and verify data between agents using the did keypairs of the previously created connections.

In below example, the prover signs some data using the did of a previously created connection.

  1. Prover sign data:

    Java
    ConnectionTypes.Connection issuerConnection = findConnection(prover, publicInvitation.getLabel());
    final byte[] signedData = "Data To Be Signed".getBytes(StandardCharsets.UTF_8);
    final String base64EncodedSignature = prover.signWithDid(issuerConnection.getMyDid(), signedData);
    Python
    # Sign and verify with did
    # Prover get connections
    agent_b_conns = prover.connection_get_connections()
    from_did = agent_b_conns[0]["my_did"]
    their_did = agent_b_conns[0]["their_did"]
    
    data = "Data To Be Signed"
    signed_data = bytes(data, 'utf-8')
    logging.info("Data To Be Signed: {}".format(base64.b64encode(signed_data)));
    base64_encoded_signature = prover.sign_with_did(from_did, signed_data)
    logging.info("Signature: {}".format(base64_encoded_signature));
    Swift
    let signedData = "Some Data To Sign".data(using: .utf8)!.bytes
    print("Data To Sign \(signedData)")
    let base64EncodedSignature = try agentb.signWithDid(did: connection.myDid, data: signedData)
    print("Signature \(base64EncodedSignature)")
  2. The prover sends this data to the other agent (e.g. issuer) via did-comm.

    Java
    //send the signature and the data to issuer (via didcomm)
    Map<String, String> packedSignature = new HashMap<>();
    packedSignature.put("signedData", Base64.getEncoder().encodeToString(signedData));
    packedSignature.put("signature", base64EncodedSignature);
    
    String messageString = JsonHelper.toJson(packedSignature);
    
    encryptedMessage = prover.encryptMessage(issuerConnection.getMyDid(), issuerConnection.getTheirDid(),
        NixarNativeInterface.NixarMessageType.MessageTypeSimple, messageString);
    Python
    packed_signature = {}
    packed_signature['signedData'] = base64.b64encode(signed_data).decode('utf-8')
    packed_signature['signature'] = base64_encoded_signature
    logging.info("Packed signature {}".format(packed_signature))
    encrypted_message = prover.connection_encrypt(from_did,
                                                  their_did,
                                                  NixarMessageType.MESSAGE_TYPE_SIMPLE,
                                                  packed_signature)
    logging.info("Prover Agent create message of signature info, encrypted_message: {}".format(encrypted_message))
    Swift
    //send the signature and the data to issuer (via didcomm)
    var packedSignature : [String: String] = [:]
    packedSignature["signedData"] = Data(signedData).base64EncodedString()
    packedSignature["signature"] = base64EncodedSignature;
    
    let messageString = try JSONHelper.encode(packedSignature);
    
    let encryptedMessage = try agentb.encryptMessage(fromDid: connection.myDid, toDid: connection.theirDid,
                                                 messageType: SwiftNixarMessageType.MessageTypeSimple, content: messageString);
  3. After receiving, the decrypts the message

    Java
    final String messageJson = JsonHelper.toJson(encryptedMessage);
    final MessageTypes.NixarDecryptedMessage decryptMessage = issuer.decryptMessage(messageJson);
    
    Map<String, String> packedSignature = JsonHelper.readValue(decryptMessage.getContent(), HashMap.class);
    
    String sSignedData = packedSignature.get("signedData");
    String base64EncodedSignature = packedSignature.get("signature");
    
    byte[] signedData = Base64.getDecoder().decode(sSignedData);
    byte[] signature = Base64.getDecoder().decode(base64EncodedSignature);
    Python
    decrypted_message = issuer.connection_decrypt(encrypted_message)
    logging.info("Issuer Agent decrypt message, decrypted_message: {}".format(decrypted_message))
    unpacked_signature = json.loads(decrypted_message['content'])
    logging.info("Unpacked signature info {}".format(unpacked_signature))
    Swift
    let decryptedMessage = try agenta.decryptMessage(message: try JSONHelper.encode(encryptedMessage));
    
    let pPackedSignature = try JSONHelper.decode([String: String].self, from: decryptedMessage.content.data(using: .utf8)!);
    
    guard let pSignedDataBase64 = pPackedSignature["signedData"] else {return}
    guard let pSignatureBase64 = pPackedSignature["signature"] else {return}
    
    guard let pSignedData = Data(base64Encoded: pSignedDataBase64)?.bytes else {return}
    guard let pSignature = Data(base64Encoded: pSignatureBase64)?.bytes else {return}
  4. Finally issuer verifies the signature of the sender.

    Java
    //1. verify with their did from connection.
    final boolean b1 = issuer.verifyWithTheirDid(decryptMessage.getSenderDid(), signedData, signature);
    System.out.println("Verification Result " + b1);
    
    //2. OR verify with their PUBLIC Key
    
    final ConnectionTypes.DidDoc oTheirDidAsDidDoc = issuer.getTheirDidAsDidDoc(decryptMessage.getSenderDid());
    
    final boolean b2 = issuer.verifyWithDidPublicKey(oTheirDidAsDidDoc.getPublicKeys().get(0).getBase58EncodedPublicKey(), signedData, signature);
    System.out.println("Verification Result " + b2);
    Python
    # 1. verify with their did from connection.
    is_verified = issuer.verify_signature_with_their_did(decrypted_message['senderDid'],
                                                         unpacked_signature['signedData'],
                                                         unpacked_signature['signature'])
    
    logging.info("Verification Result with their did {}".format(is_verified))
    
    # 2. OR verify with their PUBLIC Key
    did_doc = issuer.get_their_did_as_did_doc(decrypted_message['senderDid'])
    logging.info(did_doc)
    logging.info(did_doc['publicKeys'][0])
    is_verified = prover.verify_signature_with_did_public_key(did_doc['publicKeys'][0]['value'],
                                                              unpacked_signature['signedData'],
                                                              unpacked_signature['signature'])
    
    logging.info("Verification Result {}".format(is_verified))
    Swift
    //1. verify with their did from connection.
    let b1 = try agenta.verifyWithTheirDid(theirDid:decryptedMessage.senderDid, data: pSignedData, signature: pSignature);
    print("Verification Result \(b1)");
    
    //2. OR verify with their PUBLIC Key
    
    let oTheirDidAsDidDoc = try agenta.getTheirDidAsDidDoc(theirDid: decryptedMessage.senderDid);
    
    let b2 = try agenta.verifyWithDidPublicKey(didPublicKeyBase58: (oTheirDidAsDidDoc.publicKeys?[0].base58EncodedPublicKey)!, data: pSignedData, signature: pSignature);
    print("Verification Result \(b2)");

    As you can see there are two ways of verifying a signature:

    1. Using the did of the other side

    2. Using the public key of the did of the other side

If the connection is in your wallet, using the fist method is easier. But if the signature will be validated later in other places as well, you can export the public key and save it somewhere to verify the signature explicitly.

6.4. Issue-Store-Verify-Revoke Credentials

After communication creation between different agents, the famous trinity of issue-prove-verify can be achieved. This is the basic and most generic scenario of SSID.

Please refer to [Create Agent](#Create%20Agent) section for agent creation and [Agent Connection](#connect-to-another-agent-agent1-agent2) for connection between agents.

Please also note that the message flow between the issuer, prover and verifier needs to be encrypted using DidComm. Although the examples below do not contain an encryption example, we will remind about this frequently and give an ecryption-decryption example in the end of the document.

6.4.1. Issuer Creates Schema and Credential Definition

A prerequisite of creating credentials for another entity is to create a schema and a credential definition for the target credential.

A schema defines which attributes will exist in a credential. Its basically a list of attributes with additional schema name and ID.

A credential definition cryptologically defines how an issuer will issue a credential based on the schema.

The issuer creates the schema and the credential definition and writes them to the ledger.

Java
final String schemaName = "javaTestSchema1139";

String schemaId;
try {
  SchemaTypes.Schema schema = issuer.getSchema("TbAqayWyFFK6VN7eebBTf1:2:" + schemaName + ":2.0");
  schemaId = schema.getId();
} catch (Exception ex) {
  schemaId = issuer.createSchema(schemaName, new HashSet<>(Arrays.asList("name", "surname", "age", "gender")), "2.0");
}
LOGGER.info("SchemaId: " + schemaId);

String credDefId;
try {
  final List<CredDefTypes.CredentialDefinition> credentialDefinitions = issuer.getCredentialDefinitions(schemaId);
  if (credentialDefinitions.size() > 0) {
    credDefId = credentialDefinitions.get(credentialDefinitions.size() - 1).getId();
  } else {
    throw new IllegalStateException("No cred def");
  }
} catch (Exception ex) {
  credDefId = issuer.createCredentialDefinition(schemaId, true, NixarNativeInterface.CredDefIssuanceType.IssuanceByDefault, 20);
}

LOGGER.info("CredDefId: " + credDefId);
Python
schema_version = '2.0'
schema_id = issuer.issuer_create_schema(schema_name, attribute_set, schema_version)
logging.info('Schema created, schema_id: ' + schema_id)
# if it is not revocable, CredDefIssuanceType.ISSUANCE_ON_DEMAND, max_cred_num will be ignored in nixar
cred_def_id = issuer.issuer_create_credential_definition(schema_id, is_revocable,
                                                         CredDefIssuanceType.ISSUANCE_ON_DEMAND, max_cred_num)
logging.info('Credential definition created, cred_def_id: ' + cred_def_id)
Swift

6.4.2. Issuer Creates Credential Offer

The second step of credential issuance is to create an offer for a prover. This use case can be triggered by a client (i.e. a prover requests a credential from an issuer) and issuer creates an offer as below.

Java
Random random = new Random();
byte bytes[] = new byte[10];
random.nextBytes(bytes);

BigInteger bIssuerNonce = new BigInteger(1, bytes);
String sIssuerNonce = bIssuerNonce.toString(10);

CredentialTypes.CredentialOffer credOffer = issuer.createCredentialOffer(credDefId, sIssuerNonce);
LOGGER.info("Credential offer created, credOffer: " + JsonHelper.toJson(credOffer));
Python
issuer_nonce = str(random.getrandbits(80))
cred_offer = issuer.issuer_create_credential_offer(cred_def_id, issuer_nonce)
logging.info('Credential offer created, cred_offer: ' + str(cred_offer))
Swift

Plase don’t forget to ensure message security as described in [Encrypting - Decrypting Messages Between Agents](#encrypting---decrypting-messages-between-agents)

6.4.3. Prover Creates Credential Request

Having received an offer from the issuer, the prover needs to create a credential_request from the offer. The sample code is below.

Java
CredentialTypes.CredentialRequest credReq = prover.createCredentialRequest(credOffer);
LOGGER.info("Credential request created, credReq: " + JsonHelper.toJson(credReq));
Python
cred_req = prover.prover_create_credential_request(cred_offer)
logging.info('Credential request created, cred_req: ' + str(cred_req))
Swift

Prover send credential the credential request and the issuer nonce which is used while creating credential offer to issuer to create credential.

Plase don’t forget to ensure message security as described in [Encrypting - Decrypting Messages Between Agents](#encrypting---decrypting-messages-between-agents)

6.4.4. Issuer Creates Credential

The prover creates and sends a credential request to the issuer in the previous step. In this step, the issuer will process the credential request and create a credential for prover

Java
Map<String, CredentialTypes.AttributeValues> credValues = new HashMap<>();
credValues.put("name", new CredentialTypes.AttributeValues("Mehmet"));
credValues.put("surname", new CredentialTypes.AttributeValues("Öztürk"));
credValues.put("age", new CredentialTypes.AttributeValues("30"));
credValues.put("gender", new CredentialTypes.AttributeValues("male"));

String credValues1 = JsonHelper.toJson(credValues);
CredentialTypes.CredentialInfo issuedCredInfo = issuer.createCredential(credReq, credValues1, sIssuerNonce);
LOGGER.info("Credential created, cred: " + JsonHelper.toJson(issuedCredInfo.getCredential()));
Python
issued_cred_info = issuer.issuer_create_credential(
    cred_req, json.dumps(cred_values), issuer_nonce)

cred = issued_cred_info['credential']
cred_rev_id = issued_cred_info['credRevId']
logging.info('Credential created, cred: ' + json.dumps(cred))
Swift

Plase don’t forget to ensure message security as described in [Encrypting - Decrypting Messages Between Agents](#encrypting---decrypting-messages-between-agents)

6.4.5. Prover Stores Credential

Having received the new credentials from the issuer, the prover agent needs to verify and save those credentials to its wallet. This is achieved as below:

Java
boolean storeCred = prover.storeCredential(issuedCredInfo.getCredRevId() + "", issuedCredInfo.getCredential());
LOGGER.info("Store credential created, store_cred: " + storeCred);
Python
store_cred = prover.prover_store_credential(None, cred)
logging.info('Store credential created, store_cred: ' + str(store_cred))
Swift

Plase don’t forget to ensure message security as described in [Encrypting - Decrypting Messages Between Agents](#encrypting---decrypting-messages-between-agents)

6.4.6. Verifier Creates Presentation Request (Proof Request)

Verifier is supposed to create a presentation request (i.e. e proof request) in order to provide service to an entity (i.e. a prover). A presentation request is a json object that contains information and metadata about what data the verifier needs for this specific verification scenario.

Verifier specify schema_id, cred_def_id and rev_reg_id as restriction for requested attribute. The following examples show that attribute name is name and its schema id equals to schema-x and credential definition id equals to cred-def-x.

Restriction Example 1
"attr_referent1": {
  "name": "name",
  "revealed": true,
  "restrictions": [
    {
      "schema_id": "schema-x",
      "cred_def_id": "cred-def-x",
    }
  ]
},
Restriction Example 2
"attr_referent1": {
  "name": "name",
  "revealed": true,
  "restrictions": {
    "$and":[
      {"schema_id": "schema-x"},
      {"cred_def_id": "cred-def-x"}
    ]
  }
},

Verifier also specify restriction with or, in, like and not operation. Restriction Example 3 and Restriction Example 4 show or operation usage. Restriction Example 5 shows in operation usage. You can find more complex restriction in Restriction Example 6. You can also see the complete presentation request here.

Restriction Example 4
"attr_referent1": {
  "name": "name",
  "revealed": true,
  "restrictions": {
    "$or":[
      {"schema_id": "schema-x"},
      {"cred_def_id": "cred-def-x"}
    ]
  }
},
Restriction Example 4
"attr_referent1": {
  "name": "name",
  "revealed": true,
  "restrictions": [
    {"schema_id": "schema-x"},
    {"cred_def_id": "cred-def-x"}
  ]
},
Restriction Example 5
"attr_referent1": {
  "name": "name",
  "restrictions":{
    "schema_id": {
      "$in": [
        "schema-x",
        "schema-x1"
      ]
    }
  }
},
Restriction Example 5
"attr_referent1": {
  "name": "name",
  "restrictions":{
    "$and": [
      {
        "cred_def_id": {
          "$in": [
            "rightside1",
            "rightside2"
          ]
        }
      },
      {
        "schema_id": {
          "$like": "rightside"
        }
      },
      {
        "leftside": "rightside"
      },
      {
        "cred_def_id": {
          "$neq": "rightside"
        }
      },
      {
        "$not": {
          "$or": [
            {
              "schema_id": "someright1"
            },
            {
              "rev_reg_id": "someright2"
            }
          ]
        }
      }
    ]
  }
},

Below, you can see a sample presentation request for creating a presentation.

Java
PresentationTypes.PresentationRequest presReq = new PresentationTypes.PresentationRequest();
presReq.setName("IdentityPR");
presReq.setVersion("2.0");
presReq.setNonce("88510039");// + (int) (Math.random() * 100000000));
//requested_attributes
Map<String, PresentationTypes.AttributeInfo> reqAttrs = new HashMap<>();

PresentationTypes.AttributeInfo attributeInfo1 = new PresentationTypes.AttributeInfo();
attributeInfo1.setName("name");
reqAttrs.put("attribute1_referent", attributeInfo1);

PresentationTypes.AttributeInfo attributeInfo2 = new PresentationTypes.AttributeInfo();
attributeInfo2.setName("surname");
reqAttrs.put("attribute2_referent", attributeInfo2);

PresentationTypes.AttributeInfo attributeInfo3 = new PresentationTypes.AttributeInfo();
attributeInfo3.setName("gender");
reqAttrs.put("attribute3_referent", attributeInfo3);

PresentationTypes.AttributeInfo attributeInfo4 = new PresentationTypes.AttributeInfo();
attributeInfo4.setName("hobbies");
reqAttrs.put("attribute4_referent", attributeInfo4);

presReq.setRequestedAttributes(reqAttrs);

// requested_predicates
Map<String, PresentationTypes.PredicateInfo> reqPreds = new HashMap<>();

PresentationTypes.PredicateInfo predicateInfo = new PresentationTypes.PredicateInfo();
predicateInfo.setName("age");
predicateInfo.setP_type(PresentationTypes.PredicateTypes.GT);
predicateInfo.setP_value(10);

reqPreds.put("predicate1_referent", predicateInfo);

presReq.setRequestedPredicates(reqPreds);
Python
# Default restriction operator or, the following restriction equal that
# "restrictions": { "$or":
#                   [
#                     {"schema_id": schema_id},
#                     {"cred_def_id": cred_def_id}
#                 ]}
pres_req = {
    "name": "IdentityPR",
    "version": "2.0",
    "nonce": "92210735",
    "requestedAttributes": {
        "name_attribute": {
            "name": "name",
            "restrictions": {"$and": [
                {"schema_id": schema_id},
                {"cred_def_id": cred_def_id}
            ]}
        },
        "attribute2_referent": {
            "name": "surname"
        },
        "attribute3_referent": {
            "name": "gender"
        },
        "self_attested_referent": {
            "name": "hobies"
        }
    },
    "requestedPredicates": {
        "predicate1_referent": {
            "name": "age",
            "p_type": ">",
            "p_value": 10
        }
    }
}
Swift

The json contains what data the prover needs to prove: (name, surname, gender), optional/additional data (that is not issued but provided by the prover) (hobbies), and predicates (age > 10). The verifier will send this request to the prover.

Plase don’t forget to ensure message security as described in [Encrypting - Decrypting Messages Between Agents](#encrypting---decrypting-messages-between-agents)

6.4.7. Prover Creates Presentation (Proof)

The prover receives the presentation_request from the verifier and creates a presentation (a proof) from it using its existing credentials.

Java
// Prover create presentation
String selfAttestedValues = "{\"attribute4_referent\": \"swim\"}";

PresentationTypes.Presentation pres = prover.createPresentation(presReq, selfAttestedValues);
LOGGER.info("Prover created presentation, pres: " + JsonHelper.toJson(pres));
Python
# Prover create presentation
self_attested_values = {}
self_attested_values["self_attested_referent"] = "swim"

pres = prover.prover_create_presentation(pres_req, self_attested_values)
logging.info('Prover created presentation, pres: ' + json.dumps(pres))
Swift

6.4.8. Verifier Verifies Presentation (Proof)

Having received the presentation from the prover, the verifier verifies the presentation and then decides to give the service.

Java
// verifier verify presentation
boolean vResult = verifier.verifyPresentation(presReq, pres);
LOGGER.info("Verifier verify presentation, result: " + vResult);
assert vResult;
Python
# verifier verify presentation
v_result = verifier.verifier_verify_presentation(pres_req, pres)
logging.info('verfier verify presentation, result: ' + str(v_result))
Swift

Plase don’t forget to ensure message security as described in [Encrypting - Decrypting Messages Between Agents](#encrypting---decrypting-messages-between-agents)

6.4.9. Issuer Revoke Credential

The issuer can revoke a credential that it has issued to a prover before.

Java
// issuer revoke credential
issuer.revokeCredential("" + issuedCredInfo.getCredRevId(), credDefId);
LOGGER.info("Issuer revoke credential: OK");
Python
# issuer revoke credential
issuer.issuer_revoke_credential(cred_rev_id, cred_def_id)
logging.info('Issuer revoked credential')
Swift

6.5. Tail Sharing

If the issuer has created a revokable credential definition, the tail information has to be published so that the provers can access it. The issuer can share the tail information whatever way it wants, that is not in our scope.

Here we provide samples on how to get the tails info from the issuer and how to store this information on the prover.

6.5.1. Issuer Read Tail

Java
final String tail = issuer.getTail("TbAqayWyFFK6VN7eebBTf1:4:TbAqayWyFFK6VN7eebBTf1:3:CL:302423:bilgem:CL_ACCUM:bilgem");
LOGGER.debug("TAIL");
LOGGER.debug(tail);
Python
tail_name_of_rev_reg = cred['rev_reg_id']
tail = issuer.issuer_get_tail(tail_name_of_rev_reg)
Swift

6.5.2. Prover Store Tail

Java
prover.storeTail("TmAqayWyFFK6VN7eebBTf1:4:TbAqayWyFFK6VN7eebBTf1:3:CL:302423:bilgem:CL_ACCUM:bilgem",
    tail);
Python
        # store tail into prover, note: in this example issuer and prover use same location to store tail we have to change name of tail to see system is work
        prover.prover_store_tail('P_' + tail_name_of_rev_reg, tail)
        return issued_cred_info['credRevId']


    return None


def create_proof_and_verify(pres_req):
    # Prover create presentation
    self_attested_values = {}
    self_attested_values["self_attested_referent"] = "swim"

    pres = prover.prover_create_presentation(pres_req, self_attested_values)
    logging.info('Prover created presentation, pres: ' + json.dumps(pres))

    # verifier verify presentation
    v_result = verifier.verifier_verify_presentation(pres_req, pres)
    logging.info('verfier verify presentation, result: ' + str(v_result))

    return v_result


def revoke_cred(cred_rev_id, cred_def_id):
    # issuer revoke credential
    issuer.issuer_revoke_credential(cred_rev_id, cred_def_id)
    logging.info('Issuer revoked credential')


def demo_with_revocable():
    schema_name = 'python_schema_revocable.v.0.1'
    schema_name = 'python_schema_non_revocable.v.0.1'
    attribute_set = ['name', 'surname', 'age', 'gender']
    schema_id = create_schema_if_not_exist(schema_name, attribute_set)

    cred_def_id = create_credential_definition(schema_id, True)

    cred_values = {
        "name": {
            "raw": "Metin",
            "encoded": "332414609774"
        },
        "surname": {
            "raw": "Öztürk",
            "encoded": "521660491122"
        },
        "age": {
            "raw": "30",
            "encoded": "30"
        },
        "gender": {
            "raw": "male",
            "encoded": "1835101285"
        }
    }

    cred_rev_id = create_credential(cred_def_id, cred_values)

    cred_def_id = create_credential_definition(schema_id, True)

    # create presentation reauest
    pres_req = {
        "name": "IdentityPR",
        "version": "2.0",
        "nonce": "92210735",
        "requestedAttributes": {
            "name_attribute": {
                "name": "name",
                "restrictions": {"$or": [
                    {"schema_id": schema_id},
                    {"cred_def_id": cred_def_id}
                ]}
            },
            "attribute2_referent": {
                "name": "surname"
            },
            "attribute3_referent": {
                "name": "gender"
            },
            "self_attested_referent": {
                "name": "hobies"
            }
        },
        "requestedPredicates": {
            "predicate1_referent": {
                "name": "age",
                "p_type": ">",
                "p_value": 10
            }
        }
    }

    v_result = create_proof_and_verify(pres_req)
    assert v_result == True

    # issuer revoke credential
    revoke_cred(cred_rev_id, cred_def_id)

    v_result = create_proof_and_verify(pres_req)
    assert v_result == False


def demo_with_non_revocable():
    schema_name = 'python_schema_non_revocable.v.0.1'
    attribute_set = ['name', 'surname', 'age', 'gender']
    schema_id = create_schema_if_not_exist(schema_name, attribute_set)

    cred_def_id = create_credential_definition(schema_id, False)

    cred_values = {
        "name": {
            "raw": "Metin",
            "encoded": "332414609774"
        },
        "surname": {
            "raw": "Öztürk",
            "encoded": "521660491122"
        },
        "age": {
            "raw": "30",
            "encoded": "30"
        },
        "gender": {
            "raw": "male",
            "encoded": "1835101285"
        }
    }
    cred_rev_id = create_credential(cred_def_id, cred_values)

    # Default restriction operator or, the following restriction equal that
    # "restrictions": { "$or":
    #                   [
    #                     {"schema_id": schema_id},
    #                     {"cred_def_id": cred_def_id}
    #                 ]}
    pres_req = {
        "name": "IdentityPR",
        "version": "2.0",
        "nonce": "92210735",
        "requestedAttributes": {
            "name_attribute": {
                "name": "name",
                "restrictions": {"$and": [
                    {"schema_id": schema_id},
                    {"cred_def_id": cred_def_id}
                ]}
            },
            "attribute2_referent": {
                "name": "surname"
            },
            "attribute3_referent": {
                "name": "gender"
            },
            "self_attested_referent": {
                "name": "hobies"
            }
        },
        "requestedPredicates": {
            "predicate1_referent": {
                "name": "age",
                "p_type": ">",
                "p_value": 10
            }
        }
    }

    v_result = create_proof_and_verify(pres_req)
    assert v_result == True


def demo_did_comm():
    # Agent A create connection invitation

    inv_seed = "00000000000000000000000000AGENTA"
    conn_inv = issuer.connection_create_local_invitation("agent A", "http://localhost:8080", inv_seed)
    logging.info("Agent A create connection invitation, conn_inv" +
                 json.dumps(conn_inv))

    # Agent B receive invitation and create connection request
    req_seed = "00000000000000000000000000AGENTB"
    conn_req = prover.connection_create_request(conn_inv, req_seed)
    logging.info("Agent B create connection request, conn_req" +
                 json.dumps(conn_req))

    # Agent A receive connection request and create connection response
    conn_res = issuer.connection_accept_request(conn_req, None)
    logging.info("Agent A create connection response, conn_res" +
                 json.dumps(conn_res))


    # Agent B receive connection response and finalize connection creation
    prover.connection_accept_response(conn_res)
    logging.info("Connection created between Agent A and Agent  B")


    # Agent A Connections
    agent_a_conns = issuer.connection_get_connections()
    logging.info("Agent A Connections" + json.dumps(agent_a_conns))
    assert len(agent_a_conns) == 1

    # Agent B Connections
    agent_b_conns = prover.connection_get_connections()
    logging.info("Agent B Connections" + json.dumps(agent_b_conns))
    assert len(agent_b_conns) == 1

    # Agent A Create Simpele Message
    from_did = agent_a_conns[0]["my_did"]
    their_did = agent_a_conns[0]["their_did"]
    message = "hello agent A"
    encrypted_message = issuer.connection_encrypt(
        from_did, their_did, NixarMessageType.MESSAGE_TYPE_SIMPLE, message)
    logging.info("Agent A create message, encrypted_message:" +
                 json.dumps(encrypted_message))

    # Agent B receive message and decrypt
    decrypted_message = prover.connection_decrypt(encrypted_message)
    logging.info("Agent B decrypt message, decrypted_message:" +
                 json.dumps(decrypted_message))

    # Sign and verify with did
    # Prover get connections
    agent_b_conns = prover.connection_get_connections()
    from_did = agent_b_conns[0]["my_did"]
    their_did = agent_b_conns[0]["their_did"]

    data = "Data To Be Signed"
    signed_data = bytes(data, 'utf-8')
    logging.info("Data To Be Signed: {}".format(base64.b64encode(signed_data)));
    base64_encoded_signature = prover.sign_with_did(from_did, signed_data)
    logging.info("Signature: {}".format(base64_encoded_signature));

    packed_signature = {}
    packed_signature['signedData'] = base64.b64encode(signed_data).decode('utf-8')
    packed_signature['signature'] = base64_encoded_signature
    logging.info("Packed signature {}".format(packed_signature))
    encrypted_message = prover.connection_encrypt(from_did,
                                                  their_did,
                                                  NixarMessageType.MESSAGE_TYPE_SIMPLE,
                                                  packed_signature)
    logging.info("Prover Agent create message of signature info, encrypted_message: {}".format(encrypted_message))

    decrypted_message = issuer.connection_decrypt(encrypted_message)
    logging.info("Issuer Agent decrypt message, decrypted_message: {}".format(decrypted_message))
    unpacked_signature = json.loads(decrypted_message['content'])
    logging.info("Unpacked signature info {}".format(unpacked_signature))

    # 1. verify with their did from connection.
    is_verified = issuer.verify_signature_with_their_did(decrypted_message['senderDid'],
                                                         unpacked_signature['signedData'],
                                                         unpacked_signature['signature'])

    logging.info("Verification Result with their did {}".format(is_verified))

    # 2. OR verify with their PUBLIC Key
    did_doc = issuer.get_their_did_as_did_doc(decrypted_message['senderDid'])
    logging.info(did_doc)
    logging.info(did_doc['publicKeys'][0])
    is_verified = prover.verify_signature_with_did_public_key(did_doc['publicKeys'][0]['value'],
                                                              unpacked_signature['signedData'],
                                                              unpacked_signature['signature'])

    logging.info("Verification Result {}".format(is_verified))


def read_store_tail():
    issuer.issuer_get_tail()


demo_did_comm()

demo_with_non_revocable()

demo_with_revocable()
Swift