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. ChangeLog

5.1. V1.0.0-RC3 (current version)

Extended Conditional Presentation Support

In this version, we introduce conditional presentation support. The main idea is to be able to evaluate a complex precondition during credential creation and credential verification.

The conditional queries are defined as a JSON AST (Abstract Syntax Tree), and embedded into a new Presentation Request format (V3). See a sample presentation request in JSON AST here Presentation Request V3.

As you can see, the restictions are way more complex and flexible then the previous versions. You can recursively deepen the AST tree as you want.

NixarAgent::createPresentationByAST

Given a PresentationTypes.ConditionalPresentationRequest object, create a presentation.

Java
public PresentationTypes.Presentation createPresentationByAST(PresentationTypes.ConditionalPresentationRequest conditionalPresentationRequest, String selfAttestedValues)
Python
def prover_create_presentation_by_ast(self, conditional_presentation_request: dict, self_attested_values: dict) -> dict
Swift
public func createPresentationByAST(presentationRequest: ConditionalPresentationRequest, selfAttestedValues: String) throws -> Presentation

NixarAgent::verifyPresentationByAST

Given a presentation and an ast, verify the presentation by comparing to the ast and evaluating the ast.

Java
public boolean verifyPresentationByAST(PresentationTypes.ConditionalPresentationRequest conditionalPresentationRequest, PresentationTypes.Presentation presentation)
Python
def verifier_verify_presentation_by_ast(self, conditional_presentation_request: dict, presentation: dict) -> bool
Swift
public func verifyPresentationByAST(presentationRequest: ConditionalPresentationRequest, presentation: Presentation) throws -> bool

5.2. V1.0.0-RC2

createAgent

  • AgentInitParams has been refactored to CreateAgentInitParams. A new field named walletInitParams has been added to CreateAgentInitParams. It allows selection of parameters with respected to the wallet type (Json, SqLite or PostgreSql)

    Sample
    type CreateAgentInitParams {
      String alias;
      String role;
      String seed;
      String genesisPath;
      IWalletInitParams walletInitParams;
    }
    
    trait IWalletInitParams {
    }
    
    type JsonWalletInitParams : IWalletInitParams {
      String walletName;
    }
    
    
    type JsonWalletInitParams : IWalletInitParams {
      String walletName;
    }
    
    
    type JsonWalletInitParams : IWalletInitParams {
      String walletName;
    }
    
    type SqliteWalletInitParams : IWalletInitParams {
      String walletName;
    }
    
    type PostgresWalletInitParams : IWalletInitParams {
      String dbHost;
      String dbUsername;
      String dbPassword;
      String walletName;
    }

    Depending on your requirements, you can choose to initialize the underlying wallet choosing any of above init params.

openAgent

  • genesisPath parameter has been dropped

createCredentialDefinition(String schemaId, boolean isRevocable, NixarNativeInterface.CredDefIssuanceType issuanceType, int maxCredNum,String tag)

A new tag parameter was added to the function call. Sample call (java):

Sample Call
String schemaId = NixarAgentTestUtil.getOrCreateSchema(issuer, basicUniversityDegreeSchema.getSchema("23YD3dyHuyFvLpLC3zF58F:2:"));
String credDefId = NixarAgentTestUtil.getOrCreateCredentialDefinition(issuer, schemaId, false, NixarNativeInterface.CredDefIssuanceType.IssuanceByDefault, "bilgem");

changePassword

The wallet password can be updated with the new changePassword api.

Sample Signature (Java)
public void changePassword(Supplier<char[]> newPasswordCallback) {
  NixarApiHelper.WalletPasswordCallback walletPasswordCallback = new NixarApiHelper.WalletPasswordCallback(newPasswordCallback);
  NixarAgentFactory.changePassword(this.handle, walletPasswordCallback);
}

Please see Change Password for sample usage.

5.3. V0.3.3

  • The seeds are accepted as base64 strings by the library. Please see the examples

  • The DIDInfo structure/class returns the verkey. See the wrapper codes

  • Added getPublicDid() to the agent api.

  • DOCS IOS samples are now taken from the nixar_api_ios unit tests, instead of the kickstart.

6. Prerequisites

6.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 (V1.0.0-RC3)

Table 1. List of Binaries
Platform/Resource Link

OsX-X86_64

https://proje.bag.org.tr/nixar_shared/nixar_api_osx/-/tree/1.0.0-RC3

OsX-apple-silicon

https://proje.bag.org.tr/nixar_shared/nixar_api_apple_silicon/-/tree/1.0.0-RC3

IOS Swift Wrapper (Including IOS binaries)

https://proje.bag.org.tr/nixar_shared/nixar_api_ios/-/tree/1.0.0-RC3

Linux (arm64, x86_64)

https://proje.bag.org.tr/nixar_shared/nixar_api_linux/-/tree/1.0.0-RC3

Android

https://proje.bag.org.tr/nixar_shared/nixar_api_android/-/tree/1.0.0-RC3

Win_x86_64

NA

Table 2. Other Resources
Platform/Resource Link

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

https://proje.bag.org.tr/nixar_shared/nixar_api_docs/-/tree/1.0.0-RC3

Java Wrapper (Compatible with android)

https://proje.bag.org.tr/nixar_shared/nixar_api_java/-/tree/1.0.0-RC3

Python Wrapper

https://proje.bag.org.tr/nixar_shared/nixar_api_python/-/tree/1.0.0-RC3

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, libzmq.5.dylib, libsodium.23.dylib. Please note that you can also use compatible versions of the dependent libraries (i.e. LibZmq and LibSodium) that preinstalled on your environment. See following sections for more details.

6.2. Android: Notes for users

6.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.

6.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

6.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

6.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.

6.2.5. Android Kickstart

An android kickstart project has been created here:

6.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.

7. 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:
Table 3. Wrapper Addresses
Language Address

Java

https://proje.bag.org.tr/nixar_shared/nixar_api_java/-/tree/1.0.0-RC3

Python

https://proje.bag.org.tr/nixar_shared/nixar_api_python/-/tree/1.0.0-RC3

Swift

https://proje.bag.org.tr/nixar_shared/nixar_api_ios/-/tree/1.0.0-RC3

7.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
WalletType nixarAgentWalletType = WalletType.JsonWallet;

CreateAgentInitParams createAgentInitParams = new CreateAgentInitParams("Trustee1", RoleType.TRUSTEE.toString(), convertToBase64Seed("000000000000000000000000Trustee1"), genesisPath, createWalletInitParams("Trustee1", nixarAgentWalletType));
trustee = NixarAgent.createAgent(createAgentInitParams, "123456"::toCharArray);
LOGGER.info("Trustee agent created");

CreateAgentInitParams params = null;

String issuer1Alias = "IssuerSample1234";
try {
  params = new CreateAgentInitParams(issuer1Alias, RoleType.ENDORSER.toString(), NixarAgentTest.convertToBase64Seed(issuer1Alias), genesisPath, NixarAgentTest.createWalletInitParams(issuer1Alias, nixarAgentWalletType));
  issuer = NixarAgent.createAgent(params, "123456"::toCharArray);

  LOGGER.info("Issuer agent created");
} catch (AgentNotRegisteredException ex) {
  RegistrationRequestInfo registrationRequestInfo = new RegistrationRequestInfo(ex.did, ex.verkey, params.getRole(), params.getAlias());

  trustee.registerAgentToLedger(registrationRequestInfo);

  issuer = NixarAgent.createAgent(params, "123456"::toCharArray);
  LOGGER.info("Issuer agent created");
}
Python
try:
  return Nixar(name, password_cb, role, None, "sqlite")
except NixarError as err:
  # Register the agent to the ledger. This operation must be done by different authority or process
  if "AgentNotRegistered" == err.code:
    logger.warning(err)
    registration_info = json.loads(err.message)
    registration_info["alias"] = name
    registration_info["role"] = "ENDORSER"
    _register_agent_to_ledger(registration_info)
    return Nixar(name, password_cb, role, None, "sqlite")
  else:
    raise err
Swift
let issuerAlias = "tub_ios_issuer"
let genesisPath = try nixar_api_iosTests.createGenesisFile();
let agentInitParams = AgentInitParams(alias: issuerAlias,
                                      role: "ENDORSER",
                                      base64Seed: convertToBase64Seed("0000000000000000000000TUBIOSUER1"),
                                      genesisPath: genesisPath,
                                      walletInitParams: WalletInitParams.json(
                                        JsonWalletInitParams.init(walletName: issuerAlias)
                                      ))

let passworCallback = { () in
  return "123456"
}

var issuer : NixarAgent
do {
  issuer = try NixarAgentFactory.createAgent(agentInitParams:agentInitParams, passwordCallback:passworCallback)
  print("Issuer agent created");
} catch NixarApiException.AgentNotRegistered(let did, let verkey) {
  print("Agent not registered")
  print("DID: ", did)
  print("Verykey: ", verkey)
  
  let registrationRequestInfo = RegistrationRequestInfo(
    did: did,
    verkey: verkey,
    role: agentInitParams.role!, alias: agentInitParams.alias!)
  try trustee.registerAgentToLedger(registrationRequestInfo: registrationRequestInfo)
  
  issuer = try NixarAgentFactory.createAgent(agentInitParams: agentInitParams, passwordCallback:passworCallback);
  print("Issuer agent created");
}

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.

7.2. Change Password

You can change the agent’s wallet password as provided below.

This operation will not replace the original callback that you provided in during agent creation. Therefore, you should make sure that after the password change, the initial password callback provides the new password. Please check the test codes in the wrappers for details.
Java
final String newPassword = "12345678";
prover.changePassword(newPassword::toCharArray);
// Please note that the old password will also be prompted during the update.
// Since we don't cache the passwords, and since the prompt of the current password is expected
// to be interactive or ultimately interactive in a real world scenario, we expect a seamless change.
// However, in the test code, we have to make sure that, the original callback that we
// provided during the "agent creation" will provide the NEW password after "changePassword" has finished.
Python
logging.info("Prover Credential list: {}".format(json.dumps(prover.prover_get_credentials())))
prover_new_password = test_utils.native_string("Nixar123456")
prover.change_password(lambda: prover_new_password)
prover_password = prover_new_password
logging.info("Prover Credential list: {}".format(json.dumps(prover.prover_get_credentials())))
Swift
let newPassworCallback = { () in
  return "12345678"
}

try prover.changePassword(passwordCallback: newPassworCallback)
// Please note that the old password will also be prompted during the update.
// Since we don't cache the passwords, and since the prompt of the current password is expected
// to be interactive or ultimately interactive in a real world scenario, we expect a seamless change.
// However, in the test code, we have to make sure that, the original callback that we
// provided during the "agent creation" will provide the NEW password after "changePassword" has finished.

7.3. 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

7.3.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.createLocalInvitation("labello", "http://pil.sat", null);
Python
inv_base64_seed = test_utils.encode_base64("00000000000000000000000000AGENTA")
conn_inv = agent_a.connection_create_local_invitation("agent A", "http://localhost:8080", inv_base64_seed)
logging.info("Agent A create connection invitation, conn_inv: {}".format(json.dumps(conn_inv)))
Swift
let publicInvitation = try issuer.createLocalInvitation(label: "labello", endpoint: "http://pil.sat", seed: nil)

7.3.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
// Optional seed
String seed = "000000000000000000MYCONNECTION01";
final ConnectionTypes.EncryptedConnectionRequest encryptedConnectionRequest = prover.createConnectionRequest(publicInvitation, convertToBase64Seed(seed));
Python
# Agent B receive invitation and create connection request
req_base64_seed = test_utils.encode_base64("00000000000000000000000000AGENTB")
conn_req = agent_b.connection_create_request(conn_inv, req_base64_seed)
logging.info("Agent B create connection request, conn_req: {}".format(json.dumps(conn_req)))
Swift
let seed = "000000000000000000MYCONNECTION01"
let encryptedConnectionRequest = try prover.createConnectionRequest(
  invitation: publicInvitation,
  seed: convertToBase64Seed(seed)
)

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.

7.3.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 = agent_a.connection_accept_request(conn_req, None)
logging.info("Agent A create connection response, conn_res: {}".format(json.dumps(conn_res)))
Swift
let encryptedConnectionResponse = try issuer.acceptConnectionRequest(requestMessage: encryptedConnectionRequest, alias: proverAlias)

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.

7.3.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
agent_b.connection_accept_response(conn_res)
logging.info("Connection created between Agent A and Agent  B")
Swift
try prover.acceptConnectionResponse(responseMessage: encryptedConnectionResponse)

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

7.3.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'] = signed_data
packed_signature['signature'] = base64_encoded_signature
logging.info("Packed signature {}".format(packed_signature))
encrypted_message = agent_b.connection_encrypt(from_did,
                                               their_did,
                                               NixarMessageType.MESSAGE_TYPE_SIMPLE,
                                               packed_signature)
logging.info("Agent B create message of signature info, encrypted_message: {}".format(encrypted_message))
Swift
var packedSignature: [String: String] = [:]
packedSignature["signedData"] = signedDataAsData.base64EncodedString()
packedSignature["signature"] = base64EncodedSignature

let messageString = try JSONHelper.encode(packedSignature)

var _: NixarEncryptedMessage = try prover.encryptMessage(
  fromDid: (issuerConnection?.myDid)!,
  toDid: (issuerConnection?.theirDid)!,
  messageType: SwiftNixarMessageType.MessageTypeSimple,
  content: messageString
)

7.4. 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 = NixarAgentTest.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
    # agent_b get connections
    agent_b_conns = agent_b.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 = test_utils.encode_base64(data)
    logging.info("Data To Be Signed: {}".format(signed_data));
    base64_encoded_signature = agent_b.sign_with_did(from_did, signed_data)
    logging.info("Signature: {}".format(base64_encoded_signature));
    Swift
    let issuerConnection = try findConnection(prover, publicInvitation.label)
    let signedData: [UInt8] = Array("Data To Be Signed".utf8)
    let base64EncodedSignature = try prover.signWithDid(did: (issuerConnection?.myDid)!, data: signedData)
  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'] = signed_data
    packed_signature['signature'] = base64_encoded_signature
    logging.info("Packed signature {}".format(packed_signature))
    encrypted_message = agent_b.connection_encrypt(from_did,
                                                   their_did,
                                                   NixarMessageType.MESSAGE_TYPE_SIMPLE,
                                                   packed_signature)
    logging.info("Agent B create message of signature info, encrypted_message: {}".format(encrypted_message))
    Swift
    var packedSignature: [String: String] = [:]
    packedSignature["signedData"] = signedDataAsData.base64EncodedString()
    packedSignature["signature"] = base64EncodedSignature
    
    let messageString = try JSONHelper.encode(packedSignature)
    
    var _: NixarEncryptedMessage = try prover.encryptMessage(
      fromDid: (issuerConnection?.myDid)!,
      toDid: (issuerConnection?.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 = agent_a.connection_decrypt(encrypted_message)
    logging.info("Agent A decrypt message, decrypted_message: {}".format(decrypted_message))
    unpacked_signature = json.loads(decrypted_message['content'])
    logging.info("Unpacked signature info: {}".format(unpacked_signature))
    Swift
  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);
    LOGGER.info("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);
    LOGGER.info("Verification Result {}", b2);
    Python
    # 1. verify with their did from connection.
    is_verified = agent_a.verify_signature_with_their_did(decrypted_message['senderDid'],
                                                          unpacked_signature['signedData'],
                                                          unpacked_signature['signature'])
    logging.info("Verification result with their did {}".format(is_verified))
    assert is_verified == True
    
    # 2. OR verify with their PUBLIC Key
    did_doc = agent_a.get_their_did_as_did_doc(decrypted_message['senderDid'])
    logging.info(did_doc)
    logging.info(did_doc['publicKeys'][0])
    is_verified = agent_b.verify_signature_with_did_public_key(did_doc['publicKeys'][0]['value'],
                                                               unpacked_signature['signedData'],
                                                               unpacked_signature['signature'])
    
    logging.info("Verification result with their PUBLIC Key {}".format(is_verified))
    assert is_verified == True
    Swift

    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.

7.5. 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.

7.5.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
String schemaId;
try {
  SchemaTypes.Schema schema = agent.getSchema(expectedSchema.getId());
  schemaId = schema.getId();
} catch (Exception ex) {
  LOGGER.error(ex.getMessage(), ex);
  schemaId = agent.createSchema(expectedSchema.getName(), (HashSet<String>) expectedSchema.getAttrNames(), expectedSchema.getVersion());
}
LOGGER.info("Schema created, SchemaId: {}", schemaId);

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

LOGGER.info("Credential definition created, credDefId: {}", credDefId);
Python
schema_version = "2.0"
schema_id = issuer.issuer_create_schema(schema_name, attribute_set, schema_version)
logger.info("Schema created, schema_id: {}".format(schema_id))
# if it is not revocable, issuance_type, max_cred_num will be ignored in nixar
cred_def_id = issuer.issuer_create_credential_definition(schema_id, is_revocable, tag, issuance_type, MAX_CRED_NUM)
logger.info("Credential definition created, cred_def_id: " + cred_def_id)
Swift
var schemaId: String

do {
  let schema = try agent.getSchema(schemaId: expectedSchema.id!)
  schemaId = schema.id!
} catch {
  print("Error", error.localizedDescription, error)
  schemaId = try agent.createSchema(
    schemaName: expectedSchema.name, attributeSet: Set(expectedSchema.attrNames), schemaVersion: expectedSchema.version
  )
}

print("SchemaId: \(schemaId)")
var credDefId: String

do {
  let credentialDefinitions = try agent.getCredentialDefinitions(schemaId: schemaId)
  if credentialDefinitions.count > 0 {
    credDefId = credentialDefinitions[credentialDefinitions.count - 1].id
  } else {
    throw NixarApiException.ApiError(code: "NoCredentialDefinition", description: "No cred def")
  }
} catch {
  print("Error", error.localizedDescription, error)
  credDefId = try agent.createCredentialDefinition(
    schemaId: schemaId,
    isRevocable: isRevocable,
    issuanceType: issuanceType,
    maxCredNum: 20,
    tag: tag
  )
}

print("CredDefId: \(credDefId)")

7.5.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
String sIssuerNonce = NixarAgentTest.getNonce();
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:{}".format(json.dumps(cred_offer)))
Swift
let sIssuerNonce = getNonce()
let credOffer = try issuer.createCredentialOffer(credDefId: credDefId, sIssuerNonce)
print("Credential offer created, credOffer: \(try JSONHelper.encode(credOffer))")

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

7.5.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_request = prover.prover_create_credential_request(cred_offer)
logging.info("Credential request created, cred_request: {}".format( json.dumps(cred_request)))
Swift
let credReq = try prover.createCredentialRequest(credOffer: credOffer)
print("Credential request created, credReq: \(try JSONHelper.encode(credReq))")

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)

7.5.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 = basicIdentitySchema.fillAttributeValues("Mehmet", "Öztürk", "30", "Male");
String credValues1 = JsonHelper.toJson(credValues);
CredentialTypes.CredentialInfo issuedCredInfo = issuer.createCredential(credReq, credValues1, sIssuerNonce);
LOGGER.info("Credential created, cred: {}", JsonHelper.toJson(issuedCredInfo.getCredential()));
Python
cred_values = generate_sample_cred_values(attribute_set)
cred_info = issuer.issuer_create_credential(cred_request, cred_values, issuer_nonce)
cred = cred_info["credential"]
cred_rev_id = cred_info["credRevId"]
logging.info("Credential created, cred: {}".format( json.dumps(cred)))
Swift
let credValues = basicIdentitySchema.fillAttributeValues(name: "Mehmet", surname: "Öztürk", age: "30", gender: "Male")
let credValues1 = try JSONHelper.encode(credValues)
let issuedCredInfo = try issuer.createCredential(credRequest: credReq, credValues: credValues1, sIssuerNonce)
print("Credential created, cred: \(try JSONHelper.encode(issuedCredInfo.credential))")

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

7.5.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("Credential stored, store_cred: {}".format(store_cred))
Swift
let storeCred = try prover.storeCredential(credId: issuedCredInfo.credRevId!, credential: issuedCredInfo.credential)
print("Store credential created, store_cred: \(storeCred)")

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

7.5.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": {
    "attribute1_referent": {
      "name": "name",
      # Optional
      "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
//requested_attributes
var reqAttrs: [String: AttributeInfo] = [:]

let attributeInfo1 = AttributeInfo(name: "name")
reqAttrs["attribute1_referent"] = attributeInfo1

let attributeInfo2 = AttributeInfo(name: "surname")
reqAttrs["attribute2_referent"] = attributeInfo2

let attributeInfo3 = AttributeInfo(name: "gender")
reqAttrs["attribute3_referent"] = attributeInfo3

let attributeInfo4 = AttributeInfo(name: "hobbies")
reqAttrs["attribute4_referent"] = attributeInfo4

// requested_predicates
var reqPreds: [String: PredicateInfo] = [:]

let predicateInfo = PredicateInfo(name: "age", p_type: PredicateTypes.GT, p_value: 10)
reqPreds["predicate1_referent"] = predicateInfo

let presReq = PresentationRequest(nonce: "88510039", name: "IdentityPR", version: "2.0", requestedAttributes: reqAttrs, requestedPredicates: reqPreds, nonRevokedInterval: Optional.none)

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)

7.5.7. Verifier Creates Conditional Presentation Request (Pres. Req V3)

Instead of using legacy presentation requests, we defined a new request type, which handles restrictions in a more advanced, idiomatic manner and supports advanced and complex conditional logic, via operators like and, or, eq, neq, ge, le, gt, lt` and in and supports recursive conditionals. We represent this new conditional format in a JSON based "AST" (abstract syntax tree) (See below for an example)

Presentation Request V3
{
  "Version": "3.0",
  "Nonce": "1234566544",
  "RequestedAttributes": ["schema1.name", "surname"],
  "Restrictions": {
    "And": [
      {
        "Eq": [{"Identifier": "schema1.city"}, {"String": "İstanbul"}]
      },
      {
        "Or": [
          {
            "Eq": [{"Identifier": "is_active_student"}, {"String": "yes"}]
          },
          {
            "In": {
              "left": {"Identifier": "schema1.occupation"},
              "right": [
                {"String": "Medic"},
                {"String": "Instructor"}
              ]
            }
          }
        ]
      }
    ]
  }
}

Sample code for creating presentation request V3 (Conditional Pres. Request)

Java
private PresentationTypes.ConditionalPresentationRequest getConditionalPresentationRequest() {
  //gender="Male"
  PresentationTypes.Expr.Eq gender = new PresentationTypes.Expr.Eq(List.of(new PresentationTypes.Operand.IdentifierOperand("gender"), new PresentationTypes.Operand.StringOperand("Male")));

  //age >=25 and age <=35
  PresentationTypes.Expr.Lte ageLte = new PresentationTypes.Expr.Lte(List.of(new PresentationTypes.Operand.IdentifierOperand("age"), new PresentationTypes.Operand.NumberOperand(35.0)));
  PresentationTypes.Expr.Gte ageGte = new PresentationTypes.Expr.Gte(List.of(new PresentationTypes.Operand.IdentifierOperand("age"), new PresentationTypes.Operand.NumberOperand(25.0)));
  PresentationTypes.Expr.And age = new PresentationTypes.Expr.And(List.of(ageLte, ageGte));

  //department in ("Bilgisayar Mühendisliği","Bilişim Sistemleri Mühendisliği","Elektrik-Elektronik Mühendisliği","Elektrik Mühendisliği","Endüstri Mühendisliği")
  PresentationTypes.InExpr inExpr = new PresentationTypes.InExpr(new PresentationTypes.Operand.IdentifierOperand("department"),
    List.of(new PresentationTypes.Operand.StringOperand("Bilgisayar Mühendisliği"),
      new PresentationTypes.Operand.StringOperand("Bilişim Sistemleri Mühendisliği"),
      new PresentationTypes.Operand.StringOperand("Elektrik-Elektronik Mühendisliği"),
      new PresentationTypes.Operand.StringOperand("Elektrik Mühendisliği"),
      new PresentationTypes.Operand.StringOperand("Endüstri Mühendisliği")));
  PresentationTypes.Expr.In in = new PresentationTypes.Expr.In(inExpr);

  //grade >=3.
  PresentationTypes.Expr.Gte grade = new PresentationTypes.Expr.Gte(List.of(new PresentationTypes.Operand.IdentifierOperand("grade"), new PresentationTypes.Operand.NumberOperand(3.0)));

  //has labor_number
  PresentationTypes.Expr.Exists laborNumber = new PresentationTypes.Expr.Exists(new PresentationTypes.Operand.IdentifierOperand("labor_number"));

  //profession="Bilgisayar Mühendisi"
  PresentationTypes.Expr.Eq profession = new PresentationTypes.Expr.Eq(List.of(new PresentationTypes.Operand.IdentifierOperand("profession"), new PresentationTypes.Operand.StringOperand("Bilgisayar Mühendisi")));

  //salary
  PresentationTypes.Expr.Gte salary = new PresentationTypes.Expr.Gte(List.of(new PresentationTypes.Operand.IdentifierOperand("salary"), new PresentationTypes.Operand.NumberOperand(25000.0)));

  // And all conditions
  PresentationTypes.Expr.And expr = new PresentationTypes.Expr.And(List.of(gender, in, age, grade, laborNumber, profession, salary));

  return new PresentationTypes.ConditionalPresentationRequest("2.0", "1023493215165936210711920", List.of("name", "surname"), expr);
}
Python
con_pre_req = {
  "version": "1.0",
  "nonce": "1023493215165936210711920",
  "requestedAttributes": [
    "name",
    "surname"
  ],
  "restrictions": {
    "And": [
      {
        "And": [
          {
            "Gte": [
              {
                "Identifier": "age"
              },
              {
                "Number": 18.0
              }
            ]
          },
          {
            "Lte": [
              {
                "Identifier": "age"
              },
              {
                "Number": 40.0
              }
            ]
          }
        ]
      },
      {
        "In": {
          "left": {
            "Identifier": "department"
          },
          "right": [
            {
              "String": "Bilgisayar Mühendisliği"
            },
            {
              "String": "Bilişim Sistemleri Mühendisliği"
            },
            {
              "String": "Elektrik-Elektronik Mühendisliği"
            },
            {
              "String": "Elektrik Mühendisliği"
            },
            {
              "String": "Endüstri Mühendisliği"
            }
          ]
        }
      },
      {
        "Gte": [
          {
            "Identifier": "grade"
          },
          {
            "Number": 2.0
          }
        ]
      },
      {
        "Exists": {
          "Identifier": "labor_number"
        }
      },
      {
        "Eq": [
          {
            "Identifier": "profession"
          },
          {
            "String": "Bilgisayar Mühendisi"
          }
        ]
      },
      {
        "Gte": [
          {
            "Identifier": "salary"
          },
          {
            "Number": 35000.0
          }
        ]
      }
    ]
  }
}
Swift
func getConditionalPresentationRequest() -> ConditionalPresentationRequest {
  let gender = ExprWrapper.eq(
    Eq(eq: [
      .identifier(IdentifierOperand(value: "gender")),
      .string(StringOperand(value: "Male"))
    ])
  )
  
  let ageLte = ExprWrapper.lte(
    Lte(lte: [
      .identifier(IdentifierOperand(value: "age")),
      .number(NumberOperand(value: 35.0))
    ])
  )
  
  let ageGte = ExprWrapper.gte(
    Gte(gte: [
      .identifier(IdentifierOperand(value: "age")),
      .number(NumberOperand(value: 25.0))
    ])
  )
  
  let age = ExprWrapper.and(
    And(and: [ageLte, ageGte])
  )
  
  let inExpr = InExpr(
    left: IdentifierOperand(value: "department"),
    right: [
      .string(StringOperand(value: "Bilgisayar Mühendisliği")),
      .string(StringOperand(value: "Bilişim Sistemleri Mühendisliği")),
      .string(StringOperand(value: "Elektrik-Elektronik Mühendisliği")),
      .string(StringOperand(value: "Elektrik Mühendisliği")),
      .string(StringOperand(value: "Endüstri Mühendisliği"))
    ]
  )
  
  let `in` = ExprWrapper.in(In(in: inExpr))
  
  let grade = ExprWrapper.gte(
    Gte(gte: [
      .identifier(IdentifierOperand(value: "grade")),
      .number(NumberOperand(value: 3.0))
    ])
  )
  
  let laborNumber = ExprWrapper.exists(
    Exists(exists: IdentifierOperand(value: "labor_number"))
  )
  
  let profession = ExprWrapper.eq(
    Eq(eq: [
      .identifier(IdentifierOperand(value: "profession")),
      .string(StringOperand(value: "Bilgisayar Mühendisi"))
    ])
  )
  
  let salary = ExprWrapper.gte(
    Gte(gte: [
      .identifier(IdentifierOperand(value: "salary")),
      .number(NumberOperand(value: 25000.0))
    ])
  )
  
  let expr = ExprWrapper.and(
    And(and: [gender, `in`, age, grade, laborNumber, profession, salary])
  )
  
  return ConditionalPresentationRequest(
    version: "2.0",
    nonce: "1023493215165936210711920",
    requestedAttributes: ["name", "surname"],
    restrictions: expr
  )
}

7.5.8. 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
self_attested_values = {"self_attested_referent": "swim"}

pres_req = get_presentation_request(schema_id, cred_def_id)
pres = prover.prover_create_presentation(pres_req, self_attested_values)
logging.info("Presentation created, pres: {}".format(json.dumps(pres)))
Swift
// Prover create presentation
let selfAttestedValues = "{\"attribute4_referent\": \"swim\"}"

var pres = try prover.createPresentation(presentationRequest: presReq, selfAttestedValues: selfAttestedValues)
print("Prover created presentation, pres: \(try JSONHelper.encode(pres))")

7.5.9. Prover Creates Presentation with Conditional Presentation Request (Pres. Req V3)

Java
PresentationTypes.ConditionalPresentationRequest conPresReq = getConditionalPresentationRequest();
LOGGER.info("Conditional presentation request created, conPresReq: {}", JsonHelper.toJson(conPresReq));
//Create conditional presentation request.
PresentationTypes.Presentation pres = prover.createPresentationByAST(conPresReq, null);
LOGGER.info("Presentation created, pres: {}", JsonHelper.toJson(pres));
Python
con_pres_req = test_utils.get_conditional_presentation_request()
logging.info("Conditional presentation request created, con_pres_req: {}".format(json.dumps(con_pres_req)))
self_attested_values = {}
pres = prover.prover_create_presentation_by_ast(con_pres_req, self_attested_values)
logging.info("Presentation created, pres: {}".format(json.dumps(pres)))
Swift
// Prover create presentation
let selfAttestedValues = "{\"attribute4_referent\": \"swim\"}"

var pres = try prover.createPresentation(presentationRequest: presReq, selfAttestedValues: selfAttestedValues)
print("Prover created presentation, pres: \(try JSONHelper.encode(pres))")

7.5.10. 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
v_result = verifier.verifier_verify_presentation(pres_req, pres)
logging.info("Presentation verified, result: {}".format(v_result))
assert v_result == True
Swift
// verifier verify presentation
var vResult = try verifier.verifyPresentation(presentationRequest: presReq, presentation: pres)
print("Verifier verify presentation, result: \(vResult)")
XCTAssert(vResult == true)

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

7.5.11. Verifier Verifies Conditional Presentation Request

Java
// verifier verify presentation
boolean vResult = verifier.verifyPresentationByAST(conPresReq, pres);
LOGGER.info("Presentation verified, result: {}", vResult);
assert vResult;
Python
v_result = verifier.verifier_verify_presentation_by_ast(con_pres_req, pres)
logging.info("Presentation verified, result: {}".format(v_result))
assert v_result == True
Swift
// verifier verify presentation
let vResult = try verifier.verifyPresentationByAST(conditionalPresentationRequest: conPresReq, presentation: pres)
print("Verifier verify presentation, result: \(vResult)")
assert(vResult)

7.5.12. Issuer Revoke Credential

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

Java
issuer.revokeCredential("" + issuedCredInfo.getCredRevId(), credDefId);
LOGGER.info("Issuer revoke credential: OK");
Python
issuer.issuer_revoke_credential(cred_rev_id, cred_def_id)
logging.info("Credential revoked")
Swift
try issuer.revokeCredential(credRevIndex: issuedCredInfo.credRevId!, credDefId: credDefId)
print("Issuer revoke credential: OK")

7.6. 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.

7.6.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
let tail = try issuer.getTail(revRegId: "TbAqayWyFFK6VN7eebBTf1:4:TbAqayWyFFK6VN7eebBTf1:3:CL:302423:bilgem:CL_ACCUM:bilgem")
print("TAIL")
print(tail)

7.6.2. Prover Store Tail

Java
prover.storeTail("TmAqayWyFFK6VN7eebBTf1:4:TbAqayWyFFK6VN7eebBTf1:3:CL:302423:bilgem:CL_ACCUM:bilgem",
  tail);
Python
prover.prover_store_tail("P_" + tail_name_of_rev_reg, tail)
logging.info("Tail stored")
Swift
try prover.storeTail(revRegId: "TmAqayWyFFK6VN7eebBTf1:4:TbAqayWyFFK6VN7eebBTf1:3:CL:302423:bilgem:CL_ACCUM:bilgem", tail: tail)