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.

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.
public PresentationTypes.Presentation createPresentationByAST(PresentationTypes.ConditionalPresentationRequest conditionalPresentationRequest, String selfAttestedValues)
def prover_create_presentation_by_ast(self, conditional_presentation_request: dict, self_attested_values: dict) -> dict
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.
public boolean verifyPresentationByAST(PresentationTypes.ConditionalPresentationRequest conditionalPresentationRequest, PresentationTypes.Presentation presentation)
def verifier_verify_presentation_by_ast(self, conditional_presentation_request: dict, presentation: dict) -> bool
public func verifyPresentationByAST(presentationRequest: ConditionalPresentationRequest, presentation: Presentation) throws -> bool
5.2. V1.0.0-RC2
createAgent
-
AgentInitParams
has been refactored toCreateAgentInitParams
. A new field namedwalletInitParams
has been added toCreateAgentInitParams
. It allows selection of parameters with respected to the wallet type (Json, SqLite or PostgreSql)Sampletype 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):
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.
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)
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 |
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.6. Enable Cleartext traffix to http
networks
-
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: |
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.
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");
}
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
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. |
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.
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())))
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.
ConnectionTypes.ConnectionInvitation publicInvitation = issuer.createLocalInvitation("labello", "http://pil.sat", null);
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)))
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.
// Optional seed
String seed = "000000000000000000MYCONNECTION01";
final ConnectionTypes.EncryptedConnectionRequest encryptedConnectionRequest = prover.createConnectionRequest(publicInvitation, convertToBase64Seed(seed));
# 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)))
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.
final ConnectionTypes.EncryptedConnectionResponse encryptedConnectionResponse = issuer.acceptConnectionRequest(encryptedConnectionRequest, proverAlias);
# 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)))
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.
prover.acceptConnectionResponse(encryptedConnectionResponse);
# 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")
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:
//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);
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))
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.
-
Prover sign data:
JavaConnectionTypes.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));
Swiftlet 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)
-
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);
Pythonpacked_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))
Swiftvar 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 )
-
After receiving, the decrypts the message
Javafinal 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);
Pythondecrypted_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 -
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
SwiftAs you can see there are two ways of verifying a signature:
-
Using the did of the other side
-
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.
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);
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)
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.
String sIssuerNonce = NixarAgentTest.getNonce();
CredentialTypes.CredentialOffer credOffer = issuer.createCredentialOffer(credDefId, sIssuerNonce);
LOGGER.info("Credential offer created, credOffer: {}", JsonHelper.toJson(credOffer));
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)))
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.
CredentialTypes.CredentialRequest credReq = prover.createCredentialRequest(credOffer);
LOGGER.info("Credential request created, credReq: {}", JsonHelper.toJson(credReq));
cred_request = prover.prover_create_credential_request(cred_offer)
logging.info("Credential request created, cred_request: {}".format( json.dumps(cred_request)))
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
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()));
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)))
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:
boolean storeCred = prover.storeCredential(issuedCredInfo.getCredRevId() + "", issuedCredInfo.getCredential());
LOGGER.info("Store credential created, store_cred: {}", storeCred);
store_cred = prover.prover_store_credential(None, cred)
logging.info("Credential stored, store_cred: {}".format(store_cred))
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
.
"attr_referent1": {
"name": "name",
"revealed": true,
"restrictions": [
{
"schema_id": "schema-x",
"cred_def_id": "cred-def-x",
}
]
},
"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.
"attr_referent1": {
"name": "name",
"revealed": true,
"restrictions": {
"$or":[
{"schema_id": "schema-x"},
{"cred_def_id": "cred-def-x"}
]
}
},
"attr_referent1": {
"name": "name",
"revealed": true,
"restrictions": [
{"schema_id": "schema-x"},
{"cred_def_id": "cred-def-x"}
]
},
"attr_referent1": {
"name": "name",
"restrictions":{
"schema_id": {
"$in": [
"schema-x",
"schema-x1"
]
}
}
},
"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.
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);
# 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
}
}
}
//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)
{
"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)
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);
}
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
}
]
}
]
}
}
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.
// Prover create presentation
String selfAttestedValues = "{\"attribute4_referent\": \"swim\"}";
PresentationTypes.Presentation pres = prover.createPresentation(presReq, selfAttestedValues);
LOGGER.info("Prover created presentation, pres: {}", JsonHelper.toJson(pres));
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)))
// 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)
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));
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)))
// 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.
// verifier verify presentation
boolean vResult = verifier.verifyPresentation(presReq, pres);
LOGGER.info("Verifier verify presentation, result: {}", vResult);
assert vResult;
v_result = verifier.verifier_verify_presentation(pres_req, pres)
logging.info("Presentation verified, result: {}".format(v_result))
assert v_result == True
// 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
// verifier verify presentation
boolean vResult = verifier.verifyPresentationByAST(conPresReq, pres);
LOGGER.info("Presentation verified, result: {}", vResult);
assert vResult;
v_result = verifier.verifier_verify_presentation_by_ast(con_pres_req, pres)
logging.info("Presentation verified, result: {}".format(v_result))
assert v_result == True
// 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.
issuer.revokeCredential("" + issuedCredInfo.getCredRevId(), credDefId);
LOGGER.info("Issuer revoke credential: OK");
issuer.issuer_revoke_credential(cred_rev_id, cred_def_id)
logging.info("Credential revoked")
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
final String tail = issuer.getTail("TbAqayWyFFK6VN7eebBTf1:4:TbAqayWyFFK6VN7eebBTf1:3:CL:302423:bilgem:CL_ACCUM:bilgem");
LOGGER.debug("TAIL");
LOGGER.debug(tail);
tail_name_of_rev_reg = cred["rev_reg_id"]
tail = issuer.issuer_get_tail(tail_name_of_rev_reg)
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
prover.storeTail("TmAqayWyFFK6VN7eebBTf1:4:TbAqayWyFFK6VN7eebBTf1:3:CL:302423:bilgem:CL_ACCUM:bilgem",
tail);
prover.prover_store_tail("P_" + tail_name_of_rev_reg, tail)
logging.info("Tail stored")
try prover.storeTail(revRegId: "TmAqayWyFFK6VN7eebBTf1:4:TbAqayWyFFK6VN7eebBTf1:3:CL:302423:bilgem:CL_ACCUM:bilgem", tail: tail)