Indy SDK

https://raw.githubusercontent.com/hyperledger/indy-node/master/collateral/logos/indy-logo.png

This is the official documentation for the Hyperledger Indy SDK, which provides a distributed-ledger-based foundation for self-sovereign identity. Indy provides a software ecosystem for private, secure, and powerful identity, and the Indy SDK enables clients for it. The major artifact of the SDK is a c-callable library; there are also convenience wrappers for various programming languages and Indy CLI tool.

All bugs, stories, and backlog for this project are managed through Hyperledger’s Jira in project IS (note that regular Indy tickets are in the INDY project instead…). Also, make sure to join us on Hyperledger’s Rocket.Chat at #indy-sdk to discuss. You will need a Linux Foundation login to get access to these channels.

Go to the next page to get started using the Indy SDK.

Find the other Indy docs at https://hyperledger-indy.readthedocs.io

Getting Started

Welcome to the Indy SDK! This is the best place to be introduced to the Indy ecosystem. If you are looking to first understand how Indy SDK can be used to implement self-sovereign identity and verifiable credentials, first check out the video of a demo created by IBM:





If you have just started learning about self-sovereign identity, here are some resources to increase your understanding:

  • Hyperledger Indy Working Group calls happen every Thursday at 8amPT, 9amMT, 11amET, 4pmBST. Add to your calendar and join from any device: https://zoom.us/j/232861185
  • A recent webinar explaining self-sovereign identity using Hyperledger Indy and Sovrin: SSI Meetup Webinar
  • Visit the main resource for all things “Indy” to get acquainted with the code base, helpful resources, and up-to-date information: Hyperledger Wiki-Indy
  • The next page contains an extended tutorial introduces Indy, explains how the whole ecosystem works, and how the functions in the SDK can be used to construct rich clients.

To begin writing code, we recommend running the Getting Started Guide with Docker

If you’d like a more thorough technical walkthrough of SSI using Indy, take a look at the Indy Story Walkthrough that we’ve written.

Once you’ve looked through these guides, we recommend looking around the SDK Wrappers (Github folder) to understand the SDK, and then using the How-to Guides we’ve created to accomplish what you’d like to do.

Indy Walkthrough

What Indy and Libindy are and Why They Matter

Indy provides a software ecosystem for private, secure, and powerful identity, and libindy enables clients for it. Indy puts people — not the organizations that traditionally centralize identity — in charge of decisions about their own privacy and disclosure. This enables all kinds of rich innovation: connection contracts, revocation, novel payment workflows, asset and document management features, creative forms of escrow, curated reputation, integrations with other cool technologies, and so on.

Indy uses open-source, distributed ledger technology. These ledgers are a form of database that is provided cooperatively by a pool of participants, instead of by a giant database with a central admin. Data lives redundantly in many places, and it accrues in transactions orchestrated by many machines. Strong, industry-standard cryptography protects it. Best practices in key management and cybersecurity pervade its design. The result is a reliable, public source of truth under no single entity’s control, robust to system failure, resilient to hacking, and highly immune to subversion by hostile entities.

If the concepts of cryptography and blockchain details feel mysterious, fear not: this guide will help introduce you to key concepts within Indy. You’re starting in the right place.

What We’ll Cover

Our goal is to introduce you to many of the concepts of Indy and give you some idea of what happens behind the scenes to make it all work.

We’re going to frame the exploration with a story. Alice, a graduate of the fictional Faber College, wants to apply for a job at the fictional company Acme Corp. As soon as she has the job, she wants to apply for a loan in Thrift Bank so she can buy a car. She would like to use her college transcript as proof of her education on the job application and once hired, Alice would like to use the fact of employment as evidence of her creditworthiness for the loan.

The sorts of identity and trust interactions required to pull this off are messy in the world today; they are slow, they violate privacy, and they are susceptible to fraud. We’ll show you how Indy is a quantum leap forward.

Ready?

About Alice

As a graduate of Faber College, Alice receives an alumni newsletter where she learns that her alma mater is offering digital transcripts. She logs in to the college alumni website and requests her transcript by clicking Get Transcript. (Other ways to initiate this request might include scanning a QR code, downloading a transcript package from a published URL, etc.)

Alice doesn’t realize it yet, but to use this digital transcript she will need a new type of identity — not the traditional identity that Faber College has built for her in its on-campus database, but a new and portable one that belongs to her, independent of all past and future relationships, that nobody can revoke or co-opt or correlate without her permission. This is a self-sovereign identity and it is the core feature of Indy.

In normal contexts, managing a self-sovereign identity will require a tool such as a desktop or mobile application. It might be a standalone app or it might leverage a third party service provider that the ledger calls an agency. The Sovrin Foundation publishes reference versions of such tools. Faber College will have studied these requirements and will recommend an Indy app to Alice if she doesn’t already have one. This app will install as part of the workflow from the Get Transcript button.

When Alice clicks Get Transcript, she will download a file that holds an Indy connection request. This connection request file, having an .indy extension and associated with her Indy app, will allow her to establish a secure channel of communication with another party in the ledger ecosystem — Faber College.

So when Alice clicks Get Transcript, she will normally end up installing an app (if needed), launching it, and then being asked by the app whether she wants to accept a request to connect with Faber.

For this guide, however, we’ll be using an Indy SDK API (as provided by libindy) instead of an app, so we can see what happens behind the scenes. We will pretend to be a particularly curious and technically adventurous Alice…

Infrastructure Preparation

Step 1: Getting Trust Anchor Credentials for Faber, Acme, Thrift and Government

Faber College and other actors have done some preparation to offer this service to Alice. To understand these steps let’s start with some definitions.

The ledger is intended to store Identity Records that describe a Ledger Entity. Identity Records are public data and may include Public Keys, Service Endpoints, Credential Schemas, and Credential Definitions. Every Identity Record is associated with exactly one DID (Decentralized Identifier) that is globally unique and resolvable (via a ledger) without requiring any centralized resolution authority. To maintain privacy each Identity Owner can own multiple DIDs.

In this tutorial we will use two types of DIDs. The first one is a Verinym. A Verinym is associated with the Legal Identity of the Identity Owner. For example, all parties should be able to verify that some DID is used by a Government to publish schemas for some document type. The second type is a Pseudonym - a Blinded Identifier used to maintain privacy in the context of an ongoing digital relationship (Connection). If the Pseudonym is used to maintain only one digital relationship we will call it a Pairwise-Unique Identifier. We will use Pairwise-Unique Identifiers to maintain secure connections between actors in this tutorial.

The creation of a DID known to the Ledger is an Identity Record itself (NYM transaction). The NYM transaction can be used for creation of new DIDs that is known to that ledger, the setting and rotation of a verification key, and the setting and changing of roles. The most important fields of this transaction are dest (target DID), role (role of a user NYM record being created for) and the verkey (target verification key). See Requests to get more information about supported ledger transactions.

Publishing with a DID verification key allows a person, organization or thing, to verify that someone owns this DID as that person, organization or thing is the only one who knows the corresponding signing key and any DID-related operations requiring signing with this key.

Our ledger is public permissioned and anyone who wants to publish DIDs needs to get the role of Trust Anchor on the ledger. A Trust Anchor is a person or organization that the ledger already knows about, that is able to help bootstrap others. (It is not the same as what cybersecurity experts call a “trusted third party”; think of it more like a facilitator). See Roles to get more information about roles.

The first step towards being able to place transactions on the ledger involves getting the role of Trust Anchor on the ledger. Faber College, Acme Corp and Thrift Bank will need to get the role of Trust Anchor on the ledger so they can create Verinyms and Pairwise-Unique Identifiers to provide the service to Alice.

Becoming a Trust Anchor requires contacting a person or organization who already has the Trust Anchor role on the ledger. For the sake of the demo, in our empty test ledger we have only NYMs with the Steward role, but all Stewards are automatically Trust Anchors.

Step 2: Connecting to the Indy Nodes Pool

We are ready to start writing the code that will cover Alice’s use case from start to finish. It is important to note that for demo purposes it will be a single test that will contain the code intended to be executed on different agents. We will always point to what Agent is intended to execute each code part. Also we will use different wallets to store the DID and keys of different Agents. Let’s begin.

The first code block will contain the code of the Steward’s agent.

To write and read the ledger’s transactions after gaining the proper role, you’ll need to make a connection to the Indy nodes pool. To make a connection to the different pools that exist, like the Sovrin pool or the local pool we started by ourselves as part of this tutorial, you’ll need to set up a pool configuration.

The list of nodes in the pool is stored in the ledger as NODE transactions. Libindy allows you to restore the actual list of NODE transactions by a few known transactions that we call genesis transactions. Each Pool Configuration is defined as a pair of pool configuration name and pool configuration JSON. The most important field in pool configuration json is the path to the file with the list of genesis transactions. Make sure this path is correct.

The pool.create_pool_ledger_config call allows you to create a named pool configuration. After the pool configuration is created we can connect to the nodes pool that this configuration describes by calling pool.open_pool_ledger. This call returns the pool handle that can be used to reference this opened connection in future libindy calls.

The code block below contains each of these items. Note how the comments denote that this is the code for the “Steward Agent.”

  await pool.set_protocol_version(2)
  
  pool_ = {'name': 'pool1'}
  pool_['genesis_txn_path'] = get_pool_genesis_txn_path(pool_['name'])
  pool_['config'] = json.dumps({"genesis_txn": str(pool_['genesis_txn_path'])})
  await pool.create_pool_ledger_config(pool_['name'], pool_['config'])
  pool_['handle'] = await pool.open_pool_ledger(pool_['name'], None)
Step 3: Getting the ownership for Steward’s Verinym

Next, the Steward’s agent should get the ownership for the DID that has corresponding NYM transactions with the Steward role on the ledger.

The test ledger we use was pre-configured to store some known Steward NYMs. Also we know seed values for the random number generator that were used to generate keys for these NYMs. These seed values allow us to restore signing keys for these DIDs on the Steward’s agent side and as a result get the DID ownership.

Libindy has a concept of the Wallet. The wallet is secure storage for crypto materials like DIDs, keys, etc… To store the Steward’s DID and corresponding signkey, the agent should create a named wallet first by calling wallet.create_wallet. After this the named wallet can be opened by calling wallet.open_wallet. This call returns the wallet handle that can be used to reference this opened wallet in future libindy calls.

After the wallet is opened we can create a DID record in this wallet by calling did.create_and_store_my_did that returns the generated DID and verkey part of the generated key. The signkey part for this DID will be stored in the wallet too, but it is impossible to read it directly.

  # Steward Agent
  steward = {
      'name': "Sovrin Steward",
      'wallet_config': json.dumps({'id': 'sovrin_steward_wallet'}),
      'wallet_credentials': json.dumps({'key': 'steward_wallet_key'}),
      'pool': pool_['handle'],
      'seed': '000000000000000000000000Steward1'
  }
  
  await wallet.create_wallet(steward['wallet_config'], steward['wallet_credentials'])
  steward['wallet'] = await wallet.open_wallet(steward['wallet_config'], steward['wallet_credentials'])
  
  steward['did_info'] = json.dumps({'seed': steward['seed']})
  steward['did'], steward['key'] = await did.create_and_store_my_did(steward['wallet'], steward['did_info'])

Please note: We provided only information about the seed to did.create_and_store_my_did, but not any information about the Steward’s DID. By default DID’s are generated as the first 16 bytes of the verkey. For such DID’s, when dealing with operations that require both a DID and the verkey we can use the verkey in an abbreviated form. In this form the verkey starts with a tilde ‘~’ followed by 22 or 23 characters. The tilde indicates that the DID itself represents the first 16 bytes of the verkey and the string following the tilde represents the second 16 bytes of the verkey, both using base58Check encoding.

Step 4: Onboarding Faber, Acme, Thrift and Government by Steward

Faber, Acme, Thrift and Government should now establish a Connection with the Steward.

Each connection is actually a pair of Pairwise-Unique Identifiers (DIDs). The one DID is owned by one party to the connection and the second by another.

Both parties know both DIDs and understand what connection this pair describes.

The relationship between them is not shareable with others; it is unique to those two parties in that each pairwise relationship uses different DIDs.

We call the process of establish a connection Onboarding.

In this tutorial we will describe the simple version of onboarding process. In our case, one party will always be the Trust Anchor. Real enterprise scenarios can use a more complex version.

Connecting the Establishment

Let’s look the process of connection establishment between Steward and Faber College.

  1. Faber and Steward contact in some way to initiate onboarding process. It can be filling the form on web site or a phone call.

  2. Steward creates a new DID record in the wallet by calling did.create_and_store_my_did that he will use for secure interactions only with Faber.

    # Steward Agent
    (steward['did_for_faber'], steward['key_for_faber']) = await did.create_and_store_my_did(steward['wallet'], "{}")
    
  3. Steward sends the corresponding NYM transaction to the Ledger by consistently calling ledger.build_nym_request to build the NYM request and ledger.sign_and_submit_request to send the created request.

    # Steward Agent
    nym_request = await ledger.build_nym_request(steward['did'], steward['did_for_faber'], steward['key_for_faber'], None, role)
    await ledger.sign_and_submit_request(steward['pool'], steward['wallet'], steward['did'], nym_request)
    
  4. Steward creates the connection request which contains the created DID and Nonce. This nonce is just a big random number generated to track the unique connection request. A nonce is a random arbitrary number that can only be used one time. When a connection request is accepted, the invitee digitally signs the nonce so that the inviter can match the response with a prior request.

    # Steward Agent
    connection_request = {
        'did': steward['did_for_faber'],
        'nonce': 123456789
    }
    
  5. Steward sends the connection request to Faber.

  6. Faber accepts the connection request from Steward.

  7. Faber creates a wallet if it does not exist yet.

    # Faber Agent
    await wallet.create_wallet(faber['wallet_config'], faber['wallet_credentials'])
    faber['wallet'] = await wallet.open_wallet(faber['wallet_config'], faber['wallet_credentials'])
    
  8. Faber creates a new DID record in its wallet by calling did.create_and_store_my_did that it will use only for secure interactions with the Steward.

    # Faber Agent
    (faber['did_for_steward'], faber['key_for_steward']) = await did.create_and_store_my_did(faber['wallet'], "{}")
    
  9. Faber creates the connection response which contains the created DID, Verkey and Nonce from the received connection request.

    # Faber Agent
    connection_response = json.dumps({
        'did': faber['did_for_steward'],
        'verkey': faber['key_for_steward'],
        'nonce': connection_request['nonce']
    })
    
  10. Faber asks the ledger for the Verification key of the Steward’s DID by calling did.key_for_did.

    # Faber Agent
    faber['steward_key_for_faber'] = await did.key_for_did(faber['pool'], faber['wallet'], connection_request['did'])
    
  11. Faber anonymously encrypts the connection response by calling crypto.anon_crypt with the Steward verkey. The Anonymous-encryption schema is designed for the sending of messages to a Recipient which has been given its public key. Only the Recipient can decrypt these messages, using its private key. While the Recipient can verify the integrity of the message, it cannot verify the identity of the Sender.

    # Faber Agent
    anoncrypted_connection_response = await crypto.anon_crypt(faber['steward_key_for_faber'], connection_response.encode('utf-8'))
    
  12. Faber sends the anonymously encrypted connection response to the Steward.

  13. Steward anonymously decrypts the connection response by calling crypto.anon_decrypt.

    # Steward Agent
    decrypted_connection_response = \
        (await crypto.anon_decrypt(steward['wallet'], steward['key_for_faber'], anoncrypted_connection_response)).decode("utf-8")
    
  14. Steward authenticates Faber by the comparison of Nonce.

    # Steward Agent
    assert connection_request['nonce'] == decrypted_connection_response['nonce']
    
  15. Steward sends the NYM transaction for Faber’s DID to the Ledger. Please note that despite the fact that the Steward is the sender of this transaction the owner of the DID will be Faber as it uses the verkey as provided by Faber.

    # Steward Agent
    nym_request = await ledger.build_nym_request(steward['did'], decrypted_connection_response['did'], decrypted_connection_response['verkey'], None, role)
    await ledger.sign_and_submit_request(steward['pool'], steward['wallet'], steward['did'], nym_request)
    

At this point Faber is connected to the Steward and can interact in a secure peer-to-peer way. Faber can trust the response is from Steward because:

  • it connects to the current endpoint
  • no replay - attack is possible, due to her random challenge
  • it knows the verification key used to verify Steward digital signature is the correct one because it just confirmed it on the ledger

Note: All parties must not use the same DID’s to establish other relationships. By having independent pairwise relationships, you’re reducing the ability for others to correlate your activities across multiple interactions.

Getting Verinym

It is important to understand that earlier created Faber DID is not, in and of itself, the same thing as self-sovereign identity. This DID must be used only for secure interaction with the Steward. After the connection is established Faber must create a new DID record that he will use as Verinym in the Ledger.

  1. Faber creates a new DID in its wallet by calling did.create_and_store_my_did.

    # Faber Agent
    (faber['did'], faber['key']) = await did.create_and_store_my_did(faber['wallet'], "{}")
    
  2. Faber prepares the message that will contain the created DID and verkey.

    # Faber Agent
    faber['did_info'] = json.dumps({
        'did': faber['did'],
        'verkey': faber['key']
    })
    
  3. Faber authenticates and encrypts the message by calling crypto.auth_crypt function, which is an implementation of the authenticated-encryption schema. Authenticated encryption is designed for sending of a confidential message specifically for the Recipient. The Sender can compute a shared secret key using the Recipient’s public key (verkey) and his secret (signing) key. The Recipient can compute exactly the same shared secret key using the Sender’s public key (verkey) and his secret (signing) key. That shared secret key can be used to verify that the encrypted message was not tampered with, before eventually decrypting it.

    # Faber Agent
    authcrypted_faber_did_info_json = \
        await crypto.auth_crypt(faber['wallet'], faber['key_for_steward'], faber['steward_key_for_faber, faber['did_info'].encode('utf-8'))
    
  4. Faber sends the encrypted message to the Steward.

  5. Steward decrypts the received message by calling crypto.auth_decrypt.

    # Steward Agent    
    sender['faber_key_for_steward'], authdecrypted_faber_did_info_json = \
        await crypto.auth_decrypt(steward['wallet'], steward['key_for_faber'], authcrypted_faber_did_info_json)
    faber_did_info = json.loads(authdecrypted_faber_did_info_json)
    
  6. Steward asks the ledger for the Verification key of Faber’s DID by calling did.key_for_did.

    # Steward Agent    
    steward['faber_key_for_steward'] = await did.key_for_did(steward['pool'], steward['wallet'], ['faber_did_for_steward'])
    
  7. Steward authenticates Faber by comparison of the Message Sender Verkey and the Faber Verkey received from the Ledger.

    # Steward Agent    
    assert sender_verkey == steward['faber_key_for_steward']
    
  8. Steward sends the corresponded NYM transaction to the Ledger with TRUST ANCHOR role. Please note that despite the fact that the Steward is the sender of this transaction the owner of DID will be Faber as it uses Verkey provided by Faber.

    # Steward Agent
    nym_request = await ledger.build_nym_request(steward['did'], decrypted_faber_did_info_json['did'],
                                                 decrypted_faber_did_info_json['verkey'], None, 'TRUST_ANCHOR')
    await ledger.sign_and_submit_request(steward['pool'], steward['wallet'], steward['did'], nym_request)
    

At this point Faber has a DID related to his identity in the Ledger.

Acme, Thrift Bank, and Government must pass the same Onboarding process connection establishment with Steward.

Step 5: Credential Schemas Setup

Credential Schema is the base semantic structure that describes the list of attributes which one particular Credential can contain.

Note: It’s not possible to update an existing Schema. So, if the Schema needs to be evolved, a new Schema with a new version or name needs to be created.

A Credential Schema can be created and saved in the Ledger by any Trust Anchor.

Here is where the Government creates and publishes the Transcript Credential Schema to the Ledger:

  1. The Trust Anchor creates the Credential Schema by calling the anoncreds.issuer_create_schema that returns the generated Credential Schema.

    # Government Agent
    transcript = {
        'name': 'Transcript',
        'version': '1.2',
        'attributes': ['first_name', 'last_name', 'degree', 'status', 'year', 'average', 'ssn']
    }
    (government['transcript_schema_id'], government['transcript_schema']) = \
        await anoncreds.issuer_create_schema(government['did'], transcript['name'], transcript['version'],
                                             json.dumps(transcript['attributes']))
    transcript_schema_id = government['transcript_schema_id']
    
  2. The Trust Anchor sends the corresponding Schema transaction to the Ledger by consistently calling the ledger.build_schema_request to build the Schema request and ledger.sign_and_submit_request to send the created request.

    # Government Agent
    schema_request = await ledger.build_schema_request(government['did'], government['transcript_schema'])
    await ledger.sign_and_submit_request(government['pool'], government['wallet'], government['did'], schema_request)
    

In the same way Government creates and publishes the Job-Certificate Credential Schema to the Ledger:

  # Government Agent
    job_certificate = {
        'name': 'Job-Certificate',
        'version': '0.2',
        'attributes': ['first_name', 'last_name', 'salary', 'employee_status', 'experience']
    }
    (government['job_certificate_schema_id'], government['job_certificate_schema']) = \
        await anoncreds.issuer_create_schema(government['did'], job_certificate['name'], job_certificate['version'],
                                             json.dumps(job_certificate['attributes']))
    job_certificate_schema_id = government['job_certificate_schema_id']
    
    schema_request = await ledger.build_schema_request(government['did'], government['job_certificate_schema'])
    await ledger.sign_and_submit_request(government['pool'], government['wallet'], government['did'], schema_request)

At this point we have the Transcript and the Job-Certificate Credential Schemas published by Government to the Ledger.

Step 6: Credential Definition Setup

Credential Definition is similar in that the keys that the Issuer uses for the signing of Credentials also satisfies a specific Credential Schema.

Note It’s not possible to update data in an existing Credential Definition. So, if a CredDef needs to be evolved (for example, a key needs to be rotated), then a new Credential Definition needs to be created by a new Issuer DID.

A Credential Definition can be created and saved in the Ledger by any Trust Anchor. Here Faber creates and publishes a Credential Definition for the known Transcript Credential Schema to the Ledger.

  1. The Trust Anchor gets the specific Credential Schema from the Ledger by consistently calling the ledger.build_get_schema_request to build the GetSchema request, ledger.sign_and_submit_request to send the created request and the ledger.parse_get_schema_response to get the Schema in the format required by Anoncreds API from the GetSchema response.

    # Faber Agent
    get_schema_request = await ledger.build_get_schema_request(faber['did'], transcript_schema_id)
    get_schema_response = await ledger.submit_request(faber['pool'], get_schema_request) 
    faber['transcript_schema_id'], faber['transcript_schema'] = await ledger.parse_get_schema_response(get_schema_response)
    
  2. The Trust Anchor creates the Credential Definition related to the received Credential Schema by calling anoncreds.issuer_create_and_store_credential_def that returns the generated public Credential Definition. The private Credential Definition part for this Credential Schema will be stored in the wallet too, but it is impossible to read it directly.

    # Faber Agent
    transcript_cred_def = {
        'tag': 'TAG1',
        'type': 'CL',
        'config': {"support_revocation": False}
    }
    (faber['transcript_cred_def_id'], faber['transcript_cred_def']) = \
        await anoncreds.issuer_create_and_store_credential_def(faber['wallet'], faber['did'],
                                                               faber['transcript_schema'], transcript_cred_def['tag'],
                                                               transcript_cred_def['type'],
                                                               json.dumps(transcript_cred_def['config']))
    
  3. The Trust Anchor sends the corresponding CredDef transaction to the Ledger by consistently calling the ledger.build_cred_def_request to build the CredDef request and the ledger.sign_and_submit_request to send the created request.

    # Faber Agent     
    cred_def_request = await ledger.build_cred_def_request(faber['did'], faber['transcript_cred_def'])
    await ledger.sign_and_submit_request(faber['pool'], faber['wallet'], faber['did'], cred_def_request)
    

The same way Acme creates and publishes a Credential Definition for the known Job-Certificate Credential Schema to the Ledger.

  # Acme Agent
  get_schema_request = await ledger.build_get_schema_request(acme['did'], job_certificate_schema_id)
  get_schema_response = await ledger.submit_request(acme['pool'], get_schema_request) 
  acme['job_certificate_schema_id'], acme['job_certificate_schema'] = await ledger.parse_get_schema_response(get_schema_response)
    
  job_certificate_cred_def = {
      'tag': 'TAG1',
      'type': 'CL',
      'config': {"support_revocation": False}
  }
  (acme['job_certificate_cred_def_id'], acme['job_certificate_cred_def']) = \
      await anoncreds.issuer_create_and_store_credential_def(acme['wallet'], acme['did'],
                                                             acme['job_certificate_schema'], job_certificate_cred_def['tag'],
                                                             job_certificate_cred_def['type'],
                                                             json.dumps(job_certificate_cred_def['config']))
  
  cred_def_request = await ledger.build_cred_def_request(acme['did'], acme['job_certificate_cred_def'])
  await ledger.sign_and_submit_request(acme['pool'], acme['wallet'], acme['did'], cred_def_request)

Acme anticipates revoking **Job-Certificate* credentials. It decides to create a revocation registry. One of Hyperledger Indy’s revocation registry types uses cryptographic accumulators for publishing revoked credentials. For details about the inner working of those accumulators see here). The use of those accumulators requires the publication of “validity tails” outside of the Ledger. For the purpose of this demo, the validity tails are written in a file using a ‘blob storage’.

    # Acme Agent
    acme['tails_writer_config'] = json.dumps({'base_dir': "/tmp/indy_acme_tails", 'uri_pattern': ''})
    tails_writer = await blob_storage.open_writer('default', acme['tails_writer_config'])

Once the validity tails are configured, Acme can create a new revocation registry for the given credential definition.

    # Acme Agent
    (acme['revoc_reg_id'], acme['revoc_reg_def'], acme['revoc_reg_entry']) = \
        await anoncreds.issuer_create_and_store_revoc_reg(acme['wallet'], acme['did'], 'CL_ACCUM', 'TAG1',
                                                          acme['job_certificate_cred_def_id'],
                                                          json.dumps({'max_cred_num': 5,
                                                                      'issuance_type': 'ISSUANCE_ON_DEMAND'}),
                                                          tails_writer)

    acme['revoc_reg_def_request'] = await ledger.build_revoc_reg_def_request(acme['did'], acme['revoc_reg_def'])
    await ledger.sign_and_submit_request(acme['pool'], acme['wallet'], acme['did'], acme['revoc_reg_def_request'])

    acme['revoc_reg_entry_request'] = \
        await ledger.build_revoc_reg_entry_request(acme['did'], acme['revoc_reg_id'], 'CL_ACCUM',
                                                   acme['revoc_reg_entry'])
    await ledger.sign_and_submit_request(acme['pool'], acme['wallet'], acme['did'], acme['revoc_reg_entry_request'])

At this point we have a Credential Definition (supporting revocation) for the Job-Certificate Credential Schema published by Acme and a Credential Definition for the Transcript Credential Schema published by Faber.

Alice Gets a Transcript

A credential is a piece of information about an identity — a name, an age, a credit score… It is information claimed to be true. In this case, the credential is named, “Transcript”.

Credentials are offered by an issuer.

An issuer may be any identity owner known to the Ledger and any issuer may issue a credential about any identity owner it can identify.

The usefulness and reliability of a credential are tied to the reputation of the issuer with respect to the credential at hand. For Alice to self-issue a credential that she likes chocolate ice cream may be perfectly reasonable, but for her to self-issue a credential that she graduated from Faber College should not impress anyone.

As we mentioned in About Alice, Alice graduated from Faber College. After Faber College had established a connection with Alice, it created for her a Credential Offer about the issuance of the Transcript Credential.

  # Faber Agent
  faber['transcript_cred_offer'] = await anoncreds.issuer_create_credential_offer(faber['wallet'], faber['transcript_cred_def_id'])

Note: All messages sent between actors are encrypted using Authenticated-encryption scheme.

The value of this Transcript Credential is that it is provably issued by Faber College.

Alice wants to see the attributes that the Transcript Credential contains. These attributes are known because a Credential Schema for Transcript has been written to the Ledger.

  # Alice Agent
  get_schema_request = await ledger.build_get_schema_request(alice['did_for_faber'], alice['transcript_cred_offer']['schema_id'])
  get_schema_response = await ledger.submit_request(alice['pool'], get_schema_request)
  transcript_schema = await ledger.parse_get_schema_response(get_schema_response)

  print(transcript_schema['data'])
  # Transcript Schema:
  {
      'name': 'Transcript',
      'version': '1.2',
      'attr_names': ['first_name', 'last_name', 'degree', 'status', 'year', 'average', 'ssn']
  }

However, the Transcript Credential has not been delivered to Alice yet in a usable form. Alice wants to use that Credential. To get it, Alice needs to request it, but first she must create a Master Secret.

Note: A Master Secret is an item of Private Data used by a Prover to guarantee that a credential uniquely applies to them. The Master Secret is an input that combines data from multiple Credentials to prove that the Credentials have a common subject (the Prover). A Master Secret should be known only to the Prover.

Alice creates Master Secret in her wallet.

  # Alice Agent
  alice['master_secret_id'] = await anoncreds.prover_create_master_secret(alice['wallet'], None)

Alice also needs to get the Credential Definition corresponding to the cred_def_id in the Transcript Credential Offer.

  # Alice Agent
  get_cred_def_request = await ledger.build_get_cred_def_request(alice['did_for_faber'], alice['transcript_cred_offer']['cred_def_id'])
  get_cred_def_response = await ledger.submit_request(alice['pool'], get_cred_def_request)
  alice['transcript_cred_def'] = await ledger.parse_get_cred_def_response(get_cred_def_response)

Now Alice has everything to create a Credential Request of the issuance of the Faber Transcript Credential.

  # Alice Agent
    (alice['transcript_cred_request'], alice['transcript_cred_request_metadata']) = \
        await anoncreds.prover_create_credential_req(alice['wallet'], alice['did_for_faber'], alice['transcript_cred_offer'],
                                                     alice['transcript_cred_def'], alice['master_secret_id'])

Faber prepares both raw and encoded values for each attribute in the Transcript Credential Schema. Faber creates the Transcript Credential for Alice.

  # Faber Agent
  # note that encoding is not standardized by Indy except that 32-bit integers are encoded as themselves. IS-786
  transcript_cred_values = json.dumps({
      "first_name": {"raw": "Alice", "encoded": "1139481716457488690172217916278103335"},
      "last_name": {"raw": "Garcia", "encoded": "5321642780241790123587902456789123452"},
      "degree": {"raw": "Bachelor of Science, Marketing", "encoded": "12434523576212321"},
      "status": {"raw": "graduated", "encoded": "2213454313412354"},
      "ssn": {"raw": "123-45-6789", "encoded": "3124141231422543541"},
      "year": {"raw": "2015", "encoded": "2015"},
      "average": {"raw": "5", "encoded": "5"}
  })

  faber['transcript_cred_def'], _, _ = \
      await anoncreds.issuer_create_credential(faber['wallet'], faber['transcript_cred_offer'], faber['transcript_cred_request'],
                                               transcript_cred_values, None, None)

Now the Transcript Credential has been issued. Alice stores it in her wallet.

  # Alice Agent
  await anoncreds.prover_store_credential(alice['wallet'], None, faber['transcript_cred_request'], faber['transcript_cred_request_metadata'],
                                          alice['transcript_cred'], alice['transcript_cred_def'], None)

Alice has it in her possession, in much the same way that she would hold a physical transcript that had been mailed to her.

Apply for a Job

At some time in the future, Alice would like to work for the fictional company, Acme Corp. Normally she would browse to their website, where she would click on a hyperlink to apply for a job. Her browser would download a connection request in which her Indy app would open; this would trigger a prompt to Alice, asking her to accept the connection with Acme Corp. Because we’re using an Indy-SDK, the process is different, but the steps are the same. The process of the connection establishment is the same as when Faber was accepting the Steward connection request.

After Alice had established connection with Acme, she got the Job-Application Proof Request. A proof request is a request made by the party who needs verifiable proof of having certain attributes and the solving of predicates that can be provided by other verified credentials.

In this case, Acme Corp is requesting that Alice provide a Job Application. The Job Application requires a name, degree, status, SSN and also the satisfaction of the condition about the average mark or grades.

In this case, Job-Application Proof Request looks like:

  # Acme Agent
  acme['job_application_proof_request'] = json.dumps({
      'nonce': '1432422343242122312411212',
      'name': 'Job-Application',
      'version': '0.1',
      'requested_attributes': {
          'attr1_referent': {
              'name': 'first_name'
          },
          'attr2_referent': {
              'name': 'last_name'
          },
          'attr3_referent': {
              'name': 'degree',
              'restrictions': [{'cred_def_id': faber['transcript_cred_def_id']}]
          },
          'attr4_referent': {
              'name': 'status',
              'restrictions': [{'cred_def_id': faber['transcript_cred_def_id']}]
          },
          'attr5_referent': {
              'name': 'ssn',
              'restrictions': [{'cred_def_id': faber['transcript_cred_def_id']}]
          },
          'attr6_referent': {
              'name': 'phone_number'
          }
      },
      'requested_predicates': {
          'predicate1_referent': {
              'name': 'average',
              'p_type': '>=',
              'p_value': 4,
              'restrictions': [{'cred_def_id': faber['transcript_cred_def_id']}]
          }
      }
  })

Notice that some attributes are verifiable and some are not.

The proof request says that SSN, degree, and graduation status in the Credential must be formally asserted by an issuer and schema_key. Notice also that the first_name, last_name and phone_number are not required to be verifiable. By not tagging these credentials with a verifiable status, Acme’s credential request is saying it will accept Alice’s own credential about her names and phone numbers.

To show Credentials that Alice can use for the creating of Proof for the Job-Application Proof Request Alice calls anoncreds.prover_get_credentials_for_proof_req.

  # Alice Agent
    creds_for_job_application_proof_request = json.loads(
        await anoncreds.prover_get_credentials_for_proof_req(alice['wallet'], alice['job_application_proof_request']))

Alice has only one credential that meets proof the requirements for this Job Application.

  # Alice Agent
  {
    'referent': 'Transcript Credential Referent',
    'attrs': {
        'first_name': 'Alice',
        'last_name': 'Garcia',
        'status': 'graduated',
        'degree': 'Bachelor of Science, Marketing',
        'ssn': '123-45-6789',
        'year': '2015',
        'average': '5'
    },
    'schema_id': job_certificate_schema_id,
    'cred_def_id': faber_transcript_cred_def_id,
    'rev_reg_id': None,
    'cred_rev_id': None
  }

Now Alice can divide these attributes into the three groups:

  1. attributes values of which will be revealed
  2. attributes values of which will be unrevealed
  3. attributes for which creating of verifiable proof is not required

For the Job-Application Proof Request Alice divided the attributes as follows:

  # Alice Agent
    alice['job_application_requested_creds'] = json.dumps({
        'self_attested_attributes': {
            'attr1_referent': 'Alice',
            'attr2_referent': 'Garcia',
            'attr6_referent': '123-45-6789'
        },
        'requested_attributes': {
            'attr3_referent': {'cred_id': cred_for_attr3['referent'], 'revealed': True},
            'attr4_referent': {'cred_id': cred_for_attr4['referent'], 'revealed': True},
            'attr5_referent': {'cred_id': cred_for_attr5['referent'], 'revealed': True},
        },
        'requested_predicates': {'predicate1_referent': {'cred_id': cred_for_predicate1['referent']}}
    })

In addition, Alice must get the Credential Schema and corresponding Credential Definition for each used Credential, the same way, as on the step used to in the creation of the Credential Request.

Now Alice has everything to create the Proof for Acme Job-Application Proof Request.

  # Alice Agent
  alice['apply_job_proof'] = \
        await anoncreds.prover_create_proof(alice['wallet'], alice['job_application_proof_request'], alice['job_application_requested_creds'],
                                            alice['master_secret_id'], alice['schemas'], alice['cred_defs'], alice['revoc_states'])

When Acme inspects the received Proof he will see following structure:

  # Acme Agent
  {
      'requested_proof': {
          'revealed_attrs': {
              'attr4_referent': {'sub_proof_index': 0, 'raw':'graduated', 'encoded':'2213454313412354'},
              'attr5_referent': ['sub_proof_index': 0, 'raw':'123-45-6789', 'encoded':'3124141231422543541'},
              'attr3_referent': ['sub_proof_index': 0, 'raw':'Bachelor of Science, Marketing', 'encoded':'12434523576212321'}
          },
          'self_attested_attrs': {
              'attr1_referent': 'Alice',
              'attr2_referent': 'Garcia',
              'attr6_referent': '123-45-6789'
          },
          'unrevealed_attrs': {},
          'predicates': {
              'predicate1_referent': {'sub_proof_index': 0}
          }
      },
      'proof' : [] # Validity Proof that Acme can check
      'identifiers' : [ # Identifiers of credentials were used for Proof building
          {
            'schema_id': job_certificate_schema_id,
            'cred_def_id': faber_transcript_cred_def_id,
            'rev_reg_id': None,
            'timestamp': None
          }
      }
  }

Acme got all the requested attributes. Now Acme wants to check the Validity Proof. To do it Acme first must get every Credential Schema and corresponding Credential Definition for each identifier presented in the Proof, the same way that Alice did it. Now Acme has everything to check Job-Application Proof from Alice.

 # Acme Agent
 assert await anoncreds.verifier_verify_proof(acme['job_application_proof_request'], acme['apply_job_proof'],
                                              acme['schemas'], acme['cred_defs'], acme['revoc_ref_defs'], acme['revoc_regs'])

Here, we’ll assume the application is accepted and Alice ends up getting the job. Acme creates new Credential Offer for Alice.

  # Acme Agent
  acme['job_certificate_cred_offer'] = await anoncreds.issuer_create_credential_offer(acme['wallet'], acme['job_certificate_cred_def_id'])

When Alice inspects her connection with Acme, she sees that a new Credential Offer is available.

Apply for a Loan

Now that Alice has a job, she’d like to apply for a loan. That will require a proof of employment. She can get this from the Job-Certificate credential offered by Acme. Alice goes through a familiar sequence of interactions.

  1. First she creates a Credential Request.
 # Alice Agent
   (alice['job_certificate_cred_request'], alice['job_certificate_cred_request_metadata']) = \
       await anoncreds.prover_create_credential_req(alice['wallet'], alice['did_for_acme'], alice['job_certificate_cred_offer'],
                                                    alice['acme_job_certificate_cred_def'], alice['master_secret_id'])
  1. Acme issues a Job-Certificate Credential for Alice.
 # Acme Agent
 alice_job_certificate_cred_values_json = json.dumps({
     "first_name": {"raw": "Alice", "encoded": "245712572474217942457235975012103335"},
     "last_name": {"raw": "Garcia", "encoded": "312643218496194691632153761283356127"},
     "employee_status": {"raw": "Permanent", "encoded": "2143135425425143112321314321"},
     "salary": {"raw": "2400", "encoded": "2400"},
     "experience": {"raw": "10", "encoded": "10"}
 })

One difference with the ussuance of the Transcript by Faber here is that a Job-Certificate can be revoked and the credential creation takes the ID of the revocation registry created earlier by Acme and a handle to the blob storage containing the validity tails:

    # Acme Agent
    acme['blob_storage_reader_cfg_handle'] = await blob_storage.open_reader('default', acme['tails_writer_config'])
    acme['job_certificate_cred'], acme['job_certificate_cred_rev_id'], acme['alice_cert_rev_reg_delta'] = \
        await anoncreds.issuer_create_credential(acme['wallet'], acme['job_certificate_cred_offer'],
                                                 acme['job_certificate_cred_request'],
                                                 acme['job_certificate_cred_values'],
                                                 acme['revoc_reg_id'],
                                                 acme['blob_storage_reader_cfg_handle'])

Furthermore Acme must publish a revocation registry entry on the Ledger so other parties can verify later the revocation state of the credential.

    # Acme agent
    acme['revoc_reg_entry_req'] = \
        await ledger.build_revoc_reg_entry_request(acme['did'], acme['revoc_reg_id'], 'CL_ACCUM',
                                                   acme['alice_cert_rev_reg_delta'])
    await ledger.sign_and_submit_request(acme['pool'], acme['wallet'], acme['did'], acme['revoc_reg_entry_req'])

When Alice receives her Job-Certificate credential from Acme, she should request the revocation registry definition from the Ledger before storing the credential.

    # Alice Agent
    alice['acme_revoc_reg_des_req'] = \
        await ledger.build_get_revoc_reg_def_request(alice['did_for_acme'],
                                                     alice_job_certificate_cred['rev_reg_id'])
    alice['acme_revoc_reg_des_resp'] = await ledger.submit_request(alice['pool'], alice['acme_revoc_reg_des_req'])
    (alice['acme_revoc_reg_def_id'], alice['acme_revoc_reg_def_json']) = \
        await ledger.parse_get_revoc_reg_def_response(alice['acme_revoc_reg_des_resp'])

Now the Job-Certificate Credential has been issued and Alice now has it in her possession. Alice stores Job-Certificate Credential in her wallet.

  # Alice Agent
  await anoncreds.prover_store_credential(alice['wallet'], None, alice['job_certificate_cred_request_metadata'],
                                          alice['job_certificate_cred'], alice['acme_job_certificate_cred_def'], alice['acme_revoc_reg_def_json'])

She can use it when she applies for her loan, in much the same way that she used her transcript when applying for a job.

There is a disadvantage in this approach to data sharing though, — it may disclose more data than what is strictly necessary. If all Alice needs to do is provide proof of employment, this can be done with an anonymous credential instead. Anonymous credentials may prove certain predicates without disclosing actual values (e.g., Alice is employed full-time, with a salary greater than X, along with her hire date, but her actually salary remains hidden). A compound proof can be created, drawing from credentials from both Faber College and Acme Corp, that discloses only what is necessary.

Alice now establishes connection with Thrift Bank.

Alice gets a Loan-Application-Basic Proof Request from Thrift Bank that looks like:

  # Thrift Agent
  thrift['apply_loan_proof_request'] = json.dumps({
      'nonce': '123432421212',
      'name': 'Loan-Application-Basic',
      'version': '0.1',
      'requested_attributes': {
          'attr1_referent': {
              'name': 'employee_status',
              'restrictions': [{'cred_def_id': acme_job_certificate_cred_def_id}]
          }
      },
      'requested_predicates': {
          'predicate1_referent': {
              'name': 'salary',
              'p_type': '>=',
              'p_value': 2000,
              'restrictions': [{'cred_def_id': acme_job_certificate_cred_def_id}]
          },
          'predicate2_referent': {
              'name': 'experience',
              'p_type': '>=',
              'p_value': 1,
              'restrictions': [{'cred_def_id': acme_job_certificate_cred_def_id}]
          }
      },
      'non_revoked': {'to': int(time.time())}
  })

The last line indicates that the Job-Certificate provided should not be revoked by the application time.

Alice has only one credential that meets the proof requirements for this Loan-Application-Basic Proof Request.

  # Alice Agent
  {
      'referent': 'Job-Certificate Credential Referent',
      'revoc_reg_seq_no': None,
      'schema_id': job_certificate_schema_id,
      'cred_def_id': acme_job_certificate_cred_def_id,
      'attrs': {
          'employee_status': 'Permanent',
          'last_name': 'Garcia',
          'experience': '10',
          'first_name': 'Alice',
           'salary': '2400'
      }
  }

For the Loan-Application-Basic Proof Request Alice divided attributes as follows. She can get the validity time stamp for each attribute from the revocation states queried from the Ledger:

  # Alice Agent
  revoc_states_for_loan_app = json.loads(alice['revoc_states_for_loan_app'])
        timestamp_for_attr1 = await get_timestamp_for_attribute(cred_for_attr1, revoc_states_for_loan_app)
        timestamp_for_predicate1 = await get_timestamp_for_attribute(cred_for_predicate1, revoc_states_for_loan_app)
        timestamp_for_predicate2 = await get_timestamp_for_attribute(cred_for_predicate2, revoc_states_for_loan_app)
        alice['apply_loan_requested_creds'] = json.dumps({
            'self_attested_attributes': {},
            'requested_attributes': {
                'attr1_referent': {'cred_id': cred_for_attr1['referent'], 'revealed': True, 'timestamp': timestamp_for_attr1}
            },
            'requested_predicates': {
                'predicate1_referent': {'cred_id': cred_for_predicate1['referent'], 'timestamp': timestamp_for_predicate1},
                'predicate2_referent': {'cred_id': cred_for_predicate2['referent'], 'timestamp': timestamp_for_predicate2}
            }
        })

Alice creates the Proof for the Loan-Application-Basic Proof Request.

  # Alice Agent
  alice['apply_loan_proof'] = \
            await anoncreds.prover_create_proof(alice['wallet'], alice['apply_loan_proof_request'],
                                                alice['apply_loan_requested_creds'], alice['master_secret_id'],
                                                alice['schemas_for_loan_app'], alice['cred_defs_for_loan_app'],
                                                alice['revoc_states_for_loan_app'])

Alice sends just the Loan-Application-Basic proof to the bank. This allows her to minimize the PII (personally identifiable information) that she has to share when all she’s trying to do right now is prove basic eligibility.

When Thrift inspects the received Proof he will see following structure:

  # Thrift Agent
  {
      'requested_proof': {
          'revealed_attrs': {
              'attr1_referent': {'sub_proof_index': 0, 'raw': 'Permanent', 'encoded':'2143135425425143112321314321'},
          },
          'self_attested_attrs': {},
          'unrevealed_attrs': {},
          'predicates': {
              'predicate1_referent': {'sub_proof_index': 0},
              'predicate2_referent': {'sub_proof_index': 0}
          }
      },
      'proof' : [] # Validity Proof that Thrift can check
      'identifiers' : [ # Identifiers of credentials were used for Proof building
          'schema_id': acme['job_certificate_schema_id'],
          'cred_def_id': acme['job_certificate_cred_def_id'],
          'rev_reg_id': acme['revoc_reg_id'],
          'timestamp': 1550503925 # A integer timestamp
      ]
  }

Thrift Bank successfully verified the Loan-Application-Basic Proof from Alice.

  # Thrift Agent
  assert await anoncreds.verifier_verify_proof(thrift['apply_loan_proof_request'],
                                                 thrift['alice_apply_loan_proof'],
                                                 thrift['schemas_for_loan_app'],
                                                 thrift['cred_defs_for_loan_app'],
                                                 thrift['revoc_defs_for_loan_app'],
                                                 thrift['revoc_regs_for_loan_app'])

Thrift Bank sends the second Proof Request where Alice needs to share her personal information with the bank.

  # Thrift Agent
  thrift['apply_loan_kyc_proof_request'] = json.dumps({
      'nonce': '123432421212',
      'name': 'Loan-Application-KYC',
      'version': '0.1',
      'requested_attributes': {
          'attr1_referent': {'name': 'first_name'},
          'attr2_referent': {'name': 'last_name'},
          'attr3_referent': {'name': 'ssn'}
      },
      'requested_predicates': {}
  })

Alice has two credentials that meets the proof requirements for this Loan-Application-KYC Proof Request.

  # Alice Agent
  {
    'referent': 'Transcript Credential Referent',
    'schema_id': transcript_schema_id,
    'cred_def_id': faber_transcript_cred_def_id,
    'attrs': {
        'first_name': 'Alice',
        'last_name': 'Garcia',
        'status': 'graduated',
        'degree': 'Bachelor of Science, Marketing',
        'ssn': '123-45-6789',
        'year': '2015',
        'average': '5'
    },
    'rev_reg_id': None,
    'cred_rev_id': None
  },
  {
      'referent': 'Job-Certificate Credential Referent',
      'schema_key': job_certificate_schema_id,
      'cred_def_id': acme_job_certificate_cred_def_id,
      'attrs': {
          'employee_status': 'Permanent',
          'last_name': 'Garcia',
          'experience': '10',
          'first_name': 'Alice',
          'salary': '2400'
      },
      'rev_reg_id': None,
      'revoc_reg_seq_no': None
  }

For the Loan-Application-KYC Proof Request Alice divided attributes as follows:

  # Alice Agent
  alice['apply_loan_kyc_requested_creds'] = json.dumps({
      'self_attested_attributes': {},
      'requested_attributes': {
          'attr1_referent': {'cred_id': cred_for_attr1['referent'], 'revealed': True},
          'attr2_referent': {'cred_id': cred_for_attr2['referent'], 'revealed': True},
          'attr3_referent': {'cred_id': cred_for_attr3['referent'], 'revealed': True}
      },
      'requested_predicates': {}
  })

Alice creates the Proof for Loan-Application-KYC Proof Request.

  # Alice Agent
  alice['apply_loan_kyc_proof'] = \
      await anoncreds.prover_create_proof(alice['wallet'], alice['apply_loan_kyc_proof_request'], alice['apply_loan_kyc_requested_creds'],
                                          alice['alice_master_secret_id'], alice['schemas'], alice['cred_defs'], alice['revoc_states'])

When Thrift inspects the received Proof he will see following structure:

  # Thrift Agent
  {
      'requested_proof': {
          'revealed_attributes': {
              'attr1_referent': {'sub_proof_index': 0, 'raw':'123-45-6789', 'encoded':'3124141231422543541'},
              'attr1_referent': {'sub_proof_index': 1, 'raw':'Alice', 'encoded':'245712572474217942457235975012103335'},
              'attr1_referent': {'sub_proof_index': 1, 'raw':'Garcia', 'encoded':'312643218496194691632153761283356127'},
          },
          'self_attested_attrs': {},
          'unrevealed_attrs': {},
          'predicates': {}
      },
      'proof' : [] # Validity Proof that Thrift can check
      'identifiers' : [ # Identifiers of credentials were used for Proof building
          {
            'schema_id': transcript_schema_id,
            'cred_def_id': faber['transcript_cred_def_id'],
            'rev_reg_id': None,
            'timestamp': None
          },
          {
            'schema_key': job_certificate_schema_id,
            'cred_def_id': acme['job_certificate_cred_def_id'],
            'rev_reg_id': None,
            'timestamp': None
          }
      ]
  }

Thrift Bank has successfully validated the Loan-Application-KYC Proof from Alice.

  # Thrift Agent
  assert await anoncreds.verifier_verify_proof(thrift['apply_loan_kyc_proof_request'], thrift['alice_apply_loan_kyc_proof'],
                                               thrift['schemas'], thrift['cred_defs'], thrift['revoc_defs'], thrift['revoc_regs'])

Both of Alice’s Proofs have been successfully verified and she got loan from Thrift Bank.

Alice Quits her Job

Later, Alice decides to quit her job so Acme revokes the Job-Certificate credential:

    # Acme Agent
    await anoncreds.issuer_revoke_credential(acme['wallet'],
                                             acme['blob_storage_reader_cfg_handle'],
                                             acme['revoc_reg_id'],
                                             acme['job_certificate_cred_rev_id'])

Acme then just needs to publish the revocation on the ledger calling ledger.build_revoc_reg_entry_request and ledger.sign_and_submit_request.

If Alice tries to apply for a loan (Loan-Application-Basic) again, the proof verification will then fail.

Explore the Code

Now that you’ve had a chance to see how the Libindy implementation works from the outside, perhaps you’d like to see how it works underneath, from code? If so, please run Simulating Getting Started in the Jupiter. You may need to be signed into GitHub to view this link. Also you can find the source code here

If demo gives an error when executing check Trouble Shooting Guide.

Indy Getting Started

Running getting-started with docker-compose

Prerequisites

Clone the indy-sdk: git clone https://github.com/hyperledger/indy-sdk.git Navigate to the getting started folder `cd indy-sdk/docs/getting-started

docker and docker-compose should be installed.

Run

Run docker in the getting-started folder: docker-compose up

The command above will create getting-started (the jupyter notebook) and indy_pool (collection of the validator nodes) images if they hasn’t been done yet, create containers and run them.The validators run by default on IP 10.0.0.2, this can be changed by changing pool_ip in the docker-compose file.To get Jupyter click on the link in output (it must have following format: http://0.0.0.0:8888/?token= )

Stop

docker-compose down The command above will stop and delete created network and containers.

Trouble Shooting

If demo gives an error when executing in Jupyter check Trouble Shooting Guide.

TROUBLE SHOOTING Getting - Started Guide (GSG)

If you setup the demo and encounter a 307 error recommend to take the following steps to cleanup and start over. The communication with the ledger is affected and it is not possible to run the demo. Here are some recommendations for other errors.

  • 306: you already have a configured ledger. Perform clean start.
  • 301: you are trying to create a ledger but it is already configured. A single failure will cause a problem when opening the ledger. Perform clean install.
  • 212: wallet is not found. When this occur stop and start container: Ctrl-C, docker-compose down, docker-compose up

Overview steps for clean start

  1. Remove existing instances
  2. Reset source files
  3. Perform a new build
  4. Start demo

Steps in detail

  1. Make sure containers are closed
docker-compose down   # to make sure containers are closed
docker image ls       # find image names that need to be removed in next step
docker image rm getting-started
docker image rm indy_pool
docker volume ls      # find volume name that needs to be removed in next step
docker volume rm gettingstarted_sandbox
  1. Reset source files
git checkout <branch_name>
git reset --hard     **WARNING** : make copies of any changes you want to keep prior to taking this step
git fetch --all
git pull
  1. Perform a new build
docker-compose build --no-cache    # adding no cache to make clean build
  1. Start demo
docker-compose up

Key Concepts

Core concepts to understand the Indy SDK

How Credential Revocation Works

This doc aims to explain credential revocation at a conceptual level. If this doc still feels too low-level, you might consider watching this introductory video from time offset 0:30 to 4:30.

Background: Cryptographic Accumulators

Before explaining the mechanism in detail, it’s necessary to understand cryptographic accumulators at a very high level. We will try to avoid daunting math in our explanation.

You can think of an accumulator as the product of multiplying many numbers together. In the equation a * b * c * d = e, the accumulator would be e; it accumulates a value as each new factor is multiplied in. We could plug in numbers; if a=2 and b=3 and c=5 and d=7, then our accumulator e has a value of 210. If e has this value, we say that 3 is “in” e because it is a factor. If we want to take 3 out of the accumulator, we divide 210 by 3 and get 70 (=2 * 5 * 7); 3 has now been “removed”.

Notice that you can also produce e by multiplying any single factor such as a by the product of all the other factors (b * c * d).

The product of the other factors contributing to the accumulator (all factors except the private one for this credential) is called a witness.

This is a useful characteristic; it means you can tell someone else the value of a and the product of all the other inputs to the accumulator, but not the other inputs themselves, and they can produce the output.

Background: Tails Files

In our simple example above, we only have 4 factors, and we are using small numbers. We are also using standard arithmetic, where you can reverse multiplication by dividing. In such a system, the contents of an accumulator can be reverse-engineered by simple prime factorization.

To be useful for revocation, Indy’s accumulators can’t be reversible; that is, it must be the case that the only way to derive the accumulator value is to know the factors. We accomplish this by using modular arithmetic (where division is undefined), and by using massive prime numbers for factors, resulting in a very long integer witness.

A tails file is associated with an accumulator and its factors. It is a binary file that contains an array of randomly generated factors for an accumulator. Instead of small numbers like 2 and 3 and 7, these factors are massive numbers, far too big to display conveniently on a screen. Typically the quantity of these numeric factors in a tails file is large–hundreds of thousands to tens of millions.

A tails file is not secret; it is published as plain text to the world and freely downloadable by anyone. The contents of this file never change.

Each potential or actual credential issued by a particular issuer is assigned an index to an accumulator factor in a tails file. However, only credentials that have not been revoked contribute to the value of the accumulator. We will see how this works, below.

tails file and accumulator

Setup

Before revocable credentials can be issued, a number of things must be true about the ecosystem:

  1. A schema for each credential type must be written to the ledger. For example, if companies wish to issue proof of employment, then a “Employee Credential” schema would need to be published. Similarly, before birth certificate credentials can be issued, a “Birth Certificate” schema would need to be defined and made available to the public. Any number of issuers can reference the same schema. Schemas can be versioned and evolved over time. Any individual or institution can write a schema to the ledger; it does not require special privileges.
  2. Each issuer must publish on the ledger one credential definition for each credential type they intend to create. The definition announces the issuer’s intention to create credentials that match a particular schema, and specifies the keys that the issuer will use to sign such credentials. (The verkey+ signing key pair used to authenticate the issuer’s DID should be kept separate from the keys used to sign credentials, so that each key pair can be rotated independently; it would be bad if a sysadmin rotated a DID keypair and accidentally invalidated all credentials issued by an institution…)
  3. Each issuer must also publish on the ledger a revocation registry. This metadata references a credential definition and specifies how revocation for that credential type will be handled. The revocation registry tells which cryptographic accumulator can be used to test revocation, and gives the URI and hash of the associated tails file.
  4. Each issuer must publish on the ledger an accumulator value that describes the revocation status for all associated credentials. This accumulator must be updated on a periodic or as-needed basis. For example, if a driver’s license division revokes 3 licenses during a given work day, then when they close their doors at 5 pm, they might issue a ledger transaction that updates the accumulator value for their driver’s license credentials, removing the 3 revoked credentials from the accumulator. What we mean by “removing” is as described above– the factors listed in the tails file for the indexes associated with the 3 revoked credentials are no longer multiplied into the accumulator.

before and after revocation

How Revocation Will Be Tested

Let us now skip ahead to think about what needs to happen much later. When a prover gives proof to a verifier, we normally think about the proof as focusing on core information demands: What is your birthdate? Please disclose your address. This is primary proof.

But there is another dimension of proof that’s also necessary: The prover must demonstrate that the credentials behind the primary proof have not been revoked. This is called proof of non-revocation.

In Indy, proof of non-revocation is accomplished by having provers show that they can derive the value of the accumulator for their credential using a factor for the accumulator that they know, plus the product of all other factors. The verifier can see that the prover produces the right answer (because the answer is on the ledger), but does not know certain details of how the prover derived it. The issuer can revoke by changing the answer to the math problem in a way that defeats the prover.

Preparing for Revocation at Issuance

When a credential is issued, the actual credential file is transmitted to the holder (who will later become a prover). In addition, the issuer communicates two other pieces of vital information:

  • The index corresponding to this credential, in the tails file. This lets the holder look up their private factor, which we could map to a in the simple equation from the accumulator background section at the top of the doc.
  • The witness.

Presenting Proof of Non-Revocation

When the prover needs to demonstrate that her credential is not revoked, she shows that she can provide math that derives the accumulator value on the ledger using her private factor times the witness. She does this without actually disclosing what her private value is; this is important to avoid correlation.

But there is a complication: what if the accumulator has changed value since the time the credential was issued? In this case, the private factor times the witness will not equal the accumulator…

This is handled by requiring accumulator updates to also publish a witness delta as part of the same transaction. This tells provers how to adjust their witness (referencing other indexes in the public tails file) to bring it back into harmony with the current value of the accumulator. Updating witnesses requires the prover (but not the verifier) to download the tails file.

Putting It All Together

This discussion has suppressed some details. The math has been simplified, and we haven’t discussed how an issuer copes with multiple tails files and revocation registries, or why that might be desirable. However, the broad flow of the mechanism should be apparent, and its features are now easy to summarize:

  • Issuers revoke by changing a number on the ledger. They can revoke as many credentials as they want in a single transaction, since they are just changing the answer to a math problem that either does or doesn’t include the factors they choose. Issuers do not have to contact anybody–provers or verifiers–to revoke. Changes take place globally, the instant the accumulator update transaction appears on the ledger.
  • Revocation is reversible.
  • Provers demonstrate proof of non-revocation in a privacy-preserving way. They cannot be correlated by something like a credential ID or a tails index. This is radically different from a revocation list approach, which requires correlation to test.
  • Verification of proof of non-revocation is extremely easy and cheap. No tails files are needed by verifiers, and computation is trivial. Proving non-revocation is somewhat more expensive for provers, but is also not overly complex.
  • Verifiers do not need to contact issuers or consult a revocation list to test revocation.

Indy-SDK Default Wallet Implementation

The purpose of this implementation is to provide a default encrypted wallet for indy-sdk.

The indy-sdk default wallet implementation uses hardened version of SQLCipher:

  • HMAC-SHA256 instead of HMAC-SHA1.
  • PBKDF2 100K rounds for passphrase key data instead of 64K.
  • PBKDF2 10 rounds for HMAC key derivation instead of 2.
  • Page size 2K instead of 1K.

Key Credentials

The default wallet allows an optional passphrase to used for encrypting the data. If no passphrase is provided either by leaving the key blank or omitting the key, the wallet will not be encrypted but stored in SQLite3 format. The passphrase to open the wallet is stored outside of indy-sdk and is left to the consumer’s security preference such as HSMs, TEEs, or offline methods.

indy-sdk supports a JSON parameter, credentials, for opening or creating a wallet:

{
   "key": "<passphrase>"
   "rekey": "<passphrase>"
}

If the credentials parameter is omitted or if key is an empty string, the wallet is left unencrypted.

key is the passphrase for opening the wallet and will be run through 100K rounds of PBKDF2.

If rekey is provided, the wallet will be opened using key and change the passphrase to the rekey value for future open calls. rekey is only required for an existing wallet and throws an error when attempting to create a new wallet.

If rekey is included with [null, “”], the wallet will be decrypted.

If key is [null, “”] and rekey contains a non-empty value, the wallet will now be encrypted.

NOTE

rekey is only necessary when changing key. Otherwise it should be omitted.

Cases

Normal wallet / No passphrase

To create a non-encrypted wallet, credentials can be empty or not specified. key may also be [null, “”].

Encrypted wallet / A Key

Encrypted wallets require a passphrase to be specified in the key field. Passphrase can be any non-blank value.

{
   "key": "Th1sIsArEALLY$3cuR3PassW0RD"
}
Normal wallet to encrypted wallet / Adding a Key

To add encryption to an existing non-encrypted wallet, key must be set to [null, “”] and rekey must be set to a valid passphrase. Then wallet open calls are the same as the Encrypted Wallet section.

{
   "key": null,
   "rekey": "il0V3MyN3WpA$SworD"
}
Encrypted wallet to normal wallet / Removing a Key

To remove encryption from an existing encrypted wallet, key must be set to the current value and rekey must be set to a blank value [null, “”]. Then wallet open calls are the same as the Normal wallet section.

{
   "key": "Th1sIsArEALLY$3cuR3PassW0RD",
   "rekey": null
}
Updating passphrase / Changing a Key

Rotating wallet passphrases is recommended. key must be set to the current value and rekey must be set to the new value. Then wallet open calls are the same as the Encrypted wallet section.

{
   "key": "Th1sIsArEALLY$3cuR3PassW0RD",
   "rekey": "s8c0R31tYi$hARd"
}

How Tos

This folder contains short tutorials demonstrating how to accomplish common tasks with the Indy SDK. For best results, proceed through the following in order:

  1. Write a DID and Query Its Verkey
  2. Rotate a Key
  3. Save a Schema and Cred Def
  4. Issue a Credential
  5. Negotiate a Proof
  6. Send a Secure Message

Building Indy SDK

Below are the different instructions on how to build Indy SDK for specific platforms:

Setup Indy SDK build environment for Ubuntu based distro (Ubuntu 16.04)

  1. Install Rust and rustup (https://www.rust-lang.org/install.html).

  2. Install required native libraries and utilities:

    apt-get update && \
    apt-get install -y \
       build-essential \
       pkg-config \
       cmake \
       libssl-dev \
       libsqlite3-dev \
       libzmq3-dev \
       libncursesw5-dev
    
  3. libindy requires the modern 1.0.14 version of libsodium but Ubuntu 16.04 does not support installation it’s from apt repository. Because of this, it requires to build and install libsodium from source:

cd /tmp && \
  curl https://download.libsodium.org/libsodium/releases/old/libsodium-1.0.14.tar.gz | tar -xz && \
   cd /tmp/libsodium-1.0.14 && \
   ./configure --disable-shared && \
   make && \
   make install && \
   rm -rf /tmp/libsodium-1.0.14
  1. Build libindy

    git clone https://github.com/hyperledger/indy-sdk.git
    cd ./indy-sdk/libindy
    cargo build
    cd ..
    

Note: libindy debian package, installed from the apt repository, is statically linked with libsodium. For manually building this can be achieved by passing --features sodium_static into cargo build command.

  1. Run integration tests: Start local nodes pool with Docker
 If you use this method then you have to specify the TEST_POOL_IP as specified below  when running the tests.

 It can be useful if we want to launch integration tests inside another container attached to
 the same docker network.
  • Run tests

    cd libindy
    RUST_TEST_THREADS=1 cargo test
    

    It is possible to change ip of test pool by providing of TEST_POOL_IP environment variable:

    RUST_TEST_THREADS=1 TEST_POOL_IP=10.0.0.2 cargo test
    
  1. Build indy-cli (Optional)

    indy-cli is dependent on libindy and should be built after it.

    cd cli/
    RUSTFLAGS=" -L ../libindy/target/debug" cargo build
    

    If you have followed the instructions to build libindy above, the default build type will be debug

    Make sure to add the libindy to the path. Replace /path/to with the actual path to the libindy directory. Using bash:

    echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/libindy/target/{BUILD TYPE}" >> ~/.bashrc
    sudo ldconfig
    source ~/.bashrc
    

    To run indy-cli, navigate to cli/target/debug and run ./indy-cli

See libindy/ci/ubuntu.dockerfile for example of Ubuntu based environment creation in Docker.

MacOS build guide

Automated build: clone the repo and run mac.build.sh in the libindy folder.

Manual steps

  1. Install Rust and rustup (https://www.rust-lang.org/install.html).

  2. Install required native libraries and utilities (libsodium is added with URL to homebrew since version<1.0.15 is required)

    brew install pkg-config
    brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/65effd2b617bade68a8a2c5b39e1c3089cc0e945/Formula/libsodium.rb   
    brew install automake 
    brew install autoconf
    brew install cmake
    brew install openssl
    brew install zeromq
    brew install zmq
    
  3. Setup environment variables:

    export PKG_CONFIG_ALLOW_CROSS=1
    export CARGO_INCREMENTAL=1
    export RUST_LOG=indy=trace
    export RUST_TEST_THREADS=1
    
  4. Setup OPENSSL_DIR variable: path to installed openssl library

    for version in `ls -t /usr/local/Cellar/openssl/`; do
         export OPENSSL_DIR=/usr/local/Cellar/openssl/$version
         break
    done
    
  5. Checkout and build the library:

    git clone https://github.com/hyperledger/indy-sdk.git
    cd ./indy-sdk/libindy
    cargo build
    
  6. To compile the CLI, libnullpay, or other items that depend on libindy:

    export LIBRARY_PATH=/path/to/sdk/libindy/target/<config>
    cd ../cli
    cargo build
    
  7. Set your DYLD_LIBRARY_PATH and LD_LIBRARY_PATH environment variables to the path of indy-sdk/libindy/target/debug. You may want to put these in your .bash_profile to persist them.

Note on running local nodes

In order to run local nodes on MacOS, it may be necessary to set up port mapping between the Docker container and local host. Follow the instructions in Indy SDK README

IOError while running of whole set of tests on MacOS

There is a possible case when some tests are failed if whole set of tests is run (cargo test). But failed tests will be successful in case of separate runs. If an error message like IOError Too many open files is present in logs when fails can be fixed by changing default limit.

ulimit -n <new limit value>

https://jira.hyperledger.org/browse/IS-1038

Setup Indy SDK build environment for Windows

Build Environment

  1. Setup a windows virtual machine. Free images are available at here
  2. Launch the virtual machine
  3. Download Visual Studio Community Edition 2017 (these instructions also work with Visual Studio Professional 2017)
  4. Check the boxes for the Desktop development with C++ and Linux Development with C++
  5. In the summary portion on the right hand side also check C++/CLI support
  6. Click install
  7. Download git-scm for windows here
  8. Install git for windows using:
    1. Use Git from Git Bash Only so it doesn’t change any path settings of the command prompt
    2. Checkout as is, commit Unix-style line endings. You shouldn’t be commiting anything anyway but just in case
    3. Use MinTTY
    4. Check all the boxes for:
      1. Enable file system caching
      2. Enable Git Credential Manager
      3. Enable symbolic links
  9. Download rust for windows here
    1. Choose installation option 1

Get/build dependencies

  • Open a the Git Bash command prompt
  • Change directories to Downloads:
cd Downloads
  • Clone the indy-sdk repository from github.
git clone https://github.com/hyperledger/indy-sdk.git
  • Download the prebuilt dependencies here
  • Extract them into the folder C:\BIN\x64
It really doesn’t matter where you put these as long as you remember where so you can set the environment variables to this path
  • If you are not building dependencies from source you may skip to Build
Binary deps
  • https://www.npcglib.org/~stathis/downloads/openssl-1.0.2k-vs2017.7z
  • https://download.libsodium.org/libsodium/releases/old/libsodium-1.0.14-msvc.zip
Source deps
  • http://www.sqlite.org/2017/sqlite-amalgamation-3180000.zip
  • https://github.com/zeromq/libzmq
Build sqlite

Download http://www.sqlite.org/2017/sqlite-amalgamation-3180000.zip

Create an empty static library project in Visual Studio and add sqlite.c file and 2 headers from extracted archive. Then just build it.

Build libzmq

Follow to http://zeromq.org/intro.

  • Download sources from last stable release for Windows.
  • Open zeromq-x.x.x/builds/msvc/vs2015/libzmq.sln with Visual Studio
  • If necessary change solution platforms on x64(if you are working on x64 arch).
  • On main menu bar choose build->build libzmq.
  • If build project was successful, two files libzmq.dll and libzmq.lib should appear in path zeromq-x.x.x/bin/x64/Debug/vXXX/dynamic.
  • rename libzmq.lib to zmq.lib.

Build

  • Get binary dependencies (libamcl*, openssl, libsodium, libzmq, sqlite3).

  • Put all *.{lib,dll} into one directory and headers into include/ subdirectory.

  • Open a windows command prompt

  • Configure MSVS environment to privide 64-bit builds by execution of vcvars64.bat:

    "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\"vcvars64.bat
    

    Note that depending on the version of Visual Studio placement of vcvars64.bat can be different. For example, it can be "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\amd64\vcvars64.bat"

  • Execute "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"

  • Point path to this directory using environment variables:

    • set INDY_PREBUILT_DEPS_DIR=C:\BIN\x64
    • set INDY_CRYPTO_PREBUILT_DEPS_DIR=C:\BIN\x64
    • set MILAGRO_DIR=C:\BIN\x64
    • set LIBZMQ_PREFIX=C:\BIN\x64
    • set SODIUM_LIB_DIR=C:\BIN\x64
    • set OPENSSL_DIR=C:\BIN\x64
  • Set PATH to find .dlls:

    • set PATH=C:\BIN\x64\lib;%PATH%
  • change dir to indy-sdk/libindy and run cargo build (you may want to add --release --target x86_64-pc-windows-msvc keys to cargo)

openssl-sys workaround

If your windows build fails complaining on gdi32.lib you should edit

  ~/.cargo/registry/src/github.com-*/openssl-sys-*/build.rs

and add

  println!("cargo:rustc-link-lib=dylib=gdi32");

to the end of main() function.

Then try to rebuild whole project.

Run integration tests

  • Start local nodes pool on 127.0.0.1:9701-9708 with Docker:

    docker build -f ci/indy-pool.dockerfile -t indy_pool .
    docker run -itd -p 9701-9709:9701-9709 indy_pool
    

    Please note that this port mapping between container and local host requires latest Docker for Windows (linux containers) and windows system with Hyper-V support.

    If you use some Docker distribution based on Virtual Box you can use Virtual Box’s port forwarding future to map 9701-9709 container ports to local 9701-9709 ports.

  • Run tests

    RUST_TEST_THREADS=1 cargo test
    

Setup Indy SDK build environment for RHEL-based distributions

These instructions have been tested on:

  • Amazon Linux 2017.03
  • Fedora 27

Please follow the instructions appropriate for your distribution.

Building libindy

1. Install Rust

Installation via rustup is recommended. Follow these instructions.

2. Install dependencies available in system repositories

For Amazon Linux 2017.03/CentOS/RHEL:

yum clean all
yum upgrade -y
yum groupinstall -y "Development Tools"
yum install -y \
   wget \
   cmake \
   pkgconfig \
   openssl-devel \
   sqlite-devel

For Fedora 26/27/28:

dnf clean all
dnf upgrade -y
dnf groupinstall -y "Development Tools"
dnf install -y \
   wget \
   cmake \
   pkgconfig \
   openssl-devel \
   sqlite-devel
3. Build and install a modern version of libsodium from source

For Amazon Linux 2017.03 or other distributions without libsodium available in system repositories:

cd /tmp
curl https://download.libsodium.org/libsodium/releases/old/libsodium-1.0.14.tar.gz | tar -xz
cd /tmp/libsodium-1.0.14
./configure
make
make install
rm -rf /tmp/libsodium-1.0.14

export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib

For Fedora 26/27/28, libsodium-1.0.14 is already available as a system package:

dnf install libsodium libsodium-devel
4. Additional dependencies

For Fedora 26/27/28, you may also need to install zeromq (libzmq) before being able to successfully build libindy:

dnf install zeromq zeromq-devel

If you discover that there are other dependencies not mentioned here, please open an issue.

5. Checkout and build the library
git checkout https://github.com/hyperledger/indy-sdk.git
cd ./indy-sdk/libindy
cargo build

Building indy-cli

indy-cli is dependent on libindy and must be built before indy-cli.

After building libindy, run the following commands from the indy-sdk directory:

cd indy-cli
RUSTFLAGS="-L ../libindy/target/{BUILD_TYPE}" cargo build

In the above command, substitute {BUILD_TYPE} with release or debug as appropriate.

If you have installed libindy.so to a system-wide location and subsequently run ldconfig, you do not need to specify the RUSTFLAGS environment variable as rustc should be able to find libindy.so without additional help.

If not, however, indy-cli needs help to be able to find libindy.so while being built. Setting LD_LIBRARY_PATH is only referenced at runtime and not at build time and is not helpful in this case. Specifying RUSTFLAGS in the command above will tell rustc to also check ../libindy/target/{BUILD_TYPE} for libraries.

Running integration tests

Starting up

Start local nodes pool with Docker

This may be useful if you want to launch integration tests inside another container attached to the same docker network.

Run tests
RUST_TEST_THREADS=1 cargo test

It is possible to change ip of test pool by providing of TEST_POOL_IP environment variable:

RUST_TEST_THREADS=1 TEST_POOL_IP=10.0.0.2 cargo test

See ci/amazon.dockerfile for example of Amazon Linux based environment creation in Docker.

Building binaries of LibIndy for Android

Not ready for production use! Not fully tested.

Supported architectures are arm, armv7, arm64, x86 and x86_64

Prerequisites

  • Docker

Dependencies

  • The build scripts downloads the prebuilt dependencies while building. The prebuilt dependencies are available here
  • If you want build the dependencies by yourself the instructions for that can be found here

How to build.

  • If on Ubuntu make sure you have these packages installed

    apt-get install -y \
             pkg-config \
             libssl-dev \
             libgmp3-dev \
             curl \
             build-essential \
             libsqlite3-dev \
             cmake \
             apt-transport-https \
             ca-certificates \
             wget \
             devscripts \
             libncursesw5-dev \
             libzmq3-dev \
             zip \
             unzip \
             jq
    
  • Run indy-sdk/libindy/build-libindy-android.sh to build libindy for arm, arm64 and x86

    • This generates the libindy zip file with each architecture in the indy-sdk/libindy
    • You can also set the LIBINDY_VERSION environment variable to append version number to generated zip file.
  • To generate the build for a single architecture run android.build.sh

    • e.g android.build.sh -d arm . The flag -d will download the dependencies automatically
    • e.g android.build.sh arm <PATH_TO_OPENSSL> <PATH_TO_SODIUM> <PATH_TO_ZMQ>. If -d flag is not passed you have to give paths to dependencies

Usage

  • Unzip the generated library.
  • Copy generated indy-sdk/libindy/build_scripts/android/libindy_arm/libindy.so, indy-sdk/libindy/build_scripts/android/indy-android-dependencies/prebuild/sodium/libsodium_arm/lib/libsodium.so, and indy-sdk/libindy/build_scripts/android/indy-android-dependencies/prebuild/zmq/libzmq_arm/lib/libzmq.so to the jniLibs/armeabi-v7a folder of your android project
  • Copy the corresponding files for jniLibs/arm64-v8a and jniLibs/x86 (similar to step above)
    • libindy.so file is the dynamic library which is statically linked to its dependencies. This library can be loaded into apk without having dependencies along with it.
    • libindy_shared.so file is the dynamic library which is dynamically linked to its dependencies. you need to pass the dependencies into apk.
  • In order to use the library in Android, you need to set the EXTERNAL_STORAGE environment variable and load the library using JNA

Os.setenv("EXTERNAL_STORAGE", getExternalFilesDir(null).getAbsolutePath(), true);

System.loadLibrary("indy");

Notes:

The shared binary (libindy.so) of only x86_64 architecture is not statically linked with its dependencies.

Make sure the Android app which is going to use libindy has permissions to write to external storage.

Add following line to AndroidManifest.xml

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

Android emulator generally use x86 images

If you receive a JNA error, you may need to add additonal files into your jniLibs folder.

  • Add the correct version of libjnidispatch.so to the corresponding subfolder in jniLibs -> https://github.com/java-native-access/jna/tree/master/lib/native
  • For example, android-aarch64.jar goes into the jniLibs/arm64-v8a subfolder
  • NOTE: You need to download the correct version of libjnidispatch.so (tag 4.5.1 in the jna repo is the version accepted by Indy SDK v1.5)

##Known Issues

  • The Android build does not successfully compile on OSX
    • It fails on the libzmq linking

Building binaries of Libnullpay for Android

Not ready for production use! Not fully tested.

Prerequisites

  • Docker

Dependencies

  • Libindy for Android

How to build.

  • Unzip libindy_android_<ARCH>_<VERSION>
  • Copy the extracted folder to indy-sdk/libnullpay/
  • Run indy-sdk/libnullpay/build-libnullpay-android.sh to build libnullpay for arm, arm64 and x86
  • To build for individual architecture, run indy-sdk/libnullpay/android.build.sh -d arm <PATH_TO_LIBINDY> to build libnullpay for arm
    • Or set env variable INDY_DIR=<PATH_TO_LIBINDY> and run android.build.sh -d arm to generate for arm
    • Set env variable INDY_DIR=<PATH_TO_LIBINDY> and run android.build.sh -d arm64 to generate for arm64
    • Set env variable INDY_DIR=<PATH_TO_LIBINDY> and run android.build.sh -d x86 to generate for x86

Libindy Migration Guides

These documents provide necessary information for Libindy migration. This document is written for developers using Libindy 1.3.0 to provide necessary information and to simplify their transition to the next release of our API.

Libindy migration Guide from v.1.3.0 to 1.4.0

A Developer Guide for Libindy migration

There are a lot APIs that have been changed in Libindy 1.4.0. This document is written for developers using Libindy 1.3.0 to provide necessary information and to simplify their transition to API of Libindy 1.4.0.

Notes

In the following tables, there are mappings for each Libindy API part of how 1.3.0 functionality maps to 1.4.0.

Functions from version 1.3.0 are listed in the left column, and the equivalent 1.4.0 function is placed in the right column.

  • If some function had been added, the word ‘NEW’ would be placed in the left column.
  • If some function had been deleted, the word ‘DELETED’ would be placed in the right column.
  • If some function had been changed, the current format would be placed in the right column.
  • If some function had not been changed, the symbol ‘=’ would be placed in the right column.
  • To get more details about current format of a function click on the description above it.
  • Bellow are signatures of functions in Libindy C API. The params of cb (except command_handle and err) will be result values of the similar function in any Libindy wrapper.
Anoncreds API mapping

Anoncreds API is the most affected part of Libindy. The complete design of Anoncreds can be found here.

Here are three main types of changes that have been done:

  • Improved support of Revocation.
  • Changed signature of some functions to avoid persisting in wallet intermediate steps entities.
  • Changed format of some input and output objects such as filter, proof request, credential info and etc to use different identifiers for public entities:
    • Schema - id in the format did | marker | name | version instead of triple name, version, did .
    • Credential Definition - id in the format did | marker | signatureType | schemaID instead of pair did, schema_key.
    • Revocation Registry - id in the format did | marker | credDefID | revocDefType | revocDefTag instead of seqNo.
v1.3.0 - Anoncreds API v1.4.0 - Anoncreds API
Issuer create Credential Schema
NEW
indy_issuer_create_schema(
            command_handle: i32,
            issuer_did: *const c_char,
            name: *const c_char,
            version: *const c_char,
            attrs: *const c_char,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode,
                   schema_id: *const c_char, 
                   schema_json: *const c_char))
        
Issuer create Credential Definition for the given Schema
indy_issuer_create_and_store_claim_def(
        command_handle: i32,
        wallet_handle: i32,
        issuer_did: *const c_char,
        schema_json: *const c_char,
        signature_type: *const c_char,
        create_non_revoc: bool,
        cb: fn(xcommand_handle: i32, 
               err: ErrorCode,
               claim_def_json: *const c_char))
        
indy_issuer_create_and_store_credential_def(
        command_handle: i32,
        wallet_handle: i32,
        issuer_did: *const c_char,
        schema_json: *const c_char,
        tag: *const c_char,
        signature_type: *const c_char,
        config_json: *const c_char,
        cb: fn(xcommand_handle: i32, 
               err: ErrorCode,
               cred_def_id: *const c_char,
               cred_def_json: *const c_char))
        
It is IMPORTANT for current Pool version get Schema from Ledger with correct seqNo to save backward compatibility before the creation of Credential Definition.
Issuer create a new revocation registry for the given Credential Definition
indy_issuer_create_and_store_revoc_reg(
        command_handle: i32,
        wallet_handle: i32,
        issuer_did: *const c_char,
        schema_seq_no: i32,
        max_claim_num: i32,
        cb: fn(xcommand_handle: i32, 
               err: ErrorCode,
               revoc_reg_json: *const c_char))
        
indy_issuer_create_and_store_revoc_reg(
    command_handle: i32,
    wallet_handle: i32,
    issuer_did: *const c_char,
    revoc_def_type: *const c_char,
    tag: *const c_char,
    cred_def_id: *const c_char,
    config_json: *const c_char,
    tails_writer_handle: i32,
    cb: fn(xcommand_handle: i32, 
           err: ErrorCode,
           revoc_reg_id: *const c_char,
           revoc_reg_def_json: *const c_char,
           revoc_reg_entry_json: *const c_char))
        
Issuer create credential offer
NEW
indy_issuer_create_credential_offer(
    command_handle: i32,
    wallet_handle: i32,
    cred_def_id: *const c_char,
    cb: fn(xcommand_handle: i32, 
           err: ErrorCode,
           cred_offer_json: *const c_char))
Note: The format of Credential Offer has been changed
Issuer issue Credential for the given Credential Request
indy_issuer_create_claim(
    command_handle: i32,
    wallet_handle: i32,
    claim_req_json: *const c_char,
    claim_json: *const c_char,
    user_revoc_index: i32,
    cb: fn(xcommand_handle: i32, 
           err: ErrorCode,
           revoc_reg_update_json: *const c_char,
           xclaim_json: *const c_char))
        
indy_issuer_create_credential(
    command_handle: i32,
    wallet_handle: i32,
    cred_offer_json: *const c_char,
    cred_req_json: *const c_char,
    cred_values_json: *const c_char,
    rev_reg_id: *const c_char,
    blob_storage_reader_handle: i32,
    cb: fn(xcommand_handle: i32, 
           err: ErrorCode,
           cred_json: *const c_char,
           cred_revoc_id: *const c_char,
           revoc_reg_delta_json: *const c_char))
        
Note: The format of Credential has been changed
Issuer revoke a credential
indy_issuer_revoke_claim(
    command_handle: i32,
    wallet_handle: i32,
    issuer_did: *const c_char,
    schema_seq_no: i32,
    user_revoc_index: i32,
    cb: fn(xcommand_handle: i32, 
           err: ErrorCode,
           revoc_reg_update_json: *const c_char))
        
indy_issuer_revoke_credential(
    command_handle: i32,
    wallet_handle: i32,
    blob_storage_reader_cfg_handle: i32,
    rev_reg_id: *const c_char,
    cred_revoc_id: *const c_char,
    cb: fn(xcommand_handle: i32, 
           err: ErrorCode,
           revoc_reg_delta_json: *const c_char))
        
Issuer merge two revocation registry deltas
NEW
indy_issuer_merge_revocation_registry_deltas(
    command_handle: i32,
    rev_reg_delta_json: *const c_char,
    other_rev_reg_delta_json: *const c_char,
    cb: fn(xcommand_handle: i32, 
           err: ErrorCode,
           merged_rev_reg_delta: *const c_char))
        
Prover stores a Claim Offer from the given issuer in a secure storage.
indy_prover_store_claim_offer(
            command_handle: i32,
            wallet_handle: i32,
            claim_offer_json: *const c_char,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode))
        
DELETED
Prover gets all stored Claim Offers
indy_prover_get_claim_offers(
        command_handle: i32,
        wallet_handle: i32,
        filter_json: *const c_char,
        cb: fn(xcommand_handle: i32, 
               err: ErrorCode,
               claim_offers_json: *const c_char))
        
DELETED
Prover creates a Master Secret
indy_prover_create_master_secret(
            command_handle: i32,
            wallet_handle: i32,
            master_secret_name: *const c_char,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode))
        
indy_prover_create_master_secret(
    command_handle: i32,
    wallet_handle: i32,
    master_secret_id: *const c_char,
    cb: fn(xcommand_handle: i32, 
           err: ErrorCode,
           out_master_secret_id: *const c_char))
        
Prover creates a Credential Request for the given Credential Offer
indy_prover_create_and_store_claim_req(
        command_handle: i32,
        wallet_handle: i32,
        prover_did: *const c_char,
        claim_offer_json: *const c_char,
        claim_def_json: *const c_char,
        master_secret_name: *const c_char,
        cb: fn(xcommand_handle: i32, 
               err: ErrorCode,
               claim_req_json: *const c_char))
        
indy_prover_create_credential_req(
    command_handle: i32,
    wallet_handle: i32,
    prover_did: *const c_char,
    cred_offer_json: *const c_char,
    cred_def_json: *const c_char,
    master_secret_id: *const c_char,
    cb: fn(xcommand_handle: i32, 
           err: ErrorCode,
           cred_req_json: *const c_char,
           cred_req_metadata_json: *const c_char))
        
Note: The format of Credential Request has been changed
Prover stores Credential in a secure wallet
indy_prover_store_claim(
                command_handle: i32,
                wallet_handle: i32,
                claims_json: *const c_char,
                cb: fn(xcommand_handle: i32, 
                       err: ErrorCode))
        
indy_prover_store_credential(
        command_handle: i32,
        wallet_handle: i32,
        cred_id: *const c_char,
        cred_req_metadata_json: *const c_char,
        cred_json: *const c_char,
        cred_def_json: *const c_char,
        rev_reg_def_json: *const c_char,
        cb: fn(xcommand_handle: i32, 
               err: ErrorCode,
               out_cred_id: *const c_char))
        
Prover gets human readable claims according to the filter
indy_prover_get_claims(
            command_handle: i32,
            wallet_handle: i32,
            filter_json: *const c_char,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode,
                   claims_json: *const c_char))
        
indy_prover_get_credentials(
    command_handle: i32,
    wallet_handle: i32,
    filter_json: *const c_char,
    cb: fn(xcommand_handle: i32, 
           err: ErrorCode,
           matched_credentials: *const c_char))
        
Note: The formats of Filter and Matched Credential have been changed
Prover gets human readable credentials matching the given proof request
indy_prover_get_claims_for_proof_req(
            command_handle: i32,
            wallet_handle: i32,
            proof_request_json: *const c_char,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode,
                   claims_json: *const c_char))
        
indy_prover_get_credentials_for_proof_req(
        command_handle: i32,
        wallet_handle: i32,
        proof_request_json: *const c_char,
        cb: fn(xcommand_handle: i32, 
               err: ErrorCode,
               credentials_json: *const c_char))
        
Note: The formats of Proof Request and Matched Credential have been changed
Prover creates a proof according to the given proof request
fn indy_prover_create_proof(
        command_handle: i32,
        wallet_handle: i32,
        proof_req_json: *const c_char,
        requested_claims_json: *const c_char,
        schemas_json: *const c_char,
        master_secret_name: *const c_char,
        claim_defs_json: *const c_char,
        revoc_regs_json: *const c_char,
        cb: fn(xcommand_handle: i32, 
               err: ErrorCode,
               proof_json: *const c_char))
        
indy_prover_create_proof(
        command_handle: i32,
        wallet_handle: i32,
        proof_req_json: *const c_char,
        requested_credentials_json: *const c_char,
        master_secret_id: *const c_char,
        schemas_json: *const c_char,
        credential_defs_json: *const c_char,
        rev_states_json: *const c_char,
        cb: fn(xcommand_handle: i32, 
               err: ErrorCode,
               proof_json: *const c_char))
        
Note: The formats of Proof Request, Requested Credentials and Proof have been changed
Verifier verifies a proof
indy_verifier_verify_proof(
            command_handle: i32,
            proof_request_json: *const c_char,
            proof_json: *const c_char,
            schemas_json: *const c_char,
            claim_defs_jsons: *const c_char,
            revoc_regs_json: *const c_char,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode,
                   valid: bool))
        
indy_verifier_verify_proof(
            command_handle: i32,
            proof_request_json: *const c_char,
            proof_json: *const c_char,
            schemas_json: *const c_char,
            credential_defs_json: *const c_char,
            rev_reg_defs_json: *const c_char,
            rev_regs_json: *const c_char,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode,
                   valid: bool))
        
Note: The formats of Proof Request and Proof have been changed
Create revocation state for a credential in the particular time moment
NEW
indy_create_revocation_state(
        command_handle: i32,
        blob_storage_reader_handle: i32,
        rev_reg_def_json: *const c_char,
        rev_reg_delta_json: *const c_char,
        timestamp: u64,
        cred_rev_id: *const c_char,
        cb: fn(xcommand_handle: i32, 
               err: ErrorCode,
               rev_state_json: *const c_char))
        
Create new revocation state for a credential based on existed
NEW
indy_update_revocation_state(
    command_handle: i32,
    blob_storage_reader_handle: i32,
    rev_state_json: *const c_char,
    rev_reg_def_json: *const c_char,
    rev_reg_delta_json: *const c_char,
    timestamp: u64,
    cred_rev_id: *const c_char,
    cb: fn(xcommand_handle: i32,
           err: ErrorCode,
           updated_rev_state_json: *const c_char))
        
Blob Storage API mapping

CL revocation schema introduces Revocation Tails entity used to hide information about revoked credential. Tails are static information that may require huge amount of data and stored outside of Libindy wallet. A way how to access tails blobs can be very application specific. To access this Libindy 1.4.0 provides new Blob Storage API.

v1.4.0 - Blob Storage API
Open Blob Storage reader
indy_open_blob_storage_reader(
                command_handle: i32,
                type_: *const c_char,
                config_json: *const c_char,
                cb: fn(command_handle_: i32, 
                       err: ErrorCode, 
                       handle: i32))
        
Open Blob Storage writer
indy_open_blob_storage_writer(command_handle: i32,
                              type_: *const c_char,
                              config_json: *const c_char,
                              cb: fn(command_handle_: i32,
                                     err: ErrorCode, 
                                     handle: i32))
        
Ledger API mapping

There are four types of changes in Ledger API:

  • Added new transaction builders for Revocation support
  • Added new transaction builders for Pool support
  • Added parsers of transaction responses related to entities participating in Anoncreds
  • Changed params of some transaction builders
v1.3.0 - Ledger API v1.4.0 - Ledger API
Builds a SCHEMA request
indy_build_schema_request(
            command_handle: i32,
            submitter_did: *const c_char,
            data: *const c_char,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode,
                   request_json: *const c_char))
              
Left the same but the format of data has been changed to:
{
    id: identifier of schema
    attrNames: array of attribute name strings
    name: Schema's name string
    version: Schema's version string,
    ver: version of the Schema json
}
Builds a GET_SCHEMA request
indy_build_get_schema_request(
            command_handle: i32,
            submitter_did: *const c_char,
            dest: *const c_char,
            data: *const c_char,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode,
                   request_json: *const c_char))
              
indy_build_get_schema_request(
            command_handle: i32,
            submitter_did: *const c_char,
            id: *const c_char,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode,
                   request_json: *const c_char))
              
Parse a GET_SCHEMA response
NEW
indy_parse_get_schema_response(
            command_handle: i32,
            get_schema_response: *const c_char,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode,
                   schema_id: *const c_char,
                   schema_json: *const c_char))
              
Builds an CRED_DEF request
indy_build_claim_def_txn(
    command_handle: i32,
    submitter_did: *const c_char,
    xref: i32,
    signature_type: *const c_char,
    data: *const c_char,
    cb: fn(xcommand_handle: i32, 
           err: ErrorCode,
           request_result_json: *const c_char))
              
indy_build_cred_def_request(
        command_handle: i32,
        submitter_did: *const c_char,
        data: *const c_char,
        cb: fn(xcommand_handle: i32, 
               err: ErrorCode,
               request_result_json: *const c_char))
              
Builds a GET_CRED_DEF request
indy_build_get_claim_def_txn(
        command_handle: i32,
        submitter_did: *const c_char,
        xref: i32,
        signature_type: *const c_char,
        origin: *const c_char,
        cb: fn(xcommand_handle: i32, 
               err: ErrorCode,
               request_json: *const c_char))
              
indy_build_get_cred_def_request(
        command_handle: i32,
        submitter_did: *const c_char,
        id: *const c_char,
        cb: fn(xcommand_handle: i32, 
               err: ErrorCode,
               request_json: *const c_char))
              
Parse a GET_CRED_DEF response
NEW
indy_parse_get_cred_def_response(
        command_handle: i32,
        get_cred_def_response: *const c_char,
        cb: fn(xcommand_handle: i32, 
               err: ErrorCode,
               cred_def_id: *const c_char,
               cred_def_json: *const c_char))
              
Builds a POOL_CONFIG request
NEW
indy_build_pool_config_request(
            command_handle: i32,
            submitter_did: *const c_char,
            writes: bool,
            force: bool,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode,
                   request_json: *const c_char))
              
Builds a POOL_UPGRADE request
NEW
indy_build_pool_upgrade_request(
            command_handle: i32,
            submitter_did: *const c_char,
            name: *const c_char,
            version: *const c_char,
            action: *const c_char,
            sha256: *const c_char,
            timeout: i32,
            schedule: *const c_char,
            justification: *const c_char,
            reinstall: bool,
            force: bool,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode,
                   request_json: *const c_char))
              
Builds a REVOC_REG_DEF request
NEW
indy_build_revoc_reg_def_request(
        command_handle: i32,
        submitter_did: *const c_char,
        data: *const c_char,
        cb: fn(xcommand_handle: i32, 
               err: ErrorCode,
               rev_reg_def_req: *const c_char))
              
Builds a GET_REVOC_REG_DEF request
NEW
indy_build_get_revoc_reg_def_request(
            command_handle: i32,
            submitter_did: *const c_char,
            id: *const c_char,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode,
                   request_json: *const c_char))
              
Parse a GET_REVOC_REG_DEF response
NEW
indy_parse_get_revoc_reg_def_response(
        command_handle: i32,
        get_revoc_reg_def_response: *const c_char,
        cb: fn(xcommand_handle: i32, 
               err: ErrorCode,
               revoc_reg_def_id: *const c_char,
               revoc_reg_def_json: *const c_char))
              
Builds a REVOC_REG_ENTRY request
NEW
indy_build_revoc_reg_entry_request(
            command_handle: i32,
            submitter_did: *const c_char,
            revoc_reg_def_id: *const c_char,
            rev_def_type: *const c_char,
            value: *const c_char,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode,
                   request_json: *const c_char))
              
Builds a GET_REVOC_REG request
NEW
indy_build_get_revoc_reg_request(
            command_handle: i32,
            submitter_did: *const c_char,
            revoc_reg_def_id: *const c_char,
            timestamp: i64,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode,
                   request_json: *const c_char))
              
Parse a GET_REVOC_REG response
NEW
indy_parse_get_revoc_reg_response(
        command_handle: i32,
        get_revoc_reg_response: *const c_char,
        cb: fn(xcommand_handle: i32, 
               err: ErrorCode,
               revoc_reg_def_id: *const c_char,
               revoc_reg_json: *const c_char,
               timestamp: u64))
              
Builds a GET_REVOC_REG_DELTA request
NEW
indy_build_get_revoc_reg_delta_request(
        command_handle: i32,
        submitter_did: *const c_char,
        revoc_reg_def_id: *const c_char,
        from: i64,
        to: i64,
        cb: fn(xcommand_handle: i32, 
               err: ErrorCode,
                request_json: *const c_char))
              
Parse a GET_REVOC_REG_DELTA response
NEW
indy_parse_get_revoc_reg_delta_response(
        command_handle: i32,
        get_revoc_reg_delta_response: *const c_char,
        cb: fn(xcommand_handle: i32, 
               err: ErrorCode,
               revoc_reg_def_id: *const c_char,
               revoc_reg_delta_json: *const c_char,
               timestamp: u64))
              
Signs and submits request message to validator pool
indy_sign_and_submit_request(...)
       
=
Send request message to validator pool
indy_submit_request(...)
       
=
Signs request message
indy_sign_request(...)
       
=
Builds a NYM request
indy_build_nym_request(...)
       
=
Builds a GET_NYM request
indy_build_get_nym_request(...)
       
=
Builds an ATTRIB request
indy_build_attrib_request(...)
       
=
Builds a GET_ATTRIB request
indy_build_get_attrib_request(...)
       
=
Builds a NODE request
indy_build_node_request(...)
       
=
Builds a GET_TXN request
indy_build_get_txn_request(...)
       
=
Signus API mapping

The most significant change of this part is renaming Signus API to Did API. Furthermore, some functions of Signus API has been deleted because the same goals can be achieved by using a combination of others.

v1.3.0 - Signus API v1.4.0 - Crypto API
Signs a message
indy_sign(...)
               
DELETED (use combination of either did.indy_key_for_did or did.indy_key_for_local_did with crypto.indy_crypto_sign instead)
Verify a signature
indy_verify_signature(...)
               
DELETED (use combination of either did.indy_key_for_did or did.indy_key_for_local_did with crypto.indy_crypto_verify instead)
Encrypts a message
indy_encrypt(...)
               
DELETED (use combination of either did.indy_key_for_did or did.indy_key_for_local_did with crypto.indy_crypto_auth_crypt instead)
Decrypts a message
indy_decrypt(...)
               
DELETED (use combination of either did.indy_key_for_did or did.indy_key_for_local_did with crypto.indy_crypto_auth_decrypt instead)
Encrypts a message by anonymous-encryption scheme
indy_encrypt_sealed(...)
               
DELETED (use combination of either did.indy_key_for_did or did.indy_key_for_local_did with crypto.indy_crypto_anon_crypt instead)
Decrypts a message by anonymous-encryption scheme
indy_decrypt_sealed(...)
               
DELETED (use combination of either did.indy_key_for_did or did.indy_key_for_local_did with crypto.indy_crypto_anon_decrypt instead)
Get info about My DID
NEW
indy_get_my_did_with_meta(command_handle: i32,
                          wallet_handle: i32,
                          my_did: *const c_char,
                          cb: fn(xcommand_handle: i32,
                                 err: ErrorCode,
                                 did_with_meta: *const c_char))
               
Lists created DIDs with metadata
NEW
indy_list_my_dids_with_meta(command_handle: i32,
                            wallet_handle: i32,
                            cb: fn(xcommand_handle: i32, 
                                   err: ErrorCode,
                                   ids: *const c_char))
               
Retrieves abbreviated verkey if it is possible otherwise return full verkey.
NEW
indy_abbreviate_verkey(command_handle: i32,
                       did: *const c_char,
                       full_verkey: *const c_char,
                       cb: fn(xcommand_handle: i32, 
                              err: ErrorCode,
                              verkey: *const c_char))
               
Creates key for a new DID
indy_create_and_store_my_did(...)
              
=
Generated temporary key for an existing DID.
indy_replace_keys_start(...)
              
=
Apply temporary key as main for an existing DID
indy_replace_keys_apply(...)
              
=
Saves their DID for a pairwise connection in a secured Wallet
indy_store_their_did(...)
              
=
Returns ver key (key id) for the given DID.
indy_key_for_did(...)
              
=
Returns ver key (key id) for the given DID.
indy_key_for_local_did(...)
              
=
Set/replace endpoint information for the given DID.
indy_set_endpoint_for_did(...)
              
=
Gets endpoint information for the given DID.
indy_get_endpoint_for_did(...)
              
=
Saves/replaces the meta information for the giving DID in the wallet.
indy_set_did_metadata(...)
              
=
Retrieves the meta information for the giving DID in the wallet.
indy_get_did_metadata(...)
      
=
Crypto API mapping
v1.3.0 - Crypto API v1.4.0 - Crypto API
Encrypt a message by authenticated-encryption scheme.
indy_crypto_box(
            command_handle: i32,
            wallet_handle: i32,
            my_vk: *const c_char,
            their_vk: *const c_char,
            message_raw: *const u8,
            message_len: u32,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode,
                   encrypted_msg_raw: *const u8, 
                   encrypted_msg_len: u32,
                   nonce_raw: *const u8, 
                   nonce_len: u32))
        
indy_crypto_auth_crypt(
                command_handle: i32,
                wallet_handle: i32,
                my_vk: *const c_char,
                their_vk: *const c_char,
                msg_data: *const u8,
                msg_len: u32,
                cb: fn(command_handle_: i32,
                       err: ErrorCode,
                       encrypted_msg: *const u8,
                       encrypted_len: u32))
        
Decrypt a message by authenticated-encryption scheme.
indy_crypto_box_open(
            command_handle: i32,
            wallet_handle: i32,
            my_vk: *const c_char,
            their_vk: *const c_char,
            encrypted_msg_raw: *const u8,
            encrypted_msg_len: u32,
            nonce_raw: *const u8,
            nonce_len: u32,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode,
                   decrypted_msg_raw: *const u8, 
                   decrypted_msg_len: u32))
        
indy_crypto_auth_decrypt(
                command_handle: i32,
                wallet_handle: i32,
                my_vk: *const c_char,
                encrypted_msg: *const u8,
                encrypted_len: u32,
                cb: fn(command_handle_: i32,
                       err: ErrorCode,
                       their_vk: *const c_char,
                       msg_data: *const u8,
                       msg_len: u32))
        
Encrypts a message by anonymous-encryption scheme.
indy_crypto_box_seal(
            command_handle: i32,
            their_vk: *const c_char,
            message_raw: *const u8,
            message_len: u32,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode,
                   encrypted_msg_raw: *const u8, 
                   encrypted_msg_len: u32))
        
indy_crypto_anon_crypt(
                command_handle: i32,
                their_vk: *const c_char,
                msg_data: *const u8,
                msg_len: u32,
                cb: fn(command_handle_: i32,
                       err: ErrorCode,
                       encrypted_msg: *const u8,
                       encrypted_len: u32))
        
Decrypts a message by anonymous-encryption scheme.
indy_crypto_box_seal_open(
            command_handle: i32,
            wallet_handle: i32,
            my_vk: *const c_char,
            encrypted_msg_raw: *const u8,
            encrypted_msg_len: u32,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode,
                   decrypted_msg_raw: *const u8, 
                   decrypted_msg_len: u32))
        
indy_crypto_anon_decrypt(
                command_handle: i32,
                wallet_handle: i32,
                my_vk: *const c_char,
                encrypted_msg: *const u8,
                encrypted_len: u32,
                cb: fn(command_handle_: i32,
                       err: ErrorCode,
                       msg_data: *const u8,
                       msg_len: u32))
        
Creates keys pair and stores in the wallet.
  indy_create_key(...)
                
=
Saves/replaces the meta information for the giving key in the wallet.
  indy_set_key_metadata(...)
                
=
Retrieves the meta information for the giving key in the wallet.
  indy_get_key_metadata(...)
                
=
Signs a message with a key.
  indy_crypto_sign(...)
                
=
Verify a signature with a verkey.
  indy_crypto_verify(...)
                
=
Agent API mapping

The Agent API was completely deleted from Libindy but its functionality can be achieved by using Crypto API.

v1.3.0 - Agent API v1.4.0 - Crypto API
Encrypt a message by authenticated-encryption scheme
indy_prep_msg(
            command_handle: i32,
            wallet_handle: i32,
            sender_vk: *const c_char,
            recipient_vk: *const c_char,
            msg_data: *const u8,
            msg_len: u32,
            cb: fn(command_handle_: i32,
                   err: ErrorCode,
                   encrypted_msg: *const u8,
                   encrypted_len: u32))
              
indy_crypto_auth_crypt(
            command_handle: i32,
            wallet_handle: i32,
            my_vk: *const c_char,
            their_vk: *const c_char,
            msg_data: *const u8,
            msg_len: u32,
            cb: fn(command_handle_: i32,
                   err: ErrorCode,
                   encrypted_msg: *const u8,
                   encrypted_len: u32))
        
Encrypts a message by anonymous-encryption scheme.
indy_prep_anonymous_msg(
          command_handle: i32,
          recipient_vk: *const c_char,
          msg_data: *const u8,
          msg_len: u32,
          cb: fn(command_handle_: i32,
                 err: ErrorCode,
                 encrypted_msg: *const u8,
                 encrypted_len: u32))
        
indy_crypto_anon_crypt(
          command_handle: i32,
          their_vk: *const c_char,
          msg_data: *const u8,
          msg_len: u32,
          cb: fn(command_handle_: i32,
                 err: ErrorCode,
                 encrypted_msg: *const u8,
                 encrypted_len: u32))
        
Decrypts a message.
indy_parse_msg(
            command_handle: i32,
            wallet_handle: i32,
            recipient_vk: *const c_char,
            encrypted_msg: *const u8,
            encrypted_len: u32,
            cb: fn(command_handle_: i32,
                   err: ErrorCode,
                   sender_vk: *const c_char,
                   msg_data: *const u8,
                   msg_len: u32))
      
Decrypt a message by authenticated-encryption scheme.
Reverse to indy_crypto_auth_crypt
indy_crypto_auth_decrypt( command_handle: i32, wallet_handle: i32, my_vk: *const c_char, encrypted_msg: *const u8, encrypted_len: u32, cb: fn(command_handle_: i32, err: ErrorCode, their_vk: *const c_char, msg_data: *const u8, msg_len: u32))
Decrypts a message by anonymous-encryption scheme.
Reverse to indy_crypto_anon_crypt
indy_crypto_anon_decrypt(command_handle: i32, wallet_handle: i32, my_vk: *const c_char, encrypted_msg: *const u8, encrypted_len: u32, cb: fn(command_handle_: i32, err: ErrorCode, msg_data: *const u8, msg_len: u32))
Pairwise API mapping

The Agent API has not been changed.

Pool API mapping

The Pool API has not been changed.

Wallet API mapping

The Wallet API has not been changed.

Explore the Code

Here you can find integration tests that demonstrates basic revocation scenario using Libindy and Ledger

Libindy 1.4 to 1.5 migration Guide

This document is written for developers using Libindy to provide necessary information and to simplify their transition to Libindy 1.5 from Libindy 1.4. If you are using older Libindy version you can check migration guides history:

Notes

Migration information is organized in tables, there are mappings for each Libindy API part of how older version functionality maps to a newer one. Functions from older version are listed in the left column, and the equivalent newer version function is placed in the right column:

  • If some function had been added, the word ‘NEW’ would be placed in the left column.
  • If some function had been deleted, the word ‘DELETED’ would be placed in the right column.
  • If some function had been deprecated, the word ‘DEPRECATED’ would be placed in the right column.
  • If some function had been changed, the current format would be placed in the right column.
  • If some function had not been changed, the symbol ‘=’ would be placed in the right column.
  • To get more details about current format of a function click on the description above it.
  • Bellow are signatures of functions in Libindy C API. The params of cb (except command_handle and err) will be result values of the similar function in any Libindy wrapper.
Wallet API
  • In v1.4 libindy allowed to plug different wallet implementations. Plugged wallet in v1.4 handled both security and storage layers. Libindy v1.5 restricts plugged interface by handling only storage layer. All encryption is performed in libindy. It simplifies plugged wallets and provides warranty of a good security level for 3d party wallets implementations.
  • Libindy v1.5 changes wallet format to allow efficient and flexible search for entities with pagination support. WARNING wallet format of libindy v1.5 isn’t compatible with a wallet format of libindy v1.4.
  • There have been added functions that allow performing Export/Import of Wallet. Note these endpoints are EXPERIMENTAL. Function signature and behavior may change in the future releases.
  • indy_list_wallets endpoint is DEPRECATED and will be removed in the next release. The main idea is avoid maintaining created wallet list on libindy side. It will allow to access wallets from a cluster and solve some problems on mobile platforms. indy_create_wallet and indy_open_wallet endpoints will also get related changes in the next release.

References:

v1.4.0 - Wallet API v1.5.0 - Wallet API
Register custom wallet storage implementation
indy_register_wallet_type(
        command_handle: i32,
        xtype: *const c_char,
        create: WalletCreate,
        open: WalletOpen,
        set: WalletSet,
        get: WalletGet,
        get_not_expired: WalletGetNotExpired,
        list: WalletList,
        close: WalletClose,
        delete: WalletDelete,
        free: WalletFree,
        cb: fn(xcommand_handle: i32,
             err: ErrorCode))
      
indy_register_wallet_storage(
        command_handle: i32,
        type_: *const c_char,
        create: WalletCreate,
        open: WalletOpen,
        close: WalletClose,
        delete: WalletDelete,
        add_record: WalletAddRecord,
        update_record_value: WalletUpdateRecordValue,
        update_record_tags: WalletUpdateRecordTags,
        add_record_tags: WalletAddRecordTags,
        delete_record_tags: WalletDeleteRecordTags,
        delete_record: WalletDeleteRecord,
        get_record: WalletGetRecord,
        get_record_id: WalletGetRecordId,
        get_record_type: WalletGetRecordType,
        get_record_value: WalletGetRecordValue,
        get_record_tags: WalletGetRecordTags,
        free_record: WalletFreeRecord,
        get_storage_metadata: WalletGetStorageMetadata,
        set_storage_metadata: WalletSetStorageMetadata,
        free_storage_metadata: WalletFreeStorageMetadata,
        search_records: WalletSearchRecords,
        search_all_records: WalletSearchAllRecords,
        get_search_total_count: WalletGetSearchTotalCount,
        fetch_search_next_record: WalletFetchSearchNextRecord,
        free_search: WalletFreeSearch,
        cb: fn(xcommand_handle: i32,
               err: ErrorCode))
      
Create a new secure wallet
indy_create_wallet(command_handle: i32,
                   pool_name: *const c_char,
                   name: *const c_char,
                   xtype: *const c_char,
                   config: *const c_char,
                   credentials: *const c_char,
                   cb: Option)
      
indy_create_wallet(command_handle: i32,
                   pool_name: *const c_char,
                   name: *const c_char,
                   storage_type: *const c_char,
                   config: *const c_char,
                   credentials_json: *const c_char,
                   cb: Option)
      
Note: Signatures look similar, but credentials_json parameter became the required. Format of config and credentials_json parameters was changed.
Open the wallet with specific name
indy_open_wallet(command_handle: i32,
                 name: *const c_char,
                 runtime_config: *const c_char,
                 credentials: *const c_char,
                 cb: Option)
      
indy_open_wallet(command_handle: i32,
                 name: *const c_char,
                 runtime_config: *const c_char,
                 credentials_json: *const c_char,
                 cb: Option)
      
Note: Signatures look similar, but credentials_json parameter became the required. Format of credentials_json parameter was changed.
Deletes created wallet
indy_delete_wallet(command_handle: i32,
                   name: *const c_char,
                   credentials: *const c_char,
                   cb: Option)
      
indy_delete_wallet(command_handle: i32,
                   name: *const c_char,
                   credentials_json: *const c_char,
                   cb: Option)
      
Note: Signatures look similar, but credentials_json parameter became the required. Format of credentials_json parameter was changed.
Export wallet
NEW
indy_export_wallet(command_handle: i32,
                   wallet_handle: i32,
                   export_config_json: *const c_char,
                   cb: fn(xcommand_handle: i32, err: ErrorCode))
        
Import wallet
NEW
indy_import_wallet(command_handle: i32,
                   pool_name: *const c_char,
                   name: *const c_char,
                   storage_type: *const c_char,
                   config: *const c_char,
                   credentials: *const c_char,
                   import_config_json: *const c_char,
                   cb: fn(xcommand_handle: i32, err: ErrorCode))
        
Non-Secrets API

Libindy v1.5 introduces set of API endpoints are intended to store and read application specific identity data in the wallet. This API doesn’t have an access to secrets stored in the wallet.

References:

v1.4.0 - Non-Secrets API v1.5.0 - Non-Secrets API
Create a new record in the wallet
NEW
indy_add_wallet_record(command_handle: i32,
                       wallet_handle: i32,
                       type_: *const c_char,
                       id: *const c_char,
                       value: *const c_char,
                       tags_json: *const c_char,
                       cb: fn(command_handle_: i32,
                              err: ErrorCode))
        
Update a wallet record value
NEW
indy_update_wallet_record_value(command_handle: i32,
                                wallet_handle: i32,
                                type_: *const c_char,
                                id: *const c_char,
                                value: *const c_char,
                                cb: fn(command_handle_: i32,
                                       err: ErrorCode))
        
Update a wallet record tags
NEW
indy_update_wallet_record_tags(command_handle: i32,
                               wallet_handle: i32,
                               type_: *const c_char,
                               id: *const c_char,
                               tags_json: *const c_char,
                               cb: fn(command_handle_: i32,
                                      err: ErrorCode))
        
Add new tags to the wallet record
NEW
indy_add_wallet_record_tags(command_handle: i32,
                            wallet_handle: i32,
                            type_: *const c_char,
                            id: *const c_char,
                            tags_json: *const c_char,
                            cb: fn(command_handle_: i32,
                                   err: ErrorCode))
        
Delete tags from the wallet record
NEW
indy_delete_wallet_record_tags(command_handle: i32,
                               wallet_handle: i32,
                               type_: *const c_char,
                               id: *const c_char,
                               tag_names_json: *const c_char,
                               cb: fn(command_handle_: i32,
                                      err: ErrorCode))
        
Delete an existing wallet record in the wallet
NEW
indy_delete_wallet_record(command_handle: i32,
                          wallet_handle: i32,
                          type_: *const c_char,
                          id: *const c_char,
                          cb: fn(command_handle_: i32,
                                 err: ErrorCode))
        
Get an wallet record by id
NEW
indy_get_wallet_record(command_handle: i32,
                       wallet_handle: i32,
                       type_: *const c_char,
                       id: *const c_char,
                       options_json: *const c_char,
                       cb: fn(command_handle_: i32,
                              err: ErrorCode,
                              record_json: *const c_char))
        
Search for wallet records
NEW
indy_open_wallet_search(command_handle: i32,
                        wallet_handle: i32,
                        type_: *const c_char,
                        query_json: *const c_char,
                        options_json: *const c_char,
                        cb: fn(command_handle_: i32,
                               err: ErrorCode,
                               search_handle: i32))
        
Fetch next records for wallet search.
NEW
indy_fetch_wallet_search_next_records(command_handle: i32,
                                      wallet_handle: i32,
                                      wallet_search_handle: i32,
                                      count: usize,
                                      cb: fn(command_handle_: i32,
                                             err: ErrorCode,
                                             records_json: *const c_char))
        
Close wallet search
NEW
indy_close_wallet_search(command_handle: i32,
                         wallet_search_handle: i32,
                         cb: fn(command_handle_: i32,
                                err: ErrorCode))
        
Payments API

This API is intended to provide the ability to register custom payment method and then to perform the main payments operations:

  • Creation of payment address
  • Listing of payment addresses
  • Getting list of UTXO for payment address
  • Sending payment transaction
  • Adding fees to transactions
  • Getting transactions fees amount

Note all endpoints in this group are EXPERIMENTAL. Function signatures and behavior may change in the future releases.

References:

v1.4.0 - Payments API v1.5.0 - Payments API
Register custom payment implementation
NEW
indy_register_payment_method(command_handle: i32,
                             payment_method: *const c_char,
                             create_payment_address: CreatePaymentAddressCB,
                             add_request_fees: AddRequestFeesCB,
                             parse_response_with_fees: ParseResponseWithFeesCB,
                             build_get_utxo_request: BuildGetUTXORequestCB,
                             parse_get_utxo_response: ParseGetUTXOResponseCB,
                             build_payment_req: BuildPaymentReqCB,
                             parse_payment_response: ParsePaymentResponseCB,
                             build_mint_req: BuildMintReqCB,
                             build_set_txn_fees_req: BuildSetTxnFeesReqCB,
                             build_get_txn_fees_req: BuildGetTxnFeesReqCB,
                             parse_get_txn_fees_response: ParseGetTxnFeesResponseCB,
                             cb: fn(command_handle_: i32,
                                    err: ErrorCode))
        
Create the payment address for specified payment method
NEW
indy_create_payment_address(command_handle: i32,
                            wallet_handle: i32,
                            payment_method: *const c_char,
                            config: *const c_char,
                            cb: fn(command_handle_: i32,
                                   err: ErrorCode,
                                   payment_address: *const c_char))
        
Lists all payment addresses that are stored in the wallet
NEW
indy_list_payment_addresses(command_handle: i32,
                            wallet_handle: i32,
                            cb: fn(command_handle_: i32,
                                   err: ErrorCode,
                                   payment_addresses_json: *const c_char))
        
Modifies Indy request by adding information how to pay fees for this transaction according to selected payment method
NEW
indy_add_request_fees(command_handle: i32,
                      wallet_handle: i32,
                      submitter_did: *const c_char,
                      req_json: *const c_char,
                      inputs_json: *const c_char,
                      outputs_json: *const c_char,
                      cb: fn(command_handle_: i32,
                             err: ErrorCode,
                             req_with_fees_json: *const c_char,
                             payment_method: *const c_char))
        
Parses response for Indy request with fees
NEW
indy_parse_response_with_fees(command_handle: i32,
                              payment_method: *const c_char,
                              resp_json: *const c_char,
                              cb: fn(command_handle_: i32,
                                     err: ErrorCode,
                                     utxo_json: *const c_char))
        
Builds Indy request for getting UTXO list for payment address according to this payment method
NEW
indy_build_get_utxo_request(command_handle: i32,
                            wallet_handle: i32,
                            submitter_did: *const c_char,
                            payment_address: *const c_char,
                            cb: fn(command_handle_: i32,
                                   err: ErrorCode,
                                   get_utxo_txn_json: *const c_char,
                                   payment_method: *const c_char))
        
Parses response for Indy request for getting UTXO list
NEW
indy_parse_get_utxo_response(command_handle: i32,
                             payment_method: *const c_char,
                             resp_json: *const c_char,
                             cb: fn(command_handle_: i32,
                                    err: ErrorCode,
                                    utxo_json: *const c_char))
        
Builds Indy request for doing tokens payment according to this payment method
NEW
indy_build_payment_req(command_handle: i32,
                       wallet_handle: i32,
                       submitter_did: *const c_char,
                       inputs_json: *const c_char,
                       outputs_json: *const c_char,
                       cb: fn(command_handle_: i32,
                              err: ErrorCode,
                              payment_req_json: *const c_char,
                              payment_method: *const c_char))
        
Parses response for Indy request for payment txn
NEW
indy_parse_payment_response(command_handle: i32,
                            payment_method: *const c_char,
                            resp_json: *const c_char,
                            cb: fn(command_handle_: i32,
                                   err: ErrorCode,
                                   utxo_json: *const c_char))
        
Builds Indy request for doing tokens minting according to this payment method
NEW
indy_build_mint_req(command_handle: i32,
                    wallet_handle: i32,
                    submitter_did: *const c_char,
                    outputs_json: *const c_char,
                    cb: fn(command_handle_: i32,
                           err: ErrorCode,
                           mint_req_json: *const c_char,
                           payment_method: *const c_char))
        
Builds Indy request for setting fees for transactions in the ledger
NEW
indy_build_set_txn_fees_req(command_handle: i32,
                            wallet_handle: i32,
                            submitter_did: *const c_char,
                            payment_method: *const c_char,
                            fees_json: *const c_char,
                            cb: fn(command_handle_: i32,
                                   err: ErrorCode,
                                   set_txn_fees_json: *const c_char))
        
Builds Indy get request for getting fees for transactions in the ledger
NEW
indy_build_get_txn_fees_req(command_handle: i32,
                            wallet_handle: i32,
                            submitter_did: *const c_char,
                            payment_method: *const c_char,
                            cb: fn(command_handle_: i32,
                                   err: ErrorCode,
                                   get_txn_fees_json: *const c_char))
        
Parses response for Indy request for getting fees
NEW
indy_parse_get_txn_fees_response(command_handle: i32,
                                 payment_method: *const c_char,
                                 resp_json: *const c_char,
                                 cb: fn(command_handle_: i32,
                                        err: ErrorCode,
                                        fees_json: *const c_char))
        
Pool API

The next stable release 1.4 of Indy-Node contains breaking changes related to the transaction format. The changes are done for better versioning support (to support compatibility in the future) and more readable format of data (separating payload data, metadata and signature).

LibIndy 1.5 supports both Indy Node protocols Indy Node v1.3 (version 1) and v1.4 (version 2). There is a global property PROTOCOL_VERSION that used in every request to the pool:

  • PROTOCOL_VERSION=1 for IndyNode 1.3
  • PROTOCOL_VERSION=2 for IndyNode 1.4

To switch between protocol versions used libindy 1.5 provides new indy_set_protocol_version endpoint:

  • By default LibIndy 1.5 is compatible with Indy Node 1.3, and not 1.4
  • LibIndy 1.5 can become compatible with Indy Node 1.4 if indy_set_protocol_version(2) is called during app initialization.
  • An application can freely update to LibIndy 1.5 and still use stable Node 1.3
  • If an app wants to work with the latest master or Stable Node 1.4, then it needs to support breaking changes (there are not so many, mostly a new reply for write txns as txn format is changed). See References.

References:

v1.4.0 - Pool API v1.5.0 - Pool API
Set protocol version
NEW
indy_set_protocol_version(command_handle: i32,
                          protocol_version: usize,
                          cb: fn(xcommand_handle: i32,
                                 err: ErrorCode))
        
Ledger API
  • indy_multi_sign_request was added to Libindy Pool API to allow signing of requests by multiple identity owners.
  • indy_build_get_validator_info_request was added to allow building of VALIDATOR_INFO action request. See References.
  • indy_build_pool_restart_request was added to allow building of POOL_RESTART action request. See References.
  • indy_register_transaction_parser_for_sp endpoint was added to allow usage of StateProof optimization in Client-Node communication with a custom transactions that can be added by Node plugins.
  • indy_build_get_txn_request endpoint was changed to allow reading of non-domain ledgers transactions

References:

v1.4.0 - Ledger API v1.5.0 - Ledger API
Multi signs request message
NEW
indy_multi_sign_request(command_handle: i32,
                        wallet_handle: i32,
                        submitter_did: *const c_char,
                        request_json: *const c_char,
                        cb: fn(xcommand_handle: i32,
                               err: ErrorCode,
                               signed_request_json: *const c_char))
        
Builds a GET_VALIDATOR_INFO request
NEW
indy_build_get_validator_info_request(command_handle: i32,
                                      submitter_did: *const c_char,
                                      cb: fn(xcommand_handle: i32,
                                             err: ErrorCode,
                                             request_json: *const c_char))
        
Builds a POOL_RESTART request
NEW
indy_build_pool_restart_request(command_handle: i32,
                                submitter_did: *const c_char,
                                action: *const c_char,
                                datetime: *const c_char,
                                cb: fn(xcommand_handle: i32,
                                       err: ErrorCode,
                                       request_json: *const c_char))
        
Register custom callbacks for parsing of state proofs
NEW
indy_register_transaction_parser_for_sp(command_handle: i32,
                                        txn_type: *const c_char,
                                        parser: CustomTransactionParser,
                                        free: CustomFree,
                                        cb: fn(command_handle_: i32,
                                               err: ErrorCode))
        
Builds a GET_TXN request. Request to get any transaction by its seq_no
indy_build_get_txn_request(
            command_handle: i32,
            submitter_did: *const c_char,
            seq_no: i32,
            cb: fn(xcommand_handle: i32,
                   err: ErrorCode,
                   request_json: *const c_char))
      
indy_build_get_txn_request(
            command_handle: i32,
            submitter_did: *const c_char,
            ledger_type: *const c_char,
            seq_no: i32,
            cb: fn(xcommand_handle: i32,
                   err: ErrorCode,
                   request_json: *const c_char))
      
Note: ledger_type parameter was added to provide a string identifier of a target ledger.

Libindy 1.5 to 1.6 migration Guide

This document is written for developers using Libindy to provide necessary information and to simplify their transition to Libindy 1.6 from Libindy 1.5. If you are using older Libindy version you can check migration guides history:

Notes

Migration information is organized in tables, there are mappings for each Libindy API part of how older version functionality maps to a newer one. Functions from older version are listed in the left column, and the equivalent newer version function is placed in the right column:

  • If some function had been added, the word ‘NEW’ would be placed in the left column.
  • If some function had been deleted, the word ‘DELETED’ would be placed in the right column.
  • If some function had been deprecated, the word ‘DEPRECATED’ would be placed in the right column.
  • If some function had been changed, the current format would be placed in the right column.
  • If some function had not been changed, the symbol ‘=’ would be placed in the right column.
  • To get more details about current format of a function click on the description above it.
  • Bellow are signatures of functions in Libindy C API. The params of cb (except command_handle and err) will be result values of the similar function in any Libindy wrapper.

Libindy 1.5 to 1.6.0 migration Guide

Wallet API

The main idea of changes performed in Wallet API is to avoid maintaining created wallet list on Libindy side. It allows to access wallets from a cluster and solves some problems on mobile platforms.

The following changes have been performed:

  • Changed Wallet export serialization:
    • Use MsgPack instead of custom entities serialization to be more reliable and allow extend-ability in backward compatible way.
    • Changed header format to be more reliable and allow extend-ability in backward compatible way.
    • Use STOP message to make sure that there was no truncation of export file.
    • Removed EXPERIMENTAL notice for import/export API endpoints.
  • Removed association between Wallet and Pool.
  • Removed persistence of Wallet configuration by Libindy.
  • A significant part of Wallet APIs has been updated to accept wallet configuration as a single json which provides whole wallet configuration. This wallet configuration json has the following format:
 {
   "id": string, Identifier of the wallet.
         Configured storage uses this identifier to lookup exact wallet data placement.
   "storage_type": optional<string>, Type of the wallet storage. Defaults to 'default'.
                  'Default' storage type allows to store wallet data in the local file.
                  Custom storage types can be registered with indy_register_wallet_storage call.
   "storage_config": optional<object>, Storage configuration json. Storage type defines set of supported keys.
                     Can be optional if storage supports default configuration.
                     For 'default' storage type configuration is:
   {
     "path": optional<string>, Path to the directory with wallet files.
             Defaults to $HOME/.indy_client/wallets.
             Wallet will be stored in the file {path}/{id}/sqlite.db
   }
 }

WARNING Wallet format of libindy v1.6 isn’t compatible with a wallet format of libindy v1.5.

v1.5.0 - Wallet API v1.6.0 - Wallet API
Create a new secure wallet
indy_create_wallet(command_handle: i32,
                   pool_name: *const c_char,
                   name: *const c_char,
                   xtype: *const c_char,
                   config: *const c_char,
                   credentials: *const c_char,
                   cb: Option)
      
indy_create_wallet(command_handle: i32,
                   config: *const c_char,
                   credentials: *const c_char,
                   cb: Option)
      
Note: Format of config parameter was changed. Current format is described above.
Open the wallet
indy_open_wallet(command_handle: i32,
                 name: *const c_char,
                 runtime_config: *const c_char,
                 credentials_json: *const c_char,
                 cb: Option)
      
indy_open_wallet(command_handle: i32,
                 config: *const c_char,
                 credentials: *const c_char,
                 cb: Option)
      
Note: Format of config parameter was changed. Current format is described above.
Deletes created wallet
indy_delete_wallet(command_handle: i32,
                   name: *const c_char,
                   credentials: *const c_char,
                   cb: Option)
      
indy_delete_wallet(command_handle: i32,
                   config: *const c_char,
                   credentials: *const c_char,
                   cb: Option)
      
Note: Format of config parameter was changed. Current format is described above.
Import wallet
indy_import_wallet(
            command_handle: i32,
            pool_name: *const c_char,
            name: *const c_char,
            storage_type: *const c_char,
            config: *const c_char,
            credentials: *const c_char,
            import_config_json: *const c_char,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode))
        
indy_import_wallet(
            command_handle: i32,
            config: *const c_char,
            credentials: *const c_char,
            import_config_json: *const c_char,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode))
        
Note: Format of config parameter was changed. Current format is described above.
Lists created wallets
indy_list_wallets(command_handle: i32,
                  cb: fn(xcommand_handle: i32,
                         err: ErrorCode,
                         wallets: *const c_char))
        
DELETED
Anoncreds API

The main idea of changes performed in Anoncreds API is integration tags based search to Anoncreds workflow as it has done in Non-Secrets API.

  • Create tags for a stored object.
  • Provide efficient and flexible search for entities using WQL.
  • Avoid immediately returning all matched records.
  • Provide ability to fetch records by small batches.

The following changes have been performed:

  • Updated behavior of indy_prover_store_credential API function to create tags for a stored credential object. Here is the list of tags will be created:
{
    "schema_id": <credential schema id>,
    "schema_issuer_did": <credential schema issuer did>,
    "schema_name": <credential schema name>,
    "schema_version": <credential schema version>,
    "issuer_did": <credential issuer did>,
    "cred_def_id": <credential definition id>,
    // for every attribute in <credential values>
    "attr::<attribute name>::marker": "1", // to check existence of attribute
    "attr::<attribute name>::value": <attribute raw value>, // to check value of attribute
}
  • indy_prover_get_credential was added to Libindy Anoncreds API to allow getting human readable credential by the given id.
  • Updated indy_prover_get_credentials and indy_prover_get_credentials_for_proof_req API functions to support tags based search.
  • indy_prover_get_credentials endpoint marked as DEPRECATED and will be removed in the next release because immediately returns all fetched credentials.
  • indy_prover_get_credentials_for_proof_req endpoint marked as DEPRECATED and will be removed in the next release because immediately returns all fetched credentials.
  • Added two chains of APIs related to credentials search that allows fetching records by batches:
    • Simple credentials search - indy_prover_search_credentials -> indy_prover_fetch_credentials -> indy_prover_close_credentials_search
    • Search credentials for proof request - indy_prover_search_credentials_for_proof_req -> indy_prover_fetch_credentials_for_proof_req -> indy_prover_close_credentials_search_for_proof_req

Note:

  • Functions indy_prover_get_credentials and indy_prover_get_credentials_for_proof_req support both formats of queries: old strict filter and WQL.
  • Functions indy_prover_search_credentials and indy_prover_search_credentials_for_proof_req support only WQL format of queries.

References:

v1.5.0 - Anoncreds API v1.6.0 - Anoncreds API
Gets human readable credential by the given id
NEW
indy_prover_get_credential(
            command_handle: i32,
            wallet_handle: i32,
            cred_id: *const c_char,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode,
                   credential_json: *const c_char))
        
Gets human readable credentials according to the filter
indy_prover_get_credentials(
        command_handle: i32,
        wallet_handle: i32,
        filter_json: *const c_char,
        cb: fn(xcommand_handle: i32, 
               err: ErrorCode,
               credentials_json: *const c_char))
        
DEPRECATED
Search for credentials stored in wallet
NEW
indy_prover_search_credentials(
            command_handle: i32,
            wallet_handle: i32,
            query_json: *const c_char,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode,
                   search_handle: i32,
                   total_count: usize))
        
Fetch next credentials for search
NEW
indy_prover_fetch_credentials(
            command_handle: i32,
            search_handle: i32,
            count: usize,
            cb: fn(command_handle_: i32, 
                   err: ErrorCode,
                   credentials_json: *const c_char))
        
Close credentials search
NEW
indy_prover_close_credentials_search(
            command_handle: i32,
            search_handle: i32,
            cb: fn(command_handle_: i32, 
                   err: ErrorCode))
        
Gets human readable credentials matching the given proof request
indy_prover_get_credentials_for_proof_req(
        command_handle: i32,
        wallet_handle: i32,
        proof_request_json: *const c_char,
        cb: fn(xcommand_handle: i32, 
               err: ErrorCode,
               credentials_json: *const c_char))
        
DEPRECATED
Search for credentials matching the given proof request.
NEW
indy_prover_search_credentials_for_proof_req(
            command_handle: i32,
            wallet_handle: i32,
            proof_request_json: *const c_char,
            extra_query_json: *const c_char,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode,
                   search_handle: i32))
      NOTE: Added parameter extra_query_json.
      Using this parameter you can pass an additional 
      query to any attribute/predicate in a proof request.
      
Fetch next credentials for the requested item using proof request search handle
NEW
indy_prover_fetch_credentials_for_proof_req(
            command_handle: i32,
            search_handle: i32,
            item_referent: *const c_char,
            count: usize,
            cb: fn(command_handle_: i32, 
                   err: ErrorCode,
                   credentials_json: *const c_char))
      
Close credentials search for proof request
NEW
indy_prover_close_credentials_search_for_proof_req(
            command_handle: i32,
            search_handle: i32,
            cb: fn(command_handle_: i32, 
                   err: ErrorCode))
      
Payments API

The main idea of changes performed in Payment API is avoiding UTXO based payments approach. Payment API has been updated to support non-UTXO based crypto payments and traditional payments like VISA.

Note: Removed EXPERIMENTAL notice for API endpoints.

The following changes have been performed:

  • Changed format of input and output parameters.
  • Changed format of result values of indy_parse_response_with_fees and indy_parse_payment_response API functions.
  • Renamed indy_build_get_utxo_request and indy_parse_get_utxo_response API functions.
  • Added indy_build_verify_payment_req and indy_parse_verify_payment_response API functions.
v1.5.0 - Payment API v1.6.0 - Payment API
Modifies Indy request by adding information how to pay fees for this transaction according to selected payment method
indy_add_request_fees(
        command_handle: i32,
        wallet_handle: i32,
        submitter_did: *const c_char,
        req_json: *const c_char,
        inputs_json: *const c_char,
        outputs_json: *const c_char,
        cb: fn(command_handle_: i32,
               err: ErrorCode,
               req_with_fees_json: *const c_char,
               payment_method: *const c_char))
      
Left the same but the format of outputs has been 
changed to:
[{
  recipient: , // payment address of recipient
  amount: , // amount
  extra: , // optional data
}]
      
Parses response for Indy request with fees
indy_parse_response_with_fees(
        command_handle: i32,
        payment_method: *const c_char,
        resp_json: *const c_char,
        cb: fn(command_handle_: i32,
               err: ErrorCode,
               utxo_json: *const c_char))
      
indy_parse_response_with_fees(
        command_handle: i32,
        payment_method: *const c_char,
        resp_json: *const c_char,
        cb: fn(command_handle_: i32,
               err: ErrorCode,
               receipts_json: *const c_char))
NOTE: Format of result value has been changed to:
[{
   receipt: , // receipt that can be used for 
             payment referencing and verification
   recipient: , //payment address of recipient
   amount: , // amount
   extra: , // optional data from payment transaction
}]
      
Builds Indy request for getting sources list for payment address according to this payment method
indy_build_get_utxo_request(
        command_handle: i32,
        wallet_handle: i32,
        submitter_did: *const c_char,
        payment_address: *const c_char,
        cb: fn(command_handle_: i32,
               err: ErrorCode,
               get_utxo_txn_json: *const c_char,
               payment_method: *const c_char))
      
indy_build_get_sources_request(
        command_handle: i32,
        wallet_handle: i32,
        submitter_did: *const c_char,
        payment_address: *const c_char,
        cb: fn(command_handle_: i32,
               err: ErrorCode,
               get_sources_txn_json: *const c_char,
               payment_method: *const c_char))
    
NOTE: Function and result value have been renamed.
Parses response for Indy request for getting sources list
indy_parse_get_utxo_response(
        command_handle: i32,
        payment_method: *const c_char,
        resp_json: *const c_char,
        cb: fn(command_handle_: i32,
               err: ErrorCode,
               utxo_json: *const c_char))
      
indy_parse_get_sources_response(
        command_handle: i32,
        payment_method: *const c_char,
        resp_json: *const c_char,
        cb: fn(command_handle_: i32,
               err: ErrorCode,
               sources_json: *const c_char))
NOTE: Function and result value have been renamed.
NOTE: sources have the following format:
[{
   source: , // source input
   paymentAddress: , //payment address for this source
   amount: , // amount
   extra: , // optional data from payment transaction
}]
      
Builds Indy request for doing payment according to this payment method
indy_build_payment_req(
        command_handle: i32,
        wallet_handle: i32,
        submitter_did: *const c_char,
        inputs_json: *const c_char,
        outputs_json: *const c_char,
        cb: fn(command_handle_: i32,
               err: ErrorCode,
               payment_req_json: *const c_char,
               payment_method: *const c_char))
      
Left the same but the format of outputs has been 
changed to:
[{
  recipient: , // payment address of recipient
  amount: , // amount
  extra: , // optional data
}]
      
Builds Indy request for doing payment according to this payment method
indy_parse_payment_response(
            command_handle: i32,
            payment_method: *const c_char,
            resp_json: *const c_char,
            cb: fn(command_handle_: i32,
                   err: ErrorCode,
                   utxo_json: *const c_char))
      
indy_parse_payment_response(
            command_handle: i32,
            payment_method: *const c_char,
            resp_json: *const c_char,
            cb: fn(command_handle_: i32,
                   err: ErrorCode,
                   receipts_json: *const c_char))
NOTE: Format of result value has been changed to:
[{
   receipt: , // receipt that can be used for 
             payment referencing and verification
   recipient: , //payment address of recipient
   amount: , // amount
   extra: , // optional data from payment transaction
}]
      
Builds Indy request for doing tokens minting according to this payment method
indy_build_mint_req(
        command_handle: i32,
        wallet_handle: i32,
        submitter_did: *const c_char,
        outputs_json: *const c_char,
        cb: fn(command_handle_: i32,
               err: ErrorCode,
               mint_req_json: *const c_char,
               payment_method: *const c_char))
      
Left the same but the format of outputs has been 
changed to:
[{
  recipient: , // payment address of recipient
  amount: , // amount
  extra: , // optional data
}]
      
Builds Indy request for information to verify the payment receipt
NEW
indy_build_verify_payment_req(
        command_handle: i32,
        wallet_handle: i32,
        submitter_did: *const c_char,
        receipt: *const c_char,
        cb: fn(command_handle_: i32,
               err: ErrorCode,
               verify_txn_json: *const c_char,
               payment_method: *const c_char))
      
Parses Indy response with information to verify receipt
NEW
indy_parse_verify_payment_response(
        command_handle: i32,
        payment_method: *const c_char,
        resp_json: *const c_char,
        cb: fn(command_handle_: i32,
               err: ErrorCode,
               txn_json: *const c_char))
      
Pool API
v1.5.0 - Pool API v1.6.0 - Pool API
Opens pool ledger and performs connecting to pool nodes
indy_open_pool_ledger(command_handle: i32,
                      config_name: *const c_char,
                      config: *const c_char,
                      cb: fn(xcommand_handle: i32,
                           err: ErrorCode,
                           pool_handle: i32))
      
Left the same but the format of config has been changed to:
{
   "timeout": int (optional), timeout for network request (in sec).
   "extended_timeout": int (optional), extended timeout for network request (in sec).
   "preordered_nodes": array - (optional), names of nodes which will 
       have a priority during request sending:
       ["name_of_1st_prior_node",  "name_of_2nd_prior_node", .... ]
       Note: Not specified nodes will be placed in a random way.
}
      

Libindy 1.6.0 to 1.6.1 migration Guide

The Libindy 1.6.1 release contains fixes that don’t affect API functions. Most of them relate to pool connection performance.

Libindy 1.6.1 to 1.6.2 migration Guide

Wallet API 1.6.2

Updated wallet credentials to accept the additional parameter key_derivation_method.

This parameter provides the ability to use different crypto algorithms for wallet master key derivation.

Wallet credentials json has the following format:

 {
   "key": string, Passphrase used to derive wallet master key
   "storage_credentials": optional<object> Credentials for wallet storage. Storage type defines set of supported keys.
                          Can be optional if storage supports default configuration.
                          For 'default' storage type should be empty.
   "key_derivation_method": optional<string> algorithm to use for master key derivation:
                          ARGON2I_MOD (used by default)
                          ARGON2I_INT - less secured but faster 
 }
v1.6.1 - Wallet API v1.6.2 - Wallet API
Create a new secure wallet
indy_create_wallet(command_handle: i32,
                   config: *const c_char,
                   credentials: *const c_char,
                   cb: fn(xcommand_handle: i32,
                          err: ErrorCode))
      
Note: Left the same but format of credentials parameter was changed. The current format is described above.
Open the wallet
indy_open_wallet(command_handle: i32,
                 config: *const c_char,
                 credentials: *const c_char,
                 cb: fn(xcommand_handle: i32,
                                      err: ErrorCode,
                                      handle: i32))
      
Note: Left the same but format of credentials parameter was changed. The current format is described above.
Deletes created wallet
indy_delete_wallet(command_handle: i32,
                   config: *const c_char,
                   credentials: *const c_char,
                   cb: fn(xcommand_handle: i32, 
                          err: ErrorCode))
      
Note: Left the same but format of credentials parameter was changed. The current format is described above.
Exports opened wallet
indy_export_wallet(
                command_handle: i32,
                wallet_handle: i32,
                export_config: *const c_char,
                cb: fn(xcommand_handle: i32,
                       err: ErrorCode))
        
Note: Format of export_config parameter was changed to.
{
 "path": , Path of the file that contains exported wallet content
 "key": , Key or passphrase used for wallet export key derivation.
 "key_derivation_method": optional Algorithm to use for wallet export key derivation:
                          ARGON2I_MOD - derive secured export key (used by default)
                          ARGON2I_INT - derive secured export key (less secured but faster)
}
      
Import wallet
indy_import_wallet(
            command_handle: i32,
            config: *const c_char,
            credentials: *const c_char,
            import_config_json: *const c_char,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode)
        
Note: Left the same but format of credentials parameter was changed. The current format is described above.
Ledger API 1.6.2
v1.6.1 - Ledger API v1.6.2 - Ledger API
Send action to particular nodes of validator pool
NEW
indy_submit_action(
               command_handle: i32,
               pool_handle: i32,
               request_json: *const c_char,
               nodes: *const c_char,
               timeout: i32,
               cb: fn(xcommand_handle: i32,
                      err: ErrorCode,
                      request_result_json: *const c_char))
      
Builds a POOL_UPGRADE request
indy_build_pool_upgrade_request(
            command_handle: i32,
            submitter_did: *const c_char,
            name: *const c_char,
            version: *const c_char,
            action: *const c_char,
            sha256: *const c_char,
            timeout: i32,
            schedule: *const c_char,
            justification: *const c_char,
            reinstall: bool,
            force: bool,
            cb: fn(xcommand_handle: i32,
                   err: ErrorCode,
                   request_json: *const c_char))
      
indy_build_pool_upgrade_request(
                command_handle: i32,
                submitter_did: *const c_char,
                name: *const c_char,
                version: *const c_char,
                action: *const c_char,
                sha256: *const c_char,
                timeout: i32,
                schedule: *const c_char,
                justification: *const c_char,
                reinstall: bool,
                force: bool,
                package: *const c_char,
                cb: fn(xcommand_handle: i32,
                       err: ErrorCode,
                       request_json: *const c_char))
      
Note: Added package parameter that allows to specify package to be upgraded

Libindy 1.6.2 to 1.6.3 migration Guide

Wallet API 1.6.3
  • Added separate API function indy_generate_wallet_key to generate a random wallet master key.
  • Updated key_derivation_method parameter of wallet credentials to accept the addition type - RAW. By using this type, the result of indy_generate_wallet_key can be passed as a wallet master key (key derivation will be skipped).
  • Fixed typo in the naming of key derivation methods - ARGON instead of ARAGON.

Wallet credentials json has the following format:

 {
   "key": string, Key or passphrase used for wallet key derivation.
                  Look to key_derivation_method param for information about supported key derivation methods.
   "storage_credentials": optional<object> Credentials for wallet storage. Storage type defines set of supported keys.
                          Can be optional if storage supports default configuration.
                          For 'default' storage type should be empty.
   "key_derivation_method": optional<string> Algorithm to use for wallet key derivation:
                          ARGON2I_MOD - derive secured wallet master key (used by default)
                          ARGON2I_INT - derive secured wallet master key (less secured but faster)
                          RAW - raw wallet key master provided (skip derivation).
                                RAW keys can be generated with indy_generate_wallet_key call 
 }
v1.6.2 - Wallet API v1.6.3 - Wallet API
Generate wallet master key.
NEW
indy_generate_wallet_key(command_handle: i32,
                         config: *const c_char,
                         cb: fn(xcommand_handle: i32,
                                err: ErrorCode,
                                key: *const c_char))
        
Create a new secure wallet
indy_create_wallet(command_handle: i32,
                   config: *const c_char,
                   credentials: *const c_char,
                   cb: fn(xcommand_handle: i32,
                          err: ErrorCode))
      
Note: Left the same but format of credentials parameter was changed. The current format is described above.
Open the wallet
indy_open_wallet(command_handle: i32,
                 config: *const c_char,
                 credentials: *const c_char,
                 cb: fn(xcommand_handle: i32,
                                      err: ErrorCode,
                                      handle: i32))
      
Note: Left the same but format of credentials parameter was changed. The current format is described above.
Deletes created wallet
indy_delete_wallet(command_handle: i32,
                   config: *const c_char,
                   credentials: *const c_char,
                   cb: fn(xcommand_handle: i32, 
                          err: ErrorCode))
      
Note: Left the same but format of credentials parameter was changed. The current format is described above.
Exports opened wallet
indy_export_wallet(
                command_handle: i32,
                wallet_handle: i32,
                export_config: *const c_char,
                cb: fn(xcommand_handle: i32,
                       err: ErrorCode))
        
Note: Format of export_config parameter was changed to.
{
 "path": , Path of the file that contains exported wallet content
 "key": , Key or passphrase used for wallet export key derivation.
 "key_derivation_method": optional Algorithm to use for wallet export key derivation:
                          ARGON2I_MOD - derive secured export key (used by default)
                          ARGON2I_INT - derive secured export key (less secured but faster)
                          RAW - raw wallet key master provided (skip derivation).
}
      
Import wallet
indy_import_wallet(
            command_handle: i32,
            config: *const c_char,
            credentials: *const c_char,
            import_config_json: *const c_char,
            cb: fn(xcommand_handle: i32, 
                   err: ErrorCode)
        
Note: Left the same but format of credentials parameter was changed. The current format is described above.
Ledger API 1.6.3
v1.6.2 - Ledger API v1.6.3 - Ledger API
Builds a NODE request
indy_build_node_request(
            command_handle: i32,
            submitter_did: *const c_char,
            target_did: *const c_char,
            data: *const c_char,
            cb: fn(xcommand_handle: i32,
                   err: ErrorCode,
                   request_json: *const c_char))
      
Left the same but the additional optional field blskey_pop has been added in data json.

Libindy 1.6.3 to 1.6.4 migration Guide

The Libindy 1.6.4 release contains fixes that don’t affect API functions.

Libindy 1.6.4 to 1.6.5 migration Guide

The Libindy 1.6.5 release contains changes that related with Ledger API and Payment API but doesn’t break them. Parameter submitter_did set as the optional field for:

  • Ledger API indy_build_get_* functions (except indy_build_get_validator_info_request).
  • all functions in Payment API.

Libindy 1.6.5 to 1.6.6 migration Guide

The Libindy 1.6.6 release contains fixes that don’t affect API functions.

Libindy 1.6.6 to 1.6.7 migration Guide

  • Supported hexadecimal seed.
  • Removed TGB role from indy_build_nym_request.

Libindy 1.6 to 1.7 migration Guide

This document is written for developers using Libindy to provide necessary information and to simplify their transition to Libindy 1.7 from Libindy 1.6. If you are using older Libindy version you can check migration guides history:

Notes

Migration information is organized in tables, there are mappings for each Libindy API part of how older version functionality maps to a newer one. Functions from older version are listed in the left column, and the equivalent newer version function is placed in the right column:

  • If some function had been added, the word ‘NEW’ would be placed in the left column.
  • If some function had been deleted, the word ‘DELETED’ would be placed in the right column.
  • If some function had been deprecated, the word ‘DEPRECATED’ would be placed in the right column.
  • If some function had been changed, the current format would be placed in the right column.
  • If some function had not been changed, the symbol ‘=’ would be placed in the right column.
  • To get more details about current format of a function click on the description above it.
  • Bellow are signatures of functions in Libindy C API. The params of cb (except command_handle and err) will be result values of the similar function in any Libindy wrapper.

Libindy 1.6 to 1.7.0 migration Guide

Logger API

The main purpose of this API is to forward logs of libindy and wrappers to its consumers. It is needed if you consume libindy as a .so or .dll - so you can forward logs from libindy to your logging framework. You don’t need this endpoints if you use libindy through the wrapper – in Java, Rust and Python wrappers they are already forwarded to slf4j for Java, log crate for Rust and default logging facade for python.

v1.6.0 - Logger API v1.7.0 - Logger API
Set custom logger implementation
NEW
              indy_set_logger(context: *const c_void,
                              enabled: Option bool>,
                              log: Option,
                              flush: Option) -> ErrorCode
          
Set default logger implementation.
NEW
              indy_set_default_logger(pattern: *const c_char) -> ErrorCode
          
Get the currently used logger.
NEW
              indy_get_logger(context_p: *mut *const c_void,
                              enabled_cb_p: *mut Option bool>,
                              log_cb_p: *mut Option,
                              flush_cb_p: *mut Option)
          
Libindy API

The main purpose of this API is to set Liibndy configuration.

v1.6.0 - Libindy API v1.7.0 - Libindy API
Set libindy runtime configuration.
NEW
indy_set_runtime_config(config: *const c_char) -> ErrorCode

Libindy 1.7 to 1.8 migration Guide

This document is written for developers using Libindy to provide necessary information and to simplify their transition to Libindy 1.8 from Libindy 1.7. If you are using older Libindy version you can check migration guides history:

Notes

Migration information is organized in tables, there are mappings for each Libindy API part of how older version functionality maps to a newer one. Functions from older version are listed in the left column, and the equivalent newer version function is placed in the right column:

  • If some function had been added, the word ‘NEW’ would be placed in the left column.
  • If some function had been deleted, the word ‘DELETED’ would be placed in the right column.
  • If some function had been deprecated, the word ‘DEPRECATED’ would be placed in the right column.
  • If some function had been changed, the current format would be placed in the right column.
  • If some function had not been changed, the symbol ‘=’ would be placed in the right column.
  • To get more details about current format of a function click on the description above it.
  • Bellow are signatures of functions in Libindy C API. The params of cb (except command_handle and err) will be result values of the similar function in any Libindy wrapper.

Libindy 1.7 to 1.8.0 migration Guide

Libindy API

The main purpose of this changes is providing a way of getting additional error information like message and backtrace.

Changes
  • Migrated Libindy to failure crate for better handling and error chaining.
  • Added synchronous indy_get_current_error API function that returns details for last occurred error.
  • Updated Libindy wrappers for automatic getting error details:
    • Python - added message and indy_backtrace fields to IndyError object.
    • Java - added sdkBacktrace field to IndyException. Libindy error message set as the main for IndyException.
    • NodeJS - added indyMessage and indyBacktrace fields to IndyError object.
    • Rust - changed type of returning value from enum ErrorCode on structure IndyError with error_code, message, indy_backtrace fields.
    • Objective-C - added message and indy_backtrace fields to userInfo dictionary in NSError object.
  • Updated Indy-Cli to show Libindy error message in some cases.
v1.7.0 - Libindy API v1.8.0 - Libindy API
Get details for last occurred error.
NEW
indy_get_current_error(
            error_json_p: *mut *const c_char)
Set libindy runtime configuration
indy_set_runtime_config(
        config: *const c_char) -> ErrorCode
Note: Format of config parameter was changed. Current format is:
{
    "crypto_thread_pool_size": Optional[int] - 
        size of thread pool 
    "collect_backtrace": Optional<[bool] - 
        whether errors backtrace should be collected
}
Crypto API

The main purpose of changes is the support of Wire Messages described in AMES HIPE

Note: New functions are Experimental.

v1.7.0 - Crypto API v1.8.0 - Crypto API
Packs a message by encrypting the message and serializes it in a JWE-like format
NEW
indy_pack_message(command_handle: i32,
                 wallet_handle: i32,
                 message: *const u8,
                 message_len: u32,
                 receiver_keys: *const c_char,
                 sender: *const c_char,
                 cb: Option)
          
Unpacks a JWE-like formatted message outputted by indy_pack_message
NEW
indy_unpack_message(command_handle: i32,
                    wallet_handle: i32,
                    jwe_data: *const u8,
                    jwe_len: u32,
                    cb: Option)
          
Encrypts a message by anonymous-encryption scheme
indy_crypto_anon_crypt(
            command_handle: i32,
            recipient_vk: *const c_char,
            msg_data: *const u8,
            msg_len: u32,
            cb: Option)
          
DEPRECATED
Use `indy_pack_message` instead
Decrypt a message by authenticated-encryption scheme
indy_crypto_auth_decrypt(
            command_handle: i32,
             wallet_handle: i32,
             recipient_vk: *const c_char,
             encrypted_msg: *const u8,
             encrypted_len: u32,
             cb: Option)
          
DEPRECATED
Use `indy_unpack_message` instead
Ledger API
  • Added NETWORK_MONITOR to list of acceptable values for role parameter in indy_build_nym_request API function.
  • Implemented automatic filtering of outdated responses based on comparison of local time with latest transaction ordering time.

Libindy 1.8.1 to 1.8.2 migration Guide

v1.8.1 - Ledger API v1.8.2 - Ledger API
Builds a AUTH_RULE request to change authentication rules for a ledger transaction
NEW
indy_build_auth_rule_request(command_handle: CommandHandle,
                             submitter_did: *const c_char,
                             txn_type: *const c_char,
                             action: *const c_char,
                             field: *const c_char,
                             old_value: *const c_char,
                             new_value: *const c_char,
                             constraint: *const c_char,
                             cb: fn(command_handle_: CommandHandle,
                                    err: ErrorCode,
                                    request_json: *const c_char))
      
Builds a GET_AUTH_RULE request to get authentication rules for ledger
NEW
indy_build_get_auth_rule_request(command_handle: CommandHandle,
                                 submitter_did: *const c_char,
                                 txn_type: *const c_char,
                                 action: *const c_char,
                                 field: *const c_char,
                                 old_value: *const c_char,
                                 new_value: *const c_char,
                                 cb: fn(command_handle_: CommandHandle,
                                        err: ErrorCode,
                                        request_json: *const c_char))
      

Libindy 1.8.2 to 1.8.3 migration Guide

Updated behavior of indy_build_auth_rule_request and indy_build_get_auth_rule_request API functions:

  • new_value can be empty string for ADD action.
  • new_value can be null for EDIT action.
  • old_value is skipped during transaction serialization for ADD action.

SDK Architecture

Right now we have some rendered svg diagrams on github at https://github.com/hyperledger/indy-sdk/tree/master/docs/architecture/

To be continued…

什麼是 Indy 和 Libindy,還有為什麼它們那麼重要?

Indy 提供一個私密、安全、身份的生態系統,而Libindy 為它提供客戶端。Indy 使人 – 而不是傳統機構 – 控制他們的個人資料以及如何公開。它令各種創新變得可能:授權、嶄新的支付流程、資產及文件管理、不同形式的委託、聲譽累積、與其他新技術整合等等。

Indy使用開源的分佈賬戶技術。這個賬戶是由一群參與者合作建立的一種數據庫,而非一個中央管理的大規模數據庫。數據冗餘地存在於多個地方,而由多部參與的電腦(機器)的交易所構成,透過大而有力的加密標準加以保護。它的設計充分使用密鑰管理和網絡安全的最佳實踐模式。所得的結果是一個可靠、公共的信任源頭,不受單一個體所控制,系統堅實而不受人侵駭入,足以抵禦其他個體敵意的破壞和顛覆。

如果你對加密學概念和區塊鏈技術還有疑惑,不用害怕,這指南會給你介紹Indy的主要概念,你來對地方了。

我们要讨论什么?

我们的目标是向你介绍很多关于 Indy 的概念,帮助你来理解让这一起工作起来的背后的原因。

我们会将整个过程编为一个故事。Alice,一个虚构的 Faber 大学的毕业生,想要应聘一家虚构的公司 Acme Corp 的一份工作。当她获得了这份工作后,她想要向 Thrift 银行申请一笔贷款,这样她就可以购买一辆汽车了。在工作申请表单上,她想用她的大学成绩单作为受过教育证明,并且一旦被录用后,Alice 想使用被雇佣的事实来作为申请贷款的信誉凭证。

在当今的世界里,身份信息以及信任交互非常混乱,它们很慢,与隐私性相违背,容易受到欺诈。我们将会展示给你 Indy 是如何让这些产生了巨大的进步。

作为 Faber 大学的毕业生,Alice 收到了一封毕业生的 newsletter,从中了解到她的母校可以提供数字成绩单(digital transcripts)。她登录了学校的毕业生网站,通过点击 获得成绩单 按钮,她申请了自己的成绩单。(其他的发起这个请求的方式还可能包括扫描一个 QR code,从一个公共的 URL 下载一份打包的成绩单,等等)

About Alice

Alice 还没有意识到,想要使用这个数字的成绩单,她需要一个新的类型的身份信息 - 并不是 Faber 大学存储在在校(on-campus)数据库中为她创建的传统的身份信息,而是一个属于她自己的全新的便携的身份信息,独立于所有的过去和将来的关系,不经过她的允许,没有人能够废除(revoke)、指派(co-opt)或者关联(correlate)这个身份信息。这就是一个 自我主权的身份信息(self-sovereign identity),也是 Indy 的核心功能。

在常规的情况下,管理一个自我主权的身份信息会要求使用 一个工具,比如一个桌面的或者手机的应用程序。它可能是一个独立的应用,或者使用一个第三方的服务机构(代理商)提供的账本服务。Sovrin 基金会(Sovrin Foundation)发布了一个这种类型的工具。Faber 大学了解了这些需求,并会建议 Alice 安装一个 Indy app 如果她没有安装过的话。这个 app 会作为点击 获得成绩单 按钮之后的工作流中的一部分而被安装。

在常规的情况下,管理一个自我主权的身份信息会要求使用 一个工具,比如一个桌面的或者手机的应用程序。它可能是一个独立的应用,或者使用一个第三方的服务机构(代理商)提供的账本服务。Sovrin 基金会(Sovrin Foundation)发布了一个这种类型的工具。Faber 大学了解了这些需求,并会建议 Alice 安装一个 Indy app 如果她没有安装过的话。这个 app 会作为点击 获得成绩单 按钮之后的工作流中的一部分而被安装。

当 Alice 点击了 获得成绩单 按钮后,她会下载一个带有一个 Indy 连接请求 的文件。这个连接请求文件的扩展名为 .indy,并且会和 Alice 的 Indy app 相关联,将会允许 Alice 创建跟另外一个在这个账本生态圈(ledger ecosystem)存在的一方(Faber 大学)的一个安全的信息沟通频道(channel)。

Design Documents

Here are design documents to describe core concepts of the Indy SDK:

Indy CLI Design

Re-implementation of CLI

This proposal follow the idea to re-implement CLI from scratch. Main reasons are:

  • Existing code base is written in hard-to support way.
  • Existing code base is too far from libindy entities model.
  • Existing code base requires complex runtime (python) and additional dependencies (python libindy wrapper) that complicates deployment.
  • It is just cheaper to re-implement CLI than to perform deep refactoring.

Use Rust language

We propose to re-implement CLI in Rust. Main reasons are:

  • Main libindy code base uses Rust. Team has deep Rust experience.
  • No need to big runtime, small list of dependencies. As result simple packaging and deployment.
  • Rust is nice and reliable cross-platform solution for native apps.

Terminal input-output handling

To handle complex terminal input, history and autocompletion support on different terminals linefeed crate will be used (few additional alternatives are also available). To handle colored terminal output ansi_term crate will be used.

Auto completion

The following autocompletion will be provided through readline infrastructure:

  • Command group name completion
  • Command name completion
  • Command param name completion

Libindy wrapper

CLI project will contain simple synchronous libindy wrapper:

  • Code from libindy tests that provides similar wrapper will be partially reused.
  • Synchronization will be performed through Rust channels:
    • Main thread creates channel and closure that will send message to this channel.
    • Calls libindy and puts this closure as callback.
    • Blocks on reading from channel.

As channel reading assumes timeouts it will be possible to emulate progress updating .

Threading model

There will be one main thread that will perform io operations with terminal and libindy calls synchronized through Rust’s channel. Blocking will be limited by small channel read timeout.

Execution modes

CLI will support 2 execution modes:

  • Interactive. In this mode CLI will read commands from terminal interactively.
  • Batch. In this code all commands will be read from file or pipe and executed in series.

Code structure

  • CLI code will define “Command” structure that will be container for:
    • Command meta information (name, command help, supported params, params help)
    • “Executor” function that will contain command execution logic
    • “Cleaner” function that will contain command cleanup logic
  • Each command will be implemented as Rust module with one public “new” function that returns configured “Command” instance
  • All commands will share one “CommandContext”. “CommandContext” will hold application state and contain 2 parts:
    • Pre-defined application part. Part that holds application-level state like command prompt, isExit flag and etc…
    • Generic command specific part. This part will be key-value storage that will allow commands to store command-specific data like Indy SDK handles, used DID and etc…
  • “Executor” and “Cleaner” functions will get CommandContext as parameter
  • Actual execution of commands will be performed by CommandExecutor class. This class will:
    • Instantiation of shared “CommandContext”
    • Hold all commands and command grouping info
    • Parse command lines according to commands meta information and search for relevant command
    • Triggering of command execution
    • Provide line auto completion logic
    • Triggerid of command cleanup Command instances will optionally share few contexts:
  • EntryPoint will:
  • Instantiate CommandExecutor and provide commands to command executor instance.
  • Determine execution mode.
    • In interactive mode it will start readline cycle and execute each line with CommandExecutor until Exit command received.
    • In batch mode it will execute each command in the list and finish execution after completion of all commands.

See diagram:

Commands

Command format

indy> [<group>] <command> [[<main_param_name>=]<main_param_value>] [<param_name1>=<param_value1>] ... [<param_nameN>=<param_valueN>]
Common commands
Help

Print list of groups with group help:

indy> help

Print list of group commands with command help:

indy> <group> help

Print command help, list of command param and help for each param:

indy> <group> <command> help
About

Print about and license info:

indy> about
Exit

Exit from CLI:

indy> exit
Prompt

Change command prompt:

indy> prompt <new_prompt>
Show

Print content of file:

indy> show [<file_path>
Load Plugin

Load plugin in Libindy:

indy> load-plugin library=<name/path> initializer=<init_function>
Wallets management commands (wallet group)
indy> wallet <command>
Wallet create

Create new wallet and attach to Indy CLI:

indy> wallet create <wallet name> key [key_derivation_method=<key_derivation_method>] [storage_type=<storage_type>] [storage_config={config json}]

TODO: Think about custom wallet types support. Now we force default wallet security model..

Wallet attach

Attach existing wallet to Indy CLI:

indy> wallet attach <wallet name> [storage_type=<storage_type>] [storage_config={config json}]
Wallet open

Open the wallet with specified name and make it available for commands that require wallet. If there was opened wallet it will be closed:

indy> wallet open <wallet name> key [key_derivation_method=<key_derivation_method>] [rekey] [rekey_derivation_method=<rekey_derivation_method>]
Wallet close

Close the opened wallet

indy> wallet close
Wallet delete

Delete the wallet

indy> wallet delete <wallet name> key [key_derivation_method=<key_derivation_method>]
Wallet detach

Detach wallet from Indy CLI

indy> wallet detach <wallet name>
Wallet list

List all attached wallets with corresponded status (indicates opened one):

indy> wallet list
Export wallet

Exports opened wallet to the specified file.

indy> wallet export export_path=<path-to-file> export_key=[<export key>] [export_key_derivation_method=<export_key_derivation_method>]
Import wallet

Create new wallet and then import content from the specified file.

indy> wallet import <wallet name> key=<key> [key_derivation_method=<key_derivation_method>] export_path=<path-to-file> export_key=<key used for export>  [storage_type=<storage_type>] [storage_config={config json}]
Pool management commands
indy> pool <subcommand>
Create config

Create name pool (network) configuration

indy> pool create [name=]<pool name> gen_txn_file=<gen txn file path> 
Connect

Connect to Indy nodes pool and make it available for operation that require pool access. If there was pool connection it will be disconnected.

indy> pool connect [name=]<pool name> [protocol-version=<version>] [timeout=<timeout>] [extended-timeout=<timeout>] [pre-ordered-nodes=<node names>]
Refresh

Refresh a local copy of a pool ledger and updates pool nodes connections.

indy> pool refresh
Disconnect

Disconnect from Indy nodes pool

indy> pool disconnect
List

List all created pools configurations with status (indicates connected one)

indy> pool list
Identity Management
indy> did <subcommand>
New

Create and store my DID in the opened wallet. Requires opened wallet.

indy> did new [did=<did>] [seed=<UTF-8, base64 or hex string>] [metadata=<metadata string>]
List

List my DIDs stored in the opened wallet as table (did, verkey, metadata). Requires wallet to be opened.:

indy> did list
Use

Use the DID as identity owner for commands that require identity owner:

indy> did use [did=]<did>
Rotate key

Rotate keys for used DID. Sends NYM to the ledger with updated keys. Requires opened wallet and connection to pool:

indy> did rotate-key [seed=<UTF-8, base64 or hex string>] [fees_inputs=<source-1,..,source-n>] [fees_outputs=(<recipient-1>,<amount>),..,(<recipient-n>,<amount>)] [extra=<extra>]
Ledger transactions/messages
indy> ledger <subcommand>
NYM transaction

Send NYM transaction

ledger nym did=<did-value> [verkey=<verkey-value>] [role=<role-value>] [source_payment_address=<source_payment_address-value>] [fee=<fee-value>] [fees_inputs=<source-1,..,source-n>] [fees_outputs=(<recipient-1>,<amount>),..,(<recipient-n>,<amount>)] [extra=<extra>] [sign=<true or false>] [send=<true or false>] [endorser=<endorser did>]
GET_NYM transaction

Send GET_NYM transaction

ledger get-nym did=<did-value> [send=<true or false>]
ATTRIB transaction

Send ATTRIB transaction

ledger attrib did=<did-value> [hash=<hash-value>] [raw=<raw-value>] [enc=<enc-value>] [source_payment_address=<source_payment_address-value>] [fee=<fee-value>] [fees_inputs=<source-1,..,source-n>] [fees_outputs=(<recipient-1>,<amount>),..,(<recipient-n>,<amount>)] [extra=<extra>] [sign=<true or false>]  [send=<true or false>] [endorser=<endorser did>]
GET_ATTRIB transaction

Send GET_ATTRIB transaction

ledger get-attrib did=<did-value> [raw=<raw-value>] [hash=<hash-value>] [enc=<enc-value>] [send=<true or false>]
SCHEMA transaction

Send SCHEMA transaction

ledger schema name=<name-value> version=<version-value> attr_names=<attr_names-value> [source_payment_address=<source_payment_address-value>] [fee=<fee-value>] [fees_inputs=<source-1,..,source-n>] [fees_outputs=(<recipient-1>,<amount>),..,(<recipient-n>,<amount>)] [extra=<extra>] [sign=<true or false>]  [send=<true or false>] [endorser=<endorser did>]
GET_SCHEMA transaction
ledger get-schema did=<did-value> name=<name-value> version=<version-value> [send=<true or false>]
CRED_DEF transaction

Send CRED_DEF transaction

ledger cred-def schema_id=<schema_id-value> signature_type=<signature_type-value> [tag=<tag>] primary=<primary-value> [revocation=<revocation-value>] [source_payment_address=<source_payment_address-value>] [fee=<fee-value>] [fees_inputs=<source-1,..,source-n>] [fees_outputs=(<recipient-1>,<amount>),..,(<recipient-n>,<amount>)] [extra=<extra>] [sign=<true or false>]  [send=<true or false>] [endorser=<endorser did>]
GET_CRED_DEF transaction

Send GET_CRED_DEF transaction

ledger get-cred-def schema_id=<schema_id-value> signature_type=<signature_type-value> origin=<origin-value> [send=<true or false>]
NODE transaction

Send NODE transaction

ledger node target=<target-value> alias=<alias-value> [node_ip=<node_ip-value>] [node_port=<node_port-value>] [client_ip=<client_ip-value>] [client_port=<client_port-value>] [blskey=<blskey-value>] [blskey_pop=<blskey-proof-of-possession>] [services=<services-value>] [sign=<true or false>]  [send=<true or false>]
GET_VALIDATOR_INFO transaction

Send GET_VALIDATOR_INFO transaction to get info from all nodes

ledger get-validator-info [nodes=<node names>] [timeout=<timeout>]
POOL_UPGRADE transaction

Send POOL_UPGRADE transaction

ledger pool-upgrade name=<name> version=<version> action=<start or cancel> sha256=<sha256> [timeout=<timeout>] [schedule=<schedule>] [justification=<justification>] [reinstall=<true or false (default false)>] [force=<true or false (default false)>] [package=<package>] [sign=<true or false>]  [send=<true or false>]
POOL_CONFIG transaction

Send POOL_CONFIG transaction

ledger pool-config writes=<true or false (default false)> [force=<true or false (default false)>] [sign=<true or false>]  [send=<true or false>]
POOL_RESTART transaction

Send POOL_RESTART transaction

ledger pool-restart action=<start or cancel> [datetime=<datetime>] [nodes=<node names>] [timeout=<timeout>]
Custom transaction

Send custom transaction with user defined json body and optional signature

ledger custom [txn=]<txn-json-value> [sign=<true|false>]
AUTH_RULE transaction

Send AUTH_RULE transaction

ledger auth-rule txn_type=<txn type> action=<add or edit> field=<txn field> [old_value=<value>] [new_value=<new_value>] constraint=<{constraint json}> [sign=<true or false>]  [send=<true or false>]
GET_AUTH_RULE transaction

Send GET_AUTH_RULE transaction

ledger get-auth-rule [txn_type=<txn type>] [action=<ADD or EDIT>] [field=<txn field>] [old_value=<value>] [new_value=<new_value>] [send=<true or false>]
GET_PAYMENT_SOURCES transaction

Send GET_PAYMENT_SOURCES transaction

ledger get-payment-sources payment_address=<payment_address> [send=<true or false>]
PAYMENT transaction

Send PAYMENT transaction

ledger payment [source_payment_address=<payment address>] [target_payment_address=<payment address>] [amount=<number>] [fee=<transaction fee amount>] [inputs=<source-1>,..,<source-n>] [outputs=(<recipient-1>,<amount>),..,(<recipient-n>,<amount>)] [extra=<extra>] [sign=<true or false>]  [send=<true or false>]
GET_FEES transaction

Send GET_FEES transaction

ledger get-fees payment_method=<payment_method> [send=<true or false>]
MINT transaction

Prepare MINT transaction

ledger mint-prepare outputs=(<recipient-1>,<amount>),..,(<recipient-n>,<amount>) [extra=<extra>]
SET_FEES transaction

Prepare SET_FEES transaction

ledger set-fees-prepare payment_method=<payment_method> fees=<txn-type-1>:<amount-1>,..,<txn-type-n>:<amount-n>
VERIFY_PAYMENT_RECEIPT transaction

Prepare VERIFY_PAYMENT_RECEIPT transaction

ledger verify-payment-receipt <receipt> [send=<true or false>]
Add multi signature to transaction

Add multi signature by current DID to transaction

ledger sign-multi txn=<txn_json>
Save transaction to a file.

Save stored into CLI context transaction to a file.

ledger save-transaction file=<path to file>
Load transaction from a file.

Read transaction from a file and store it into CLI context.

ledger load-transaction file=<path to file>
TXN_AUTHR_AGRMT transaction.

Request to add a new version of Transaction Author Agreement to the ledger.

ledger ledger txn-author-agreement [text=<agreement content>] [file=<file with agreement>] version=<version> [source_payment_address=<source_payment_address-value>] [fee=<fee-value>] [fees_inputs=<source-1,..,source-n>] [fees_outputs=(<recipient-1>,<amount>),..,(<recipient-n>,<amount>)] [extra=<extra>] [sign=<true or false>]  [send=<true or false>]
SET_TXN_AUTHR_AGRMT_AML transaction.

Request to add new acceptance mechanisms for transaction author agreement.

ledger txn-acceptance-mechanisms [aml=<acceptance mechanisms>] [file=<file with acceptance mechanisms>] version=<version> [context=<some context>] [source_payment_address=<source_payment_address-value>] [fee=<fee-value>] [fees_inputs=<source-1,..,source-n>] [fees_outputs=(<recipient-1>,<amount>),..,(<recipient-n>,<amount>)] [extra=<extra>] [sign=<true or false>]  [send=<true or false>]
Payment Address commands
indy> payment-address <subcommand>
Create

Create the payment address for specified payment method. Requires opened wallet.

payment-address create payment_method=<payment_method> [seed=<seed-value>]
List

Lists all payment addresses. Requires opened wallet.

payment-address list
Sign

Create a proof of payment address control by signing an input and producing a signature.

payment-address sign address=<payment_address> input=<string to sign>
Verify

Verify a proof of payment address control by verifying a signature.

payment-address verify address=<payment_address> input=<signed string> signature=<signature>

Examples

Create pool configuration and connect to pool
indy> pool create sandbox gen_txn_file=/etc/sovrin/sandbox.txn
indy> pool connect sandbox
pool(sandbox):indy> pool list
Create and open wallet
sandbox|indy> wallet create alice_wallet key
sandbox|indy> wallet open alice_wallet key
pool(sandbox):wallet(alice_wallet):indy> wallet list
Create DID in the wallet from seed and use it for the next commands
pool(sandbox):wallet(alice_wallet):indy> did new seed=SEED0000000000000000000000000001 metadata="Alice DID"
pool(sandbox):wallet(alice_wallet):indy> did use MYDID000000000000000000001
pool(sandbox):wallet(alice_wallet):did(MYD...001):indy> did list
Create new DID for BOB
pool(sandbox):wallet(alice_wallet):did(MYD...001):indy> did new metadata="Bob DID"
Post new NYM to the ledger
pool(sandbox):wallet(alice_wallet):did(MYD...001):indy> ledger nym did=MYDID000000000000000000001
Send GET_NYM transaction
pool(sandbox):wallet(alice_wallet):did(MYD...001):indy> ledger get-nym did=MYDID000000000000000000001

Anoncreds Design

Here you can find the requirements and design for Indy SDK Anoncreds workflow (including revocation).

Design Goals

  • Indy SDK and Indy Node should use the same format for public Anoncreds entities (Schema, Credential Definition, Revocation Registry Definition, Revocation Registry Delta)
  • Indy SDK and Indy Node should use the same entities referencing approach
  • It should be possible to integrate additional credential signature and revocation schemas without breaking API changes
  • API should provide flexible and pluggable approach to handle revocation tails files
  • API should provide the way to calculate revocation witness values on cloud agent to avoid downloading of the hole tails file on edge devices

Anoncreds Workflow

API

Issuer
/// Create credential schema entity that describes credential attributes list and allows credentials
/// interoperability.
///
/// Schema is public and intended to be shared with all anoncreds workflow actors usually by publishing SCHEMA transaction
/// to Indy distributed ledger.
///
/// It is IMPORTANT for current version POST Schema in Ledger and after that GET it from Ledger
/// with correct seq_no to save compatibility with Ledger.
/// After that can call indy_issuer_create_and_store_credential_def to build corresponding Credential Definition.
///
/// #Params
/// command_handle: command handle to map callback to user context
/// issuer_did: DID of schema issuer
/// name: a name the schema
/// version: a version of the schema
/// attrs: a list of schema attributes descriptions
/// cb: Callback that takes command result as parameter
///
/// #Returns
/// schema_id: identifier of created schema
/// schema_json: schema as json
///
/// #Errors
/// Common*
/// Anoncreds*
#[no_mangle]
pub extern fn indy_issuer_create_schema(command_handle: i32,
                                        issuer_did: *const c_char,
                                        name: *const c_char,
                                        version: *const c_char,
                                        attrs: *const c_char,
                                        cb: Option<extern fn(xcommand_handle: i32, err: ErrorCode,
                                                             schema_id: *const c_char, schema_json: *const c_char)>) -> ErrorCode
/// Create credential definition entity that encapsulates credentials issuer DID, credential schema, secrets used for signing credentials
/// and secrets used for credentials revocation.
///
/// Credential definition entity contains private and public parts. Private part will be stored in the wallet. Public part
/// will be returned as json intended to be shared with all anoncreds workflow actors usually by publishing CRED_DEF transaction
/// to Indy distributed ledger.
///
/// It is IMPORTANT for current version GET Schema from Ledger with correct seq_no to save compatibility with Ledger.
///
/// #Params
/// wallet_handle: wallet handler (created by open_wallet).
/// command_handle: command handle to map callback to user context.
/// issuer_did: a DID of the issuer signing cred_def transaction to the Ledger
/// schema_json: credential schema as a json
/// tag: allows to distinct between credential definitions for the same issuer and schema
/// signature_type: credential definition type (optional, 'CL' by default) that defines credentials signature and revocation math. Supported types are:
/// - 'CL': Camenisch-Lysyanskaya credential signature type
/// config_json: type-specific configuration of credential definition as json:
/// - 'CL':
///   - support_revocation: whether to request non-revocation credential (optional, default false)
/// cb: Callback that takes command result as parameter.
///
/// #Returns
/// cred_def_id: identifier of created credential definition
/// cred_def_json: public part of created credential definition
///
/// #Errors
/// Common*
/// Wallet*
/// Anoncreds*
#[no_mangle]
pub extern fn indy_issuer_create_and_store_credential_def(command_handle: i32,
                                                          wallet_handle: i32,
                                                          issuer_did: *const c_char,
                                                          schema_json: *const c_char,
                                                          tag: *const c_char,
                                                          signature_type: *const c_char,
                                                          config_json: *const c_char,
                                                          cb: Option<extern fn(xcommand_handle: i32, err: ErrorCode,
                                                                               cred_def_id: *const c_char,
                                                                               cred_def_json: *const c_char)>) -> ErrorCode
/// Create a new revocation registry for the given credential definition as tuple of entities:
/// - Revocation registry definition that encapsulates credentials definition reference, revocation type specific configuration and
///   secrets used for credentials revocation
/// - Revocation registry state that stores the information about revoked entities in a non-disclosing way. The state can be
///   represented as ordered list of revocation registry entries were each entry represents the list of revocation or issuance operations.
///
/// Revocation registry definition entity contains private and public parts. Private part will be stored in the wallet. Public part
/// will be returned as json intended to be shared with all anoncreds workflow actors usually by publishing REVOC_REG_DEF transaction
/// to Indy distributed ledger.
///
/// Revocation registry state is stored on the wallet and also intended to be shared as the ordered list of REVOC_REG_ENTRY transactions.
/// This call initializes the state in the wallet and returns the initial entry.
///
/// Some revocation registry types (for example, 'CL_ACCUM') can require generation of binary blob called tails used to hide information about revoked credentials in public
/// revocation registry and intended to be distributed out of leger (REVOC_REG_DEF transaction will still contain uri and hash of tails).
/// This call requires access to pre-configured blob storage writer instance handle that will allow to write generated tails.
///
/// #Params
/// wallet_handle: wallet handler (created by open_wallet).
/// command_handle: command handle to map callback to user context.
/// issuer_did: a DID of the issuer signing transaction to the Ledger
/// revoc_def_type: revocation registry type (optional, default value depends on credential definition type). Supported types are:
/// - 'CL_ACCUM': Type-3 pairing based accumulator. Default for 'CL' credential definition type
/// tag: allows to distinct between revocation registries for the same issuer and credential definition
/// cred_def_id: id of stored in ledger credential definition
/// config_json: type-specific configuration of revocation registry as json:
/// - 'CL_ACCUM': {
///     "issuance_type": (optional) type of issuance. Currently supported:
///         1) ISSUANCE_BY_DEFAULT: all indices are assumed to be issued and initial accumulator is calculated over all indices;
///            Revocation Registry is updated only during revocation.
///         2) ISSUANCE_ON_DEMAND: nothing is issued initially accumulator is 1 (used by default);
///     "max_cred_num": maximum number of credentials the new registry can process (optional, default 100000)
/// }
/// tails_writer_handle: handle of blob storage to store tails
/// cb: Callback that takes command result as parameter.
///
/// #Returns
/// revoc_reg_id: identifier of created revocation registry definition
/// revoc_reg_def_json: public part of revocation registry definition
/// revoc_reg_entry_json: revocation registry entry that defines initial state of revocation registry
///
/// #Errors
/// Common*
/// Wallet*
/// Anoncreds*
#[no_mangle]
pub extern fn indy_issuer_create_and_store_revoc_reg(command_handle: i32,
                                                     wallet_handle: i32,
                                                     blob_storage_writer_handle: i32,
                                                     cred_def_id:  *const c_char,
                                                     tag: *const c_char,
                                                     revoc_def_type: *const c_char,
                                                     config_json: *const c_char,
                                                     cb: Option<extern fn(xcommand_handle: i32, err: ErrorCode,
                                                                          revoc_reg_def_id: *const c_char,
                                                                          revoc_reg_def_json: *const c_char,
                                                                          revoc_reg_entry_json: *const c_char)>) -> ErrorCode
/// Create credential offer that will be used by Prover for
/// credential request creation. Offer includes nonce and key correctness proof
/// for authentication between protocol steps and integrity checking.
///
/// #Params
/// command_handle: command handle to map callback to user context
/// wallet_handle: wallet handler (created by open_wallet)
/// cred_def_id: id of credential definition stored in the wallet
/// cb: Callback that takes command result as parameter
///
/// #Returns
/// credential offer json:
///     {
///         "schema_id": string,
///         "cred_def_id": string,
///         // Fields below can depend on Cred Def type
///         "nonce": string,
///         "key_correctness_proof" : <key_correctness_proof>
///     }
///
/// #Errors
/// Common*
/// Wallet*
/// Anoncreds*
#[no_mangle]
pub extern fn indy_issuer_create_credential_offer(command_handle: i32,
                                                  wallet_handle: i32,
                                                  cred_def_id: *const c_char,
                                                  cb: Option<extern fn(xcommand_handle: i32, err: ErrorCode,
                                                                       cred_offer_json: *const c_char)>) -> ErrorCode
/// Check Cred Request for the given Cred Offer and issue Credential for the given Cred Request.
///
/// Cred Request must match Cred Offer. The credential definition and revocation registry definition
/// referenced in Cred Offer and Cred Request must be already created and stored into the wallet.
///
/// Information for this credential revocation will be store in the wallet as part of revocation registry under
/// generated cred_revoc_id local for this wallet.
///
/// This call returns revoc registry delta as json file intended to be shared as REVOC_REG_ENTRY transaction.
/// Note that it is possible to accumulate deltas to reduce ledger load.
///
/// #Params
/// wallet_handle: wallet handler (created by open_wallet).
/// command_handle: command handle to map callback to user context.
/// cred_offer_json: a cred offer created by indy_issuer_create_credential_offer
/// cred_req_json: a credential request created by indy_prover_create_credential_req
/// cred_values_json: a credential containing attribute values for each of requested attribute names.
///     Example:
///     {
///      "attr1" : {"raw": "value1", "encoded": "value1_as_int" },
///      "attr2" : {"raw": "value1", "encoded": "value1_as_int" }
///     }
/// rev_reg_id: id of revocation registry stored in the wallet
/// blob_storage_reader_handle: configuration of blob storage reader handle that will allow to read revocation tails
/// cb: Callback that takes command result as parameter.
///
/// #Returns
/// cred_json: Credential json containing signed credential values
///     {
///         "schema_id": string,
///         "cred_def_id": string,
///         "rev_reg_def_id", Optional<string>,
///         "values": <see cred_values_json above>,
///         // Fields below can depend on Cred Def type
///         "signature": <signature>,
///         "signature_correctness_proof": <signature_correctness_proof>
///     }
/// cred_revoc_id: local id for revocation info (Can be used for revocation of this credential)
/// revoc_reg_delta_json: Revocation registry delta json with a newly issued credential
///
/// #Errors
/// Annoncreds*
/// Common*
/// Wallet*
#[no_mangle]
pub extern fn indy_issuer_create_credential(command_handle: i32,
                                            wallet_handle: i32,
                                            cred_offer_json: *const c_char,
                                            cred_req_json: *const c_char,
                                            cred_values_json: *const c_char,
                                            rev_reg_id: *const c_char,
                                            blob_storage_reader_handle: i32,
                                            cb: Option<extern fn(xcommand_handle: i32, err: ErrorCode,
                                                                 cred_revoc_id: *const c_char,
                                                                 revoc_reg_delta_json: *const c_char,
                                                                 cred_json: *const c_char)>) -> ErrorCode
/// Revoke a credential identified by a cred_revoc_id (returned by indy_issuer_create_credential).
///
/// The corresponding credential definition and revocation registry must be already
/// created an stored into the wallet.
///
/// This call returns revoc registry delta as json file intended to be shared as REVOC_REG_ENTRY transaction.
/// Note that it is possible to accumulate deltas to reduce ledger load.
///
/// #Params
/// command_handle: command handle to map callback to user context.
/// wallet_handle: wallet handler (created by open_wallet).
/// blob_storage_reader_cfg_handle: configuration of blob storage reader handle that will allow to read revocation tails
/// rev_reg_id: id of revocation registry stored in wallet
/// cred_revoc_id: local id for revocation info
/// cb: Callback that takes command result as parameter.
///
/// #Returns
/// revoc_reg_delta_json: Revocation registry delta json with a revoked credential
///
/// #Errors
/// Annoncreds*
/// Common*
/// Wallet*
#[no_mangle]
pub extern fn indy_issuer_revoke_cred(command_handle: i32,
                                      wallet_handle: i32,
                                      blob_storage_reader_handle: i32,
                                      cred_revoc_id: *const c_char,
                                      cb: Option<extern fn(xcommand_handle: i32, err: ErrorCode,
                                                           revoc_reg_delta_json: *const c_char)>) -> ErrorCode
/// Merge two revocation registry deltas (returned by indy_issuer_create_credential or indy_issuer_revoke_credential) to accumulate common delta.
/// Send common delta to ledger to reduce the load.
///
/// #Params
/// command_handle: command handle to map callback to user context.
/// rev_reg_delta_json: revocation registry delta.
/// other_rev_reg_delta_json: revocation registry delta for which PrevAccum value  is equal to current accum value of rev_reg_delta_json.
/// cb: Callback that takes command result as parameter.
///
/// #Returns
/// merged_rev_reg_delta: Merged revocation registry delta
///
/// #Errors
/// Annoncreds*
/// Common*
/// Wallet*
#[no_mangle]
pub extern fn indy_issuer_merge_revocation_registry_deltas(command_handle: i32,
                                                           rev_reg_delta_json: *const c_char,
                                                           other_rev_reg_delta_json: *const c_char,
                                                           cb: Option<extern fn(xcommand_handle: i32, err: ErrorCode,
                                                                                merged_rev_reg_delta: *const c_char)>) -> ErrorCode
Prover
/// Creates a master secret with a given id and stores it in the wallet.
/// The id must be unique.
///
/// #Params
/// wallet_handle: wallet handler (created by open_wallet).
/// command_handle: command handle to map callback to user context.
/// master_secret_id: (optional, if not present random one will be generated) new master id
///
/// #Returns
/// out_master_secret_id: Id of generated master secret
///
/// #Errors
/// Annoncreds*
/// Common*
/// Wallet*
#[no_mangle]
pub extern fn indy_prover_create_master_secret(command_handle: i32,
                                               wallet_handle: i32,
                                               master_secret_id: *const c_char,
                                               cb: Option<extern fn(xcommand_handle: i32, err: ErrorCode,
                                                                    out_master_secret_id: *const c_char)>) -> ErrorCode
/// Creates a credential request for the given credential offer.
///
/// The method creates a blinded master secret for a master secret identified by a provided name.
/// The master secret identified by the name must be already stored in the secure wallet (see prover_create_master_secret)
/// The blinded master secret is a part of the credential request.
///
/// #Params
/// command_handle: command handle to map callback to user context
/// wallet_handle: wallet handler (created by open_wallet)
/// prover_did: a DID of the prover
/// cred_offer_json: credential offer as a json containing information about the issuer and a credential
/// cred_def_json: credential definition json related to <cred_def_id> in <cred_offer_json> 
/// master_secret_id: the id of the master secret stored in the wallet
/// cb: Callback that takes command result as parameter.
///
/// #Returns
/// cred_req_json: Credential request json for creation of credential by Issuer
///     {
///      "prover_did" : string,
///      "cred_def_id" : string,
///         // Fields below can depend on Cred Def type
///      "blinded_ms" : <blinded_master_secret>,
///      "blinded_ms_correctness_proof" : <blinded_ms_correctness_proof>,
///      "nonce": string
///    }
/// cred_req_metadata_json: Credential request metadata json for further processing of received form Issuer credential.
///
/// #Errors
/// Annoncreds*
/// Common*
/// Wallet*
#[no_mangle]
pub extern fn indy_prover_create_credential_req(command_handle: i32,
                                                wallet_handle: i32,
                                                prover_did: *const c_char,
                                                cred_offer_json: *const c_char,
                                                cred_def_json: *const c_char,
                                                master_secret_id: *const c_char,
                                                cb: Option<extern fn(xcommand_handle: i32, err: ErrorCode,
                                                                     cred_req_json: *const c_char)>) -> ErrorCode
/// Check credential provided by Issuer for the given credential request,
/// updates the credential by a master secret and stores in a secure wallet.
///
/// To support efficient and flexible search the following tags will be created for stored credential:
///     {
///         "schema_id": <credential schema id>,
///         "schema_issuer_did": <credential schema issuer did>,
///         "schema_name": <credential schema name>,
///         "schema_version": <credential schema version>,
///         "issuer_did": <credential issuer did>,
///         "cred_def_id": <credential definition id>,
///         "rev_reg_id": <credential revocation registry id>, // "None" as string if not present
///         // for every attribute in <credential values>
///         "attr::<attribute name>::marker": "1",
///         "attr::<attribute name>::value": <attribute raw value>,
///     }
/// 
/// #Params
/// command_handle: command handle to map callback to user context.
/// wallet_handle: wallet handler (created by open_wallet).
/// cred_id: (optional, default is a random one) identifier by which credential will be stored in the wallet
/// cred_req_metadata_json: a credential request metadata created by indy_prover_create_credential_req
/// cred_json: credential json received from issuer
/// cred_def_json: credential definition json related to <cred_def_id> in <cred_json>
/// rev_reg_def_json: revocation registry definition json related to <rev_reg_def_id> in <cred_json>
/// cb: Callback that takes command result as parameter.
///
/// #Returns
/// out_cred_id: identifier by which credential is stored in the wallet
///
/// #Errors
/// Annoncreds*
/// Common*
/// Wallet*
#[no_mangle]
pub extern fn indy_prover_store_credential(command_handle: i32,
                                           wallet_handle: i32,
                                           cred_id: *const c_char,
                                           cred_req_metadata_json: *const c_char,
                                           cred_json: *const c_char,
                                           cred_def_json: *const c_char,
                                           rev_reg_def_json: *const c_char,
                                           cb: Option<extern fn(xcommand_handle: i32, err: ErrorCode,
                                                                out_cred_id: *const c_char)>) -> ErrorCode
/// Gets human readable credential by the given id.
///
/// #Params
/// wallet_handle: wallet handler (created by open_wallet).
/// cred_id: Identifier by which requested credential is stored in the wallet
/// cb: Callback that takes command result as parameter.
///
/// #Returns
/// credential json:
///     {
///         "referent": string, // cred_id in the wallet
///         "attrs": {"key1":"raw_value1", "key2":"raw_value2"},
///         "schema_id": string,
///         "cred_def_id": string,
///         "rev_reg_id": Optional<string>,
///         "cred_rev_id": Optional<string>
///     }
///
/// #Errors
/// Annoncreds*
/// Common*
/// Wallet*
#[no_mangle]
pub extern fn indy_prover_get_credential(command_handle: i32,
                                         wallet_handle: i32,
                                         cred_id: *const c_char,
                                         cb: Option<extern fn(
                                             xcommand_handle: i32, err: ErrorCode,
                                             credential_json: *const c_char)>) -> ErrorCode
/// Gets human readable credentials according to the filter.
/// If filter is NULL, then all credentials are returned.
/// Credentials can be filtered by Issuer, credential_def and/or Schema.
///
/// NOTE: This method is deprecated because immediately returns all fetched credentials.
/// Use <indy_prover_search_credentials> to fetch records by small batches.
///
/// #Params
/// wallet_handle: wallet handler (created by open_wallet).
/// filter_json: filter for credentials
///        {
///            "schema_id": string, (Optional)
///            "schema_issuer_did": string, (Optional)
///            "schema_name": string, (Optional)
///            "schema_version": string, (Optional)
///            "issuer_did": string, (Optional)
///            "cred_def_id": string, (Optional)
///        }
/// cb: Callback that takes command result as parameter.
///
/// #Returns
/// credentials json
///     [{
///         "referent": string, // cred_id in the wallet
///         "attrs": {"key1":"raw_value1", "key2":"raw_value2"},
///         "schema_id": string,
///         "cred_def_id": string,
///         "rev_reg_id": Optional<string>,
///         "cred_rev_id": Optional<string>
///     }]
///
/// #Errors
/// Annoncreds*
/// Common*
/// Wallet*
#[no_mangle]
pub extern fn indy_prover_get_credentials(command_handle: i32,
                                          wallet_handle: i32,
                                          filter_json: *const c_char,
                                          cb: Option<extern fn(
                                              xcommand_handle: i32, err: ErrorCode,
                                              matched_credentials_json: *const c_char)>) -> ErrorCode
/// Search for credentials stored in wallet.
/// Credentials can be filtered by tags created during saving of credential.
///
/// Instead of immediately returning of fetched credentials
/// this call returns search_handle that can be used later
/// to fetch records by small batches (with indy_prover_fetch_credentials).
///
/// #Params
/// wallet_handle: wallet handler (created by open_wallet).
/// query_json: Wql style filter for credentials searching based on tags.
///     where wql query: indy-sdk/docs/design/011-wallet-query-language/README.md
/// cb: Callback that takes command result as parameter.
///
/// #Returns
/// search_handle: Search handle that can be used later to fetch records by small batches (with indy_prover_fetch_credentials)
/// total_count: Total count of records
///
/// #Errors
/// Annoncreds*
/// Common*
/// Wallet*
#[no_mangle]
pub extern fn indy_prover_search_credentials(command_handle: i32,
                                             wallet_handle: i32,
                                             query_json: *const c_char,
                                             cb: Option<extern fn(
                                                 xcommand_handle: i32, err: ErrorCode,
                                                 search_handle: i32,
                                                 total_count: usize)>) -> ErrorCode
                                                 
/// Fetch next credentials for search.
///
/// #Params
/// search_handle: Search handle (created by indy_prover_search_credentials) 
/// count: Count of credentials to fetch
/// cb: Callback that takes command result as parameter.
///
/// #Returns
/// credentials_json: List of human readable credentials:
///     [{
///         "referent": string, // cred_id in the wallet
///         "attrs": {"key1":"raw_value1", "key2":"raw_value2"},
///         "schema_id": string,
///         "cred_def_id": string,
///         "rev_reg_id": Optional<string>,
///         "cred_rev_id": Optional<string>
///     }]
/// NOTE: The list of length less than the requested count means credentials search iterator is completed.
///
/// #Errors
/// Annoncreds*
/// Common*
/// Wallet*
#[no_mangle]
pub  extern fn indy_prover_fetch_credentials(command_handle: i32,
                                             search_handle: i32,
                                             count: usize,
                                             cb: Option<extern fn(command_handle_: i32, err: ErrorCode,
                                                                  credentials_json: *const c_char)>) -> ErrorCode

/// Close credentials search (make search handle invalid)
///
/// #Params
/// search_handle: Search handle (created by indy_prover_search_credentials)
///
/// #Errors
/// Annoncreds*
/// Common*
/// Wallet*
#[no_mangle]
pub  extern fn indy_prover_close_credentials_search(command_handle: i32,
                                                    search_handle: i32,
                                                    cb: Option<extern fn(command_handle_: i32, err: ErrorCode)>) -> ErrorCode                                                 
/// Gets human readable credentials matching the given proof request.
///
/// NOTE: This method is deprecated because immediately returns all fetched credentials.
/// Use <indy_prover_search_credentials_for_proof_req> to fetch records by small batches.
///
/// #Params
/// wallet_handle: wallet handler (created by open_wallet).
/// proof_request_json: proof request json
///     {
///         "name": string,
///         "version": string,
///         "nonce": string,
///         "requested_attributes": { // set of requested attributes
///              "<attr_referent>": <attr_info>, // see below
///              ...,
///         },
///         "requested_predicates": { // set of requested predicates
///              "<predicate_referent>": <predicate_info>, // see below
///              ...,
///          },
///         "non_revoked": Optional<<non_revoc_interval>>, // see below,
///                        // If specified prover must proof non-revocation
///                        // for date in this interval for each attribute
///                        // (can be overridden on attribute level)
///     }
/// cb: Callback that takes command result as parameter.
///
/// where
/// attr_referent: Proof-request local identifier of requested attribute
/// attr_info: Describes requested attribute
///     {
///         "name": string, // attribute name, (case insensitive and ignore spaces)
///         "restrictions": Optional<filter_json>, // see above
///         "non_revoked": Optional<<non_revoc_interval>>, // see below,
///                        // If specified prover must proof non-revocation
///                        // for date in this interval this attribute
///                        // (overrides proof level interval)
///     }
/// predicate_referent: Proof-request local identifier of requested attribute predicate
/// predicate_info: Describes requested attribute predicate
///     {
///         "name": attribute name, (case insensitive and ignore spaces)
///         "p_type": predicate type (Currently ">=" only)
///         "p_value": int predicate value
///         "restrictions": Optional<filter_json>, // see above
///         "non_revoked": Optional<<non_revoc_interval>>, // see below,
///                        // If specified prover must proof non-revocation
///                        // for date in this interval this attribute
///                        // (overrides proof level interval)
///     }
/// non_revoc_interval: Defines non-revocation interval
///     {
///         "from": Optional<int>, // timestamp of interval beginning
///         "to": Optional<int>, // timestamp of interval ending
///     }
///
/// #Returns
/// credentials_json: json with credentials for the given proof request.
///     {
///         "requested_attrs": {
///             "<attr_referent>": [{ cred_info: <credential_info>, interval: Optional<non_revoc_interval> }],
///             ...,
///         },
///         "requested_predicates": {
///             "requested_predicates": [{ cred_info: <credential_info>, timestamp: Optional<integer> }, { cred_info: <credential_2_info>, timestamp: Optional<integer> }],
///             "requested_predicate_2_referent": [{ cred_info: <credential_2_info>, timestamp: Optional<integer> }]
///         }
///     }, where credential is
///     {
///         "referent": <string>,
///         "attrs": {"attr_name" : "attr_raw_value"},
///         "schema_id": string,
///         "cred_def_id": string,
///         "rev_reg_id": Optional<int>,
///         "cred_rev_id": Optional<int>,
///     }
///
/// #Errors
/// Annoncreds*
/// Common*
/// Wallet*
#[no_mangle]
pub extern fn indy_prover_get_credentials_for_proof_req(command_handle: i32,
                                                        wallet_handle: i32,
                                                        proof_request_json: *const c_char,
                                                        cb: Option<extern fn(
                                                            xcommand_handle: i32, err: ErrorCode,
                                                            credentials_json: *const c_char)>) -> ErrorCode
/// Search for credentials matching the given proof request.
///
/// Instead of immediately returning of fetched credentials
/// this call returns search_handle that can be used later
/// to fetch records by small batches (with indy_prover_fetch_credentials_for_proof_req).
///
/// #Params
/// wallet_handle: wallet handler (created by open_wallet).
/// proof_request_json: proof request json
///     {
///         "name": string,
///         "version": string,
///         "nonce": string,
///         "requested_attributes": { // set of requested attributes
///              "<attr_referent>": <attr_info>, // see below
///              ...,
///         },
///         "requested_predicates": { // set of requested predicates
///              "<predicate_referent>": <predicate_info>, // see below
///              ...,
///          },
///         "non_revoked": Optional<<non_revoc_interval>>, // see below,
///                        // If specified prover must proof non-revocation
///                        // for date in this interval for each attribute
///                        // (can be overridden on attribute level)
///     }
/// extra_query_json:(Optional) List of extra queries that will be applied to correspondent attribute/predicate:
///     {
///         "<attr_referent>": <wql query>,
///         "<predicate_referent>": <wql query>,
///     }
/// where wql query: indy-sdk/docs/design/011-wallet-query-language/README.md
/// cb: Callback that takes command result as parameter.
///
/// #Returns
/// search_handle: Search handle that can be used later to fetch records by small batches (with indy_prover_fetch_credentials_for_proof_req)
///
/// #Errors
/// Annoncreds*
/// Common*
/// Wallet*
#[no_mangle]
pub extern fn indy_prover_search_credentials_for_proof_req(command_handle: i32,
                                                           wallet_handle: i32,
                                                           proof_request_json: *const c_char,
                                                           extra_query_json: *const c_char,
                                                           cb: Option<extern fn(
                                                               xcommand_handle: i32, err: ErrorCode,
                                                               search_handle: i32)>) -> ErrorCode

/// Fetch next credentials for the requested item using proof request search 
/// handle (created by indy_prover_search_credentials_for_proof_req).
///
/// #Params
/// search_handle: Search handle (created by indy_prover_search_credentials_for_proof_req)
/// item_referent: Referent of attribute/predicate in the proof request
/// count: Count of credentials to fetch
/// cb: Callback that takes command result as parameter.
///
/// #Returns
/// credentials_json: List of credentials for the given proof request.
///     [{
///         cred_info: <credential_info>,
///         interval: Optional<non_revoc_interval>
///     }]
/// where 
/// credential_info:
///     {
///         "referent": <string>,
///         "attrs": {"attr_name" : "attr_raw_value"},
///         "schema_id": string,
///         "cred_def_id": string,
///         "rev_reg_id": Optional<int>,
///         "cred_rev_id": Optional<int>,
///     }
/// non_revoc_interval:
///     {
///         "from": Optional<int>, // timestamp of interval beginning
///         "to": Optional<int>, // timestamp of interval ending
///     }
/// NOTE: The list of length less than the requested count means that search iterator
/// correspondent to the requested <item_referent> is completed.
/// 
/// #Errors
/// Annoncreds*
/// Common*
/// Wallet*
#[no_mangle]
pub  extern fn indy_prover_fetch_credentials_for_proof_req(command_handle: i32,
                                                           search_handle: i32,
                                                           item_referent: *const c_char,
                                                           count: usize,
                                                           cb: Option<extern fn(command_handle_: i32, err: ErrorCode,
                                                                                    credentials_json: *const c_char)>) -> ErrorCode

/// Close credentials search for proof request (make search handle invalid)
///
/// #Params
/// search_handle: Search handle (created by indy_prover_search_credentials_for_proof_req)
///
/// #Errors
/// Annoncreds*
/// Common*
/// Wallet*
#[no_mangle]
pub  extern fn indy_prover_close_credentials_search_for_proof_req(command_handle: i32,
                                                                  search_handle: i32,
                                                                  cb: Option<extern fn(command_handle_: i32, err: ErrorCode)>) -> ErrorCode                                                                                    
/// Creates a proof according to the given proof request
/// Either a corresponding credential with optionally revealed attributes or self-attested attribute must be provided
/// for each requested attribute (see indy_prover_get_credentials_for_pool_req).
/// A proof request may request multiple credentials from different schemas and different issuers.
/// All required schemas, public keys and revocation registries must be provided.
/// The proof request also contains nonce.
/// The proof contains either proof or self-attested attribute value for each requested attribute.
///
/// #Params
/// wallet_handle: wallet handler (created by open_wallet).
/// command_handle: command handle to map callback to user context.
/// proof_request_json: proof request json
///     {
///         "name": string,
///         "version": string,
///         "nonce": string,
///         "requested_attributes": { // set of requested attributes
///              "<attr_referent>": <attr_info>, // see below
///              ...,
///         },
///         "requested_predicates": { // set of requested predicates
///              "<predicate_referent>": <predicate_info>, // see below
///              ...,
///          },
///         "non_revoked": Optional<<non_revoc_interval>>, // see below,
///                        // If specified prover must proof non-revocation
///                        // for date in this interval for each attribute
///                        // (can be overridden on attribute level)
///     }
/// requested_credentials_json: either a credential or self-attested attribute for each requested attribute
///     {
///         "self_attested_attributes": {
///             "self_attested_attribute_referent": string
///         },
///         "requested_attributes": {
///             "requested_attribute_referent_1": {"cred_id": string, "timestamp": Optional<number>, revealed: <bool> }},
///             "requested_attribute_referent_2": {"cred_id": string, "timestamp": Optional<number>, revealed: <bool> }}
///         },
///         "requested_predicates": {
///             "requested_predicates_referent_1": {"cred_id": string, "timestamp": Optional<number> }},
///         }
///     }
/// master_secret_id: the id of the master secret stored in the wallet
/// schemas_json: all schemas json participating in the proof request
///     {
///         <schema1_id>: <schema1_json>,
///         <schema2_id>: <schema2_json>,
///         <schema3_id>: <schema3_json>,
///     }
/// credential_defs_json: all credential definitions json participating in the proof request
///     {
///         "cred_def1_id": <credential_def1_json>,
///         "cred_def2_id": <credential_def2_json>,
///         "cred_def3_id": <credential_def3_json>,
///     }
/// rev_states_json: all revocation states json participating in the proof request
///     {
///         "rev_reg_def1_id": {
///             "timestamp1": <rev_state1>,
///             "timestamp2": <rev_state2>,
///         },
///         "rev_reg_def2_id": {
///             "timestamp3": <rev_state3>
///         },
///         "rev_reg_def3_id": {
///             "timestamp4": <rev_state4>
///         },
///     }
/// cb: Callback that takes command result as parameter.
///
/// where
/// wql query: indy-sdk/docs/design/011-wallet-query-language/README.md
/// attr_referent: Proof-request local identifier of requested attribute
/// attr_info: Describes requested attribute
///     {
///         "name": string, // attribute name, (case insensitive and ignore spaces)
///         "restrictions": Optional<filter_json> // see above.
///         "non_revoked": Optional<<non_revoc_interval>>, // see below,
///                        // If specified prover must proof non-revocation
///                        // for date in this interval this attribute
///                        // (overrides proof level interval)
///     }
/// predicate_referent: Proof-request local identifier of requested attribute predicate
/// predicate_info: Describes requested attribute predicate
///     {
///         "name": attribute name, (case insensitive and ignore spaces)
///         "p_type": predicate type (Currently >= only)
///         "p_value": predicate value
///         "restrictions": Optional<wql query>,
///         "non_revoked": Optional<<non_revoc_interval>>, // see below,
///                        // If specified prover must proof non-revocation
///                        // for date in this interval this attribute
///                        // (overrides proof level interval)
///     }
/// non_revoc_interval: Defines non-revocation interval
///     {
///         "from": Optional<int>, // timestamp of interval beginning
///         "to": Optional<int>, // timestamp of interval ending
///     }
///
/// #Returns
/// Proof json
/// For each requested attribute either a proof (with optionally revealed attribute value) or
/// self-attested attribute value is provided.
/// Each proof is associated with a credential and corresponding schema_id, cred_def_id, rev_reg_id and timestamp.
/// There is also aggregated proof part common for all credential proofs.
///     {
///         "requested": {
///             "revealed_attrs": {
///                 "requested_attr1_id": {sub_proof_index: number, raw: string, encoded: string},
///                 "requested_attr4_id": {sub_proof_index: number: string, encoded: string},
///             },
///             "unrevealed_attrs": {
///                 "requested_attr3_id": {sub_proof_index: number}
///             },
///             "self_attested_attrs": {
///                 "requested_attr2_id": self_attested_value,
///             },
///             "requested_predicates": {
///                 "requested_predicate_1_referent": {sub_proof_index: int},
///                 "requested_predicate_2_referent": {sub_proof_index: int},
///             }
///         }
///         "proof": {
///             "proofs": [ <credential_proof>, <credential_proof>, <credential_proof> ],
///             "aggregated_proof": <aggregated_proof>
///         }
///         "identifiers": [{schema_id, cred_def_id, Optional<rev_reg_id>, Optional<timestamp>}]
///     }
///
/// #Errors
/// Annoncreds*
/// Common*
/// Wallet*
#[no_mangle]
pub extern fn indy_prover_create_proof(command_handle: i32,
                                       wallet_handle: i32,
                                       proof_req_json: *const c_char,
                                       requested_credentials_json: *const c_char,
                                       master_secret_id: *const c_char,
                                       schemas_json: *const c_char,
                                       credential_defs_json: *const c_char,
                                       rev_states_json: *const c_char,
                                       cb: Option<extern fn(xcommand_handle: i32, err: ErrorCode,
                                                            proof_json: *const c_char)>) -> ErrorCode
/// Verifies a proof (of multiple credential).
/// All required schemas, public keys and revocation registries must be provided.
///
/// #Params
/// wallet_handle: wallet handler (created by open_wallet).
/// command_handle: command handle to map callback to user context.
/// proof_request_json: proof request json
///     {
///         "name": string,
///         "version": string,
///         "nonce": string,
///         "requested_attributes": { // set of requested attributes
///              "<attr_referent>": <attr_info>, // see below
///              ...,
///         },
///         "requested_predicates": { // set of requested predicates
///              "<predicate_referent>": <predicate_info>, // see below
///              ...,
///          },
///         "non_revoked": Optional<<non_revoc_interval>>, // see below,
///                        // If specified prover must proof non-revocation
///                        // for date in this interval for each attribute
///                        // (can be overridden on attribute level)
///     }
/// proof_json: created for request proof json
///     {
///         "requested": {
///             "revealed_attrs": {
///                 "requested_attr1_id": {sub_proof_index: number, raw: string, encoded: string},
///                 "requested_attr4_id": {sub_proof_index: number: string, encoded: string},
///             },
///             "unrevealed_attrs": {
///                 "requested_attr3_id": {sub_proof_index: number}
///             },
///             "self_attested_attrs": {
///                 "requested_attr2_id": self_attested_value,
///             },
///             "requested_predicates": {
///                 "requested_predicate_1_referent": {sub_proof_index: int},
///                 "requested_predicate_2_referent": {sub_proof_index: int},
///             }
///         }
///         "proof": {
///             "proofs": [ <credential_proof>, <credential_proof>, <credential_proof> ],
///             "aggregated_proof": <aggregated_proof>
///         }
///         "identifiers": [{schema_id, cred_def_id, Optional<rev_reg_id>, Optional<timestamp>}]
///     }
/// schemas_json: all schema jsons participating in the proof
///     {
///         <schema1_id>: <schema1_json>,
///         <schema2_id>: <schema2_json>,
///         <schema3_id>: <schema3_json>,
///     }
/// credential_defs_json: all credential definitions json participating in the proof
///     {
///         "cred_def1_id": <credential_def1_json>,
///         "cred_def2_id": <credential_def2_json>,
///         "cred_def3_id": <credential_def3_json>,
///     }
/// rev_reg_defs_json: all revocation registry definitions json participating in the proof
///     {
///         "rev_reg_def1_id": <rev_reg_def1_json>,
///         "rev_reg_def2_id": <rev_reg_def2_json>,
///         "rev_reg_def3_id": <rev_reg_def3_json>,
///     }
/// rev_regs_json: all revocation registries json participating in the proof
///     {
///         "rev_reg_def1_id": {
///             "timestamp1": <rev_reg1>,
///             "timestamp2": <rev_reg2>,
///         },
///         "rev_reg_def2_id": {
///             "timestamp3": <rev_reg3>
///         },
///         "rev_reg_def3_id": {
///             "timestamp4": <rev_reg4>
///         },
///     }
/// cb: Callback that takes command result as parameter.
///
/// #Returns
/// valid: true - if signature is valid, false - otherwise
///
/// #Errors
/// Annoncreds*
/// Common*
/// Wallet*
#[no_mangle]
pub extern fn indy_verifier_verify_proof(command_handle: i32,
                                         proof_request_json: *const c_char,
                                         proof_json: *const c_char,
                                         schemas_json: *const c_char,
                                         credential_defs_json: *const c_char,
                                         rev_reg_defs_json: *const c_char,
                                         rev_regs_json: *const c_char,
                                         cb: Option<extern fn(xcommand_handle: i32, err: ErrorCode,
                                                              valid: bool)>) -> ErrorCode
/// Create revocation state for a credential in the particular time moment.
///
/// #Params
/// command_handle: command handle to map callback to user context
/// blob_storage_reader_handle: configuration of blob storage reader handle that will allow to read revocation tails
/// rev_reg_def_json: revocation registry definition json
/// rev_reg_delta_json: revocation registry definition delta json
/// timestamp: time represented as a total number of seconds from Unix Epoch
/// cred_rev_id: user credential revocation id in revocation registry
/// cb: Callback that takes command result as parameter
///
/// #Returns
/// revocation state json:
///     {
///         "rev_reg": <revocation registry>,
///         "witness": <witness>,
///         "timestamp" : integer
///     }
///
/// #Errors
/// Common*
/// Wallet*
/// Anoncreds*
#[no_mangle]
pub extern fn indy_create_revocation_state(command_handle: i32,
                                           blob_storage_reader_handle: i32,
                                           rev_reg_def_json: *const c_char,
                                           rev_reg_delta_json: *const c_char,
                                           timestamp: u64,
                                           cred_rev_id: *const c_char,
                                           cb: Option<extern fn(
                                               xcommand_handle: i32, err: ErrorCode,
                                               rev_state_json: *const c_char
                                           )>) -> ErrorCode
/// Create new revocation state for a credential based on existed state
/// at the particular time moment (to reduce calculation time).
///
/// #Params
/// command_handle: command handle to map callback to user context
/// blob_storage_reader_handle: configuration of blob storage reader handle that will allow to read revocation tails
/// rev_state_json: revocation registry state json
/// rev_reg_def_json: revocation registry definition json
/// rev_reg_delta_json: revocation registry definition delta json
/// timestamp: time represented as a total number of seconds from Unix Epoch
/// cred_rev_id: user credential revocation id in revocation registry
/// cb: Callback that takes command result as parameter
///
/// #Returns
/// revocation state json:
///     {
///         "rev_reg": <revocation registry>,
///         "witness": <witness>,
///         "timestamp" : integer
///     }
///
/// #Errors
/// Common*
/// Wallet*
/// Anoncreds*
#[no_mangle]
pub extern fn indy_update_revocation_state(command_handle: i32,
                                           blob_storage_reader_handle: i32,
                                           rev_state_json: *const c_char,
                                           rev_reg_def_json: *const c_char,
                                           rev_reg_delta_json: *const c_char,
                                           timestamp: u64,
                                           cred_rev_id: *const c_char,
                                           cb: Option<extern fn(
                                               xcommand_handle: i32, err: ErrorCode,
                                               updated_rev_state_json: *const c_char
                                           )>) -> ErrorCode
Blob Storage

CL revocation schema introduces Revocation Tails entity used to hide information about revoked credential in public Revocation Registry. Tails

  • are static (once generated) array of BigIntegers that can be represented as binary blob or file
  • may require quite huge amount of data (up to 1GB per Revocation Registry);
  • are created and shared by Issuers;
  • are required (so must be available for download) for both Provers and Issuers;
  • can be cached and can be downloaded only once;
  • Some operation (incremental witness updates) can require reading only small part of blob file. It can be more efficient to store complete tails blob in the cloud and ask for small parts through network.

As result the way how to access tails blobs can be very application specific. To address this SDK will provide the following:

  • API for registering custom handler for blobs reading
  • API for registering custom handler for blobs writing
  • API for blob consistency validation
  • Default handlers implementation that will allow to read blobs from local file and write blobs to local file.

Tails publishing and access workflow can be integrated with Indy Node in the following way:

  • Issuer generates tails and writes tails blob to local file (with default handler). Our API will also provide blob hash to him and generate URI based on configurable URI pattern.
  • Issuer uploads blob to some CDN with corresponded URI (out of SDK scope)
  • Issuer sends REVOC_REG_DEF transaction with and publishes tails URI and hash
  • Prover sends GET_REVOC_REG_DEF requests and receives tails URI and hash
  • Prover downloads published tails file and stores it locally (Out of SDK)

Wallet Storage Design

In current state libindy allows to plug different wallet implementations. Plugged wallet now handles both security and storage layers. This design proposes to restrict our plugged interface by handling only storage layer. All encryption will be performed in libindy. It will simplify plugged wallets and warranty good security level for 3d party wallets implementations.

Also proposals enhances our API for efficient and flexible search with paging support.

Also proposals enhances our API to support storing of application specific data into the wallet.

Goals and ideas

  • Simplify plugged wallets and warranty security level for 3d party wallets implementation by perform all encryption on libindy level:
    • Record ids will be always encrypted.
    • Record values will be always encrypted.
    • Tag names will be always encrypted.
    • Tag values will be optionally encrypted. If user wants to perform some complex searches it can be possible to include some additional un-encrypted tags.
  • Allow plugging of different storages with native OpenSSL style object-oriented C interface for. Try to avoid unnecessary json and re-allocation.
  • Allow efficient and flexible search for entities with pagination support.
  • Expose public API to store application specific data into the wallet. This API shouldn’t have an access to secrets stored by libindy.

Wallet Components

Wallet Components

Secrets API

It is our existing endpoints for secrets creation and access like indy_create_and_store_did or indy_create_and_store_cred_def:

  1. Allow to create a secret into the wallet
  2. Return record id and optionally public part (if needed)
  3. Allow to search for stored secrets ids (if needed)
  4. Allow to reference secret by id in crypto calls that require this secret
  5. Don’t allow to get stored secret back

Non-secrets API

This API is intended to store and read application specific identity data in the wallet. This API shouldn’t have an access to secrets stored by Secret Entities API.

/// Create a new non-secret record in the wallet
///
/// #Params
/// command_handle: command handle to map callback to caller context
/// wallet_handle: wallet handle (created by open_wallet)
/// type_: allows to separate different record types collections
/// id: the id of record
/// value: the value of record
/// tags_json: the record tags used for search and storing meta information as json:
///   {
///     "tagName1": <str>, // string tag (will be stored encrypted)
///     "tagName2": <int>, // int tag (will be stored encrypted)
///     "~tagName3": <str>, // string tag (will be stored un-encrypted)
///     "~tagName4": <int>, // int tag (will be stored un-encrypted)
///   }
///   Note that null means no tags
///   If tag name starts with "~" the tag will be stored un-encrypted that will allow
///   usage of this tag in complex search queries (comparison, predicates)
///   Encrypted tags can be searched only for exact matching
extern pub fn indy_add_wallet_record(command_handle: i32,
                                     wallet_handle: i32,
                                     type_: *const c_char,
                                     id: *const c_char,
                                     value: *const c_char,
                                     tags_json: *const c_char,
                                     cb: Option<extern fn(command_handle_: i32, err: ErrorCode)>) -> ErrorCode {}

/// Update a non-secret wallet record value
///
/// #Params
/// command_handle: command handle to map callback to caller context
/// wallet_handle: wallet handle (created by open_wallet)
/// type_: allows to separate different record types collections
/// id: the id of record
/// value: the new value of record
extern pub fn indy_update_wallet_record_value(command_handle: i32,
                                              wallet_handle: i32,
                                              type_: *const c_char,
                                              id: *const c_char,
                                              value: *const c_char,
                                              cb: Option<extern fn(command_handle_: i32, err: ErrorCode)>) -> ErrorCode {}

/// Update a non-secret wallet record tags
///
/// #Params
/// command_handle: command handle to map callback to caller context
/// wallet_handle: wallet handle (created by open_wallet)
/// type_: allows to separate different record types collections
/// id: the id of record
/// tags_json: the record tags used for search and storing meta information as json:
///   {
///     "tagName1": <str>, // string tag (will be stored encrypted)
///     "tagName2": <int>, // int tag (will be stored encrypted)
///     "~tagName3": <str>, // string tag (will be stored un-encrypted)
///     "~tagName4": <int>, // int tag (will be stored un-encrypted)
///   }
///   Note that null means no tags
///   If tag name starts with "~" the tag will be stored un-encrypted that will allow
///   usage of this tag in complex search queries (comparison, predicates)
///   Encrypted tags can be searched only for exact matching
extern pub fn indy_update_wallet_record_tags(command_handle: i32,
                                             wallet_handle: i32,
                                             type_: *const c_char,
                                             id: *const c_char,
                                             tags_json: *const c_char,
                                             cb: Option<extern fn(command_handle_: i32, err: ErrorCode)>) -> ErrorCode {}

/// Add new tags to the wallet record
///
/// #Params
/// command_handle: command handle to map callback to caller context
/// wallet_handle: wallet handle (created by open_wallet)
/// type_: allows to separate different record types collections
/// id: the id of record
/// tags_json: the record tags used for search and storing meta information as json:
///   {
///     "tagName1": <str>, // string tag (will be stored encrypted)
///     "tagName2": <int>, // int tag (will be stored encrypted)
///     "~tagName3": <str>, // string tag (will be stored un-encrypted)
///     "~tagName4": <int>, // int tag (will be stored un-encrypted)
///   }
///   Note that null means no tags
///   If tag name starts with "~" the tag will be stored un-encrypted that will allow
///   usage of this tag in complex search queries (comparison, predicates)
///   Encrypted tags can be searched only for exact matching
///   Note if some from provided tags already assigned to the record than
///     corresponding tags values will be replaced
extern pub fn indy_add_wallet_record_tags(command_handle: i32,
                                          wallet_handle: i32,
                                          type_: *const c_char,
                                          id: *const c_char,
                                          tags_json: *const c_char,
                                          cb: Option<extern fn(command_handle_: i32, err: ErrorCode)>) -> ErrorCode {}

/// Delete tags from the wallet record
///
/// #Params
/// command_handle: command handle to map callback to caller context
/// wallet_handle: wallet handle (created by open_wallet)
/// type_: allows to separate different record types collections
/// id: the id of record
/// tag_names_json: the list of tag names to remove from the record as json array:
///   ["tagName1", "tagName2", ...]
///   Note that null means no tag names
extern pub fn indy_delete_wallet_record_tags(command_handle: i32,
                                             wallet_handle: i32,
                                             type_: *const c_char,
                                             id: *const c_char,
                                             tag_names_json: *const c_char,
                                             cb: Option<extern fn(command_handle_: i32, err: ErrorCode)>) -> ErrorCode {}

/// Delete an existing wallet record in the wallet
///
/// #Params
/// command_handle: command handle to map callback to caller context
/// wallet_handle: wallet handle (created by open_wallet)
/// type_: record type
/// id: the id of record
extern pub fn indy_delete_wallet_record(command_handle: i32,
                                        wallet_handle: i32,
                                        type_: *const c_char,
                                        id: *const c_char,
                                        cb: Option<extern fn(command_handle_: i32, err: ErrorCode)>) -> ErrorCode> {}

/// Get an wallet record by id
///
/// #Params
/// command_handle: command handle to map callback to caller context
/// wallet_handle: wallet handle (created by open_wallet)
/// type_: allows to separate different record types collections
/// id: the id of record
/// options_json: //TODO: FIXME: Think about replacing by bitmask
///  {
///    retrieveType: (optional, false by default) Retrieve record type,
///    retrieveValue: (optional, true by default) Retrieve record value,
///    retrieveTags: (optional, false by default) Retrieve record tags
///  }
/// #Returns
/// wallet record json:
/// {
///   id: "Some id",
///   value: "Some value", // present only if retrieveValue set to true
///   tags: <tags json>, // present only if retrieveTags set to true
/// }
extern pub fn indy_get_wallet_record(command_handle: i32,
                                     wallet_handle: i32,
                                     type_: *const c_char,
                                     id: *const c_char,
                                     options_json: *const c_char,
                                     cb: Option<extern fn(command_handle_: i32, err: ErrorCode,
                                                          record_json: *const c_char)>) -> ErrorCode {}

/// Search for wallet records.
///
/// Note instead of immediately returning of fetched records
/// this call returns wallet_search_handle that can be used later
/// to fetch records by small batches (with indy_fetch_wallet_search_next_records).
///
/// #Params
/// wallet_handle: wallet handle (created by open_wallet)
/// type_: allows to separate different record types collections
/// query_json: MongoDB style query to wallet record tags:
///  {
///    "tagName": "tagValue",
///    $or: {
///      "tagName2": { $regex: 'pattern' },
///      "tagName3": { $gte: 123 },
///    },
///  }
/// options_json: //TODO: FIXME: Think about replacing by bitmask
///  {
///    retrieveRecords: (optional, true by default) If false only "counts" will be calculated,
///    retrieveTotalCount: (optional, false by default) Calculate total count,
///    retrieveValue: (optional, true by default) Retrieve record value,
///    retrieveTags: (optional, false by default) Retrieve record tags,
///  }
/// #Returns
/// wallet_search_handle: Wallet search handle that can be used later
///   to fetch records by small batches (with indy_fetch_wallet_search_next_records)
extern pub fn indy_open_wallet_search(command_handle: i32,
                                      wallet_handle: i32,
                                      type_: *const c_char,
                                      query_json: *const c_char,
                                      options_json: *const c_char,
                                      cb: Option<extern fn(command_handle_: i32, err: ErrorCode,
                                                           wallet_search_handle_p: *mut i32)>) -> ErrorCode {}


/// Fetch next records for wallet search.
///
/// Not if there are no records this call returns WalletNoRecords error.
///
/// #Params
/// wallet_handle: wallet handle (created by open_wallet)
/// wallet_search_handle: wallet wallet handle (created by indy_open_wallet_search)
/// count: Count of records to fetch
///
/// #Returns
/// wallet records json:
/// {
///   totalCount: <int>, // present only if retrieveTotalCount set to true
///   records: [{ // present only if retrieveRecords set to true
///       id: "Some id",
///       value: "Some value", // present only if retrieveValue set to true
///       tags: <tags json>, // present only if retrieveTags set to true
///   }],
/// }
extern pub fn indy_fetch_wallet_search_next_records(command_handle: i32,
                                                    wallet_handle: i32,
                                                    wallet_search_handle: i32,
                                                    count: usize,
                                                    cb: Option<extern fn(command_handle_: i32, err: ErrorCode,
                                                                         records_json: *const c_char)>) -> ErrorCode {}

Wallet API and Storage Interface

Wallet API already exists and allows wallet management. It requires to be update to allow plugging different storages implementation to libindy. For example, to allow storing of wallet records in SQL database. To achieve this we will replace existing indy_register_wallet_type call with indy_register_wallet_storage call:

/// Register custom wallet storage implementation.
///
/// It allows library user to provide custom wallet storage implementation as set of handlers.
///
/// #Params
/// command_handle: Command handle to map callback to caller context.
/// type_: Wallet storage name.
/// create: Wallet storage "create" operation handler
/// delete: Wallet storage "delete" operation handler
/// open: Wallet storage "open" operation handler
/// close: Wallet storage "close" operation handler
/// add_record: Wallet storage "add_record" operation handler
/// update_record_value: Wallet storage "update_record_value" operation handler
/// update_record_tags: Wallet storage "update_record_tags" operation handler
/// add_record_tags: Wallet storage "add_record_tags" operation handler
/// delete_record_tags: Wallet storage "delete_record_tags" operation handler
/// delete_record: Wallet storage "delete_record" operation handler
/// get_record: Wallet storage "get_record" operation handler
/// get_record_id: Wallet storage "get_record_id" operation handler
/// get_record_value: Wallet storage "get_record_value" operation handler
/// get_record_tags: Wallet storage "get_record_tags" operation handler
/// free_record: Wallet storage "free_record" operation handler
/// search_records: Wallet storage "search_records" operation handler
/// get_search_total_count: Wallet storage "get_search_total_count" operation handler
/// get_search_count: Wallet storage "get_search_count" operation handler
/// get_search_next_record: Wallet storage "get_search_next_record" operation handler
/// free_search: Wallet storage "free_search" operation handler
///
/// #Returns
/// Error code
#[no_mangle]
pub extern fn indy_register_wallet_storage(command_handle: i32,
                                           type_: *const c_char,

                                           /// Create the wallet storage (For example, database creation)
                                           ///
                                           /// #Params
                                           /// name: wallet storage name (the same as wallet name)
                                           /// config: wallet storage config (For example, database config)
                                           /// credentials: wallet storage credentials (For example, database credentials)
                                           create: Option<extern fn(name: *const c_char,
                                                                    config: *const c_char,
                                                                    credentials: *const c_char) -> ErrorCode>,

                                           /// Delete the wallet storage (For example, database deletion)
                                           ///
                                           /// #Params
                                           /// name: wallet storage name (the same as wallet name)
                                           /// config: wallet storage config (For example, database config)
                                           /// credentials: wallet storage credentials (For example, database credentials)
                                           delete: Option<extern fn(name: *const c_char,
                                                                    config: *const c_char,
                                                                    credentials: *const c_char) -> ErrorCode>,

                                           /// Open the wallet storage (For example, opening database connection)
                                           ///
                                           /// #Params
                                           /// name: wallet storage name (the same as wallet name)
                                           /// config: wallet storage config (For example, database config)
                                           /// runtime_config: wallet storage runtime config (For example, connection config)
                                           /// credentials: wallet storage credentials (For example, database credentials)
                                           /// storage_handle_p: pointer to store opened storage handle
                                           open: Option<extern fn(name: *const c_char,
                                                                  config: *const c_char,
                                                                  runtime_config: *const c_char,
                                                                  credentials: *const c_char,
                                                                  storage_handle_p: *mut i32) -> ErrorCode>,

                                           /// Close the opened walled storage (For example, closing database connection)
                                           ///
                                           /// #Params
                                           /// storage_handle: opened storage handle (See open handler)
                                           close: Option<extern fn(handle: i32) -> ErrorCode>,

                                           /// Create a new record in the wallet storage
                                           ///
                                           /// #Params
                                           /// storage_handle: opened storage handle (See open handler)
                                           /// type_: allows to separate different record types collections
                                           /// id: the id of record
                                           /// value: the value of record (pointer to buffer)
                                           /// value_len: the value of record (buffer size)
                                           /// tags_json: the record tags used for search and storing meta information as json:
                                           ///   {
                                           ///     "tagName1": "tag value 1", // string value
                                           ///     "tagName2": 123, // numeric value
                                           ///   }
                                           ///   Note that null means no tags
                                           add_record: Option<extern fn(storage_handle: i32,
                                                                        type_: *const c_char,
                                                                        id: *const c_char,
                                                                        value: *const u8,
                                                                        value_len: usize,
                                                                        tags_json: *const c_char) -> ErrorCode>,

                                           /// Update a record value
                                           ///
                                           /// #Params
                                           /// storage_handle: opened storage handle (See open handler)
                                           /// type_: allows to separate different record types collections
                                           /// id: the id of record
                                           /// value: the value of record (pointer to buffer)
                                           /// value_len: the value of record (buffer size)
                                           update_record_value: Option<extern fn(storage_handle: i32,
                                                                                 type_: *const c_char,
                                                                                 id: *const c_char,
                                                                                 value: *const c_char,
                                                                                 value_len: usize) -> ErrorCode>,

                                           /// Update a record tags
                                           ///
                                           /// #Params
                                           /// storage_handle: opened storage handle (See open handler)
                                           /// type_: allows to separate different record types collections
                                           /// id: the id of record
                                           /// tags_json: the new record tags used for search and storing meta information as json:
                                           ///   {
                                           ///     "tagName1": "tag value 1", // string value
                                           ///     "tagName2": 123, // numeric value
                                           ///   }
                                           ///   Note that null means no tags
                                           update_record_tags: Option<extern fn(storage_handle: i32,
                                                                                type_: *const c_char,
                                                                                id: *const c_char,
                                                                                tags_json: *const c_char) -> ErrorCode>,

                                           /// Add new tags to the record
                                           ///
                                           /// #Params
                                           /// storage_handle: opened storage handle (See open handler)
                                           /// type_: allows to separate different record types collections
                                           /// id: the id of record
                                           /// tags_json: the additional record tags as json:
                                           ///   {
                                           ///     "tagName1": "tag value 1", // string value
                                           ///     "tagName2": 123, // numeric value,
                                           ///     ...
                                           ///   }
                                           ///   Note that null means no tags
                                           ///   Note if some from provided tags already assigned to the record than
                                           ///     corresponding tags values will be replaced
                                           add_record_tags: Option<extern fn(storage_handle: i32,
                                                                             type_: *const c_char,
                                                                             id: *const c_char,
                                                                             tags_json: *const c_char) -> ErrorCode>,

                                           /// Delete tags from the record
                                           ///
                                           /// #Params
                                           /// storage_handle: opened storage handle (See open handler)
                                           /// type_: allows to separate different record types collections
                                           /// id: the id of record
                                           /// tag_names_json: the list of tag names to remove from the record as json array:
                                           ///   ["tagName1", "tagName2", ...]
                                           ///   Note that null means no tag names
                                           delete_record_tags: Option<extern fn(storage_handle: i32,
                                                                                type_: *const c_char,
                                                                                id: *const c_char,
                                                                                tag_names_json: *const c_char) -> ErrorCode>,

                                           /// Delete an existing record in the wallet storage
                                           ///
                                           /// #Params
                                           /// storage_handle: opened storage handle (See open handler)
                                           /// type_: record type
                                           /// id: the id of record
                                           delete_record: Option<extern fn(storage_handle: i32,
                                                                           type_: *const c_char,
                                                                           id: *const c_char) -> ErrorCode>,

                                           /// Get an wallet storage record by id
                                           ///
                                           /// #Params
                                           /// storage_handle: opened storage handle (See open handler)
                                           /// type_: allows to separate different record types collections
                                           /// id: the id of record
                                           /// options_json: //TODO: FIXME: Think about replacing by bitmask
                                           ///  {
                                           ///    retrieveType: (optional, false by default) Retrieve record type,
                                           ///    retrieveValue: (optional, true by default) Retrieve record value,
                                           ///    retrieveTags: (optional, false by default) Retrieve record tags
                                           ///  }
                                           /// record_handle_p: pointer to store retrieved record handle
                                           get_record: Option<extern fn(storage_handle: i32,
                                                                        type_: *const c_char,
                                                                        id: *const c_char,
                                                                        options_json: *const c_char,
                                                                        record_handle_p: *mut i32) -> ErrorCode>,

                                          /// Get an id for retrieved wallet storage record
                                          ///
                                          /// #Params
                                          /// storage_handle: opened storage handle (See open handler)
                                          /// record_handle: retrieved record handle (See get_record handler)
                                          ///
                                          /// returns: record id
                                          ///          Note that pointer lifetime the same as retrieved record lifetime
                                          ///            (until record_free called)
                                          get_record_id: Option<extern fn(storage_handle: i32,
                                                                          record_handle: i32,
                                                                          record_id_p: *mut *const c_char) -> ErrorCode>,

                                          /// Get an type for retrieved wallet storage record
                                          ///
                                          /// #Params
                                          /// storage_handle: opened storage handle (See open handler)
                                          /// record_handle: retrieved record handle (See get_record handler)
                                          ///
                                          /// returns: record id
                                          ///          Note that pointer lifetime the same as retrieved record lifetime
                                          ///            (until record_free called)
                                          ///          Note that null be returned if no type retrieved
                                          get_record_type: Option<extern fn(storage_handle: i32,
                                                                            record_handle: i32,
                                                                            record_type_p: *mut *const c_char) -> ErrorCode>,

                                          /// Get an value for retrieved wallet storage record
                                          ///
                                          /// #Params
                                          /// storage_handle: opened storage handle (See open handler)
                                          /// record_handle: retrieved record handle (See get_record handler)
                                          ///
                                          /// returns: record value
                                          ///          Note that pointer lifetime the same as retrieved record lifetime
                                          ///            (until record_free called)
                                          ///          Note that null be returned if no value retrieved
                                          get_record_value: Option<extern fn(storage_handle: i32,
                                                                             record_handle: i32,
                                                                             record_value_p: *mut *const u8,
                                                                             record_value_len_p: *mut i32) -> ErrorCode>,

                                          /// Get an tags for retrieved wallet record
                                          ///
                                          /// #Params
                                          /// storage_handle: opened storage handle (See open handler)
                                          /// record_handle: retrieved record handle (See get_record handler)
                                          ///
                                          /// returns: record tags as json
                                          ///          Note that pointer lifetime the same as retrieved record lifetime
                                          ///            (until record_free called)
                                          ///          Note that null be returned if no tags retrieved
                                          get_record_tags: Option<extern fn(storage_handle: i32,
                                                                            record_handle: i32,
                                                                            record_tags_p: *mut *const c_char) -> ErrorCode>,

                                          /// Free retrieved wallet record (make retrieved record handle invalid)
                                          ///
                                          /// #Params
                                          /// storage_handle: opened storage handle (See open_wallet_storage)
                                          /// record_handle: retrieved record handle (See wallet_storage_get_wallet_record)
                                          free_record: Option<extern fn(storage_handle: i32,
                                                                        record_handle: i32) -> ErrorCode>,

                                          /// Search for wallet storage records
                                          ///
                                          /// #Params
                                          /// storage_handle: opened storage handle (See open handler)
                                          /// type_: allows to separate different record types collections
                                          /// query_json: MongoDB style query to wallet record tags:
                                          ///  {
                                          ///    "tagName": "tagValue",
                                          ///    $or: {
                                          ///      "tagName2": { $regex: 'pattern' },
                                          ///      "tagName3": { $gte: 123 },
                                          ///    },
                                          ///  }
                                          /// options_json: //TODO: FIXME: Think about replacing by bitmask
                                          ///  {
                                          ///    retrieveRecords: (optional, true by default) If false only "counts" will be calculated,
                                          ///    retrieveTotalCount: (optional, false by default) Calculate total count,
                                          ///    retrieveType: (optional, false by default) Retrieve record type,
                                          ///    retrieveValue: (optional, true by default) Retrieve record value,
                                          ///    retrieveTags: (optional, false by default) Retrieve record tags,
                                          ///  }
                                          /// search_handle_p: pointer to store wallet search handle
                                          search_records: Option<extern fn(storage_handle: i32,
                                                                           type_: *const c_char,
                                                                           query_json: *const c_char,
                                                                           options_json: *const c_char,
                                                                           search_handle_p: *mut i32) -> ErrorCode>,

                                          /// Search for wallet storage records
                                          ///
                                          /// Note that for each record id, type, value and tags will be retrieved
                                          /// Note that total count will not be calculated
                                          ///
                                          /// #Params
                                          /// storage_handle: opened storage handle (See open handler)
                                          /// search_handle_p: pointer to store wallet search handle
                                          search_all_records: Option<extern fn(storage_handle: i32,
                                                                               search_handle_p: *mut i32) -> ErrorCode>,

                                          /// Get total count of records that corresponds to wallet storage search query
                                          ///
                                          /// #Params
                                          /// storage_handle: opened storage handle (See open handler)
                                          /// search_handle: wallet search handle (See search_records handler)
                                          ///
                                          /// returns: total count of records that corresponds to wallet storage search query
                                          ///          Note -1 will be returned if retrieveTotalCount set to false for search_records
                                          get_search_total_count: Option<extern fn(storage_handle: i32,
                                                                                   search_handle: i32,
                                                                                   total_count_p: *mut usize) -> ErrorCode>,

                                          /// Get the next wallet storage record handle retrieved by this wallet search.
                                          ///
                                          /// #Params
                                          /// storage_handle: opened storage handle (See open handler)
                                          /// search_handle: wallet search handle (See search_records handler)
                                          ///
                                          /// returns: record handle (the same as for get_record handler)
                                          ///          Note if no more records WalletNoRecords error will be returned
                                          fetch_search_next_record: Option<extern fn(storage_handle: i32,
                                                                                     search_handle: i32,
                                                                                     record_handle_p: *mut i32) -> ErrorCode>,

                                          /// Free wallet search (make search handle invalid)
                                          ///
                                          /// #Params
                                          /// storage_handle: opened storage handle (See open handler)
                                          /// search_handle: wallet search handle (See search_records handler)
                                          free_search: Option<extern fn(storage_handle: i32,
                                                                        search_handle: i32) -> ErrorCode>,

                                          cb: Option<extern fn(command_handle_: i32,
                                                               err: ErrorCode)>) -> ErrorCode

Storage Interface entities are:

Storage Interface entities

Wallet Service Interface

impl WalletService {

  pub fn new() -> WalletService {}

  pub fn register_wallet_storage(&self,
                                 type_: *const c_char,
                                 create: extern fn(name: *const c_char,
                                                   config: *const c_char)  -> ErrorCode,
                                 delete: extern fn(name: *const c_char,
                                                   config: *const c_char,
                                                   credentials: *const c_char) -> ErrorCode,
                                 open: extern fn(name: *const c_char,
                                                 config: *const c_char,
                                                 runtime_config: *const c_char,
                                                 credentials: *const c_char,
                                                 storage_handle_p: *mut i32) -> ErrorCode,
                                 close: extern fn(handle: i32) -> ErrorCode,
                                 add_record: extern fn(storage_handle: i32,
                                                       type_: *const c_char,
                                                       id: *const c_char,
                                                       value: *const u8,
                                                       value_len: usize,
                                                       tags_json: *const c_char) -> ErrorCode,
                                 update_record_value: extern fn(storage_handle: i32,
                                                                type_: *const c_char,
                                                                id: *const c_char,
                                                                value: *const u8,
                                                                value_len: usize) -> ErrorCode,
                                 update_record_tags: extern fn(storage_handle: i32,
                                                               type_: *const c_char,
                                                               id: *const c_char,
                                                               tags_json: *const c_char) -> ErrorCode,
                                 add_record_tags: extern fn(storage_handle: i32,
                                                            type_: *const c_char,
                                                            id: *const c_char,
                                                            tags_json: *const c_char) -> ErrorCode,
                                 delete_record_tags: extern fn(storage_handle: i32,
                                                               type_: *const c_char,
                                                               id: *const c_char,
                                                               tag_names_json: *const c_char) -> ErrorCode,
                                 delete_record: extern fn(storage_handle: i32,
                                                          type_: *const c_char,
                                                          id: *const c_char) -> ErrorCode,
                                 get_record: extern fn(storage_handle: i32,
                                                       type_: *const c_char,
                                                       id: *const c_char,
                                                       options_json: *const c_char,
                                                       record_handle_p: *mut i32) -> ErrorCode,
                                 get_record_id: extern fn(storage_handle: i32,
                                                          record_handle: i32,
                                                          record_id_p: *mut *const c_char) -> ErrorCode,
                                 get_record_value: extern fn(storage_handle: i32,
                                                             record_handle: i32,
                                                             record_value_p: *mut *const u8,
                                                             record_value_len_p: *mut usize) -> ErrorCode,
                                 get_record_tags: extern fn(storage_handle: i32,
                                                            record_handle: i32,
                                                            record_tags_p: *mut *const c_char) -> ErrorCode,
                                 free_record: extern fn(storage_handle: i32,
                                                        record_handle: i32) -> ErrorCode,
                                 search_records: extern fn(storage_handle: i32,
                                                           type_: *const c_char,
                                                           query_json: *const c_char,
                                                           options_json: *const c_char,
                                                           search_handle_p: *mut i32) -> ErrorCode,
                                search_all_records: extern fn(storage_handle: i32,
                                                              search_handle_p: *mut i32) -> ErrorCode,
                                 get_search_total_count: extern fn(storage_handle: i32,
                                                                   search_handle: i32,
                                                                   total_count_p: *mut usize) -> ErrorCode,
                                 fetch_search_next_record: Option<extern fn(storage_handle: i32,
                                                                            search_handle: i32,
                                                                            record_handle_p: *mut i32) -> ErrorCode,
                                 free_search: extern fn(storage_handle: i32,
                                                        search_handle: i32) -> ErrorCode>) -> Result<(), WalletError> {}

   pub fn create_wallet(&self,
                        pool_name: &str,
                        name: &str,
                        storage_type: Option<&str>,
                        storage_config: Option<&str>,
                        credentials: &str) -> Result<(), WalletError> {}

   pub fn delete_wallet(&self,
                        name: &str,
                        credentials: &str) -> Result<(), WalletError> {}

   pub fn open_wallet(&self,
                      name: &str,
                      credentials: &str) -> Result<i32, WalletError> {}

   pub fn close_wallet(&self,
                       wallet_handle: i32) -> Result<(), WalletError> {}

   pub fn list_wallets(&self) -> Result<Vec<WalletMetadata>, WalletError> {}

   pub fn add_record(wallet_handle: i32,
                     type_: &str,
                     id: &str,
                     tags_json: &str) -> Result<(), WalletError> {}

   pub fn update_record_value(wallet_handle: i32,
                              type_: &str,
                              id: &str,
                              value: &str) -> Result<(), WalletError> {}

   pub fn update_record_tags(wallet_handle: i32,
                             type_: &str,
                             id: &str,
                             tags_json: &str) -> Result<(), WalletError> {}

   pub fn add_record_tags(wallet_handle: i32,
                          type_: &str,
                          id: &str,
                          tags_json: &str) -> Result<(), WalletError> {}

   pub fn delete_record_tags(storage_handle: i32,
                             type_: &str,
                             id: &str,
                             tag_names_json: &str) -> Result<(), WalletError> {}

   pub fn delete_record(storage_handle: i32,
                        type_: &str,
                        id: &str) -> Result<(), WalletError> {}

   pub fn get_record(storage_handle: i32,
                     type_: &str,
                     id: &str,
                     options_json: &str) -> Result<WalletRecord, WalletError> {}

    pub fn search_records(storage_handle: i32,
                          type_: &str,
                          query_json: &str,
                          options_json: &str) -> Result<WalletSearch, WalletError> {}

    pub fn search_all_records(storage_handle: i32) -> Result<WalletSearch, WalletError> {}
}

impl WalletRecord {

  pub fn get_id() -> &str {}

  pub fn get_type() -> Option<&str> {}

  pub fn get_value() -> Option<&str> {}

  pub fn get_tags() -> Option<&str> {}
}

impl WalletSearch {

  pub fn get_total_count() -> Result<Option<i32>, WalletError> {}

  pub fn fetch_next_record() -> Result<Option<WalletRecord>, WalletError> {}
}

Wallet Query Language

This language will be used to define queries in Non-secrets API, Wallet Service and Wallet Storage Interface.

query = {subquery}
subquery = {subquery, ..., subquery} - WHERE subquery AND ... AND subquery
subquery = $or: [{subquery},..., {subquery}] - WHERE subquery OR ... OR subquery
subquery = $not: {subquery} - Where NOT (subquery)
subquery = "tagName": tagValue - WHERE tagName == tagValue
subquery = "tagName": {$neq: tagValue} - WHERE tagName != tagValue
subquery = "tagName": {$gt: tagValue} - WHERE tagName > tagValue
subquery = "tagName": {$gte: tagValue} - WHERE tagName >= tagValue
subquery = "tagName": {$lt: tagValue} - WHERE tagName < tagValue
subquery = "tagName": {$lte: tagValue} - WHERE tagName <= tagValue
subquery = "tagName": {$like: tagValue} - WHERE tagName LIKE tagValue
subquery = "tagName": {$in: [tagValue, ..., tagValue]} - WHERE tagName IN (tagValue, ..., tagValue)

Separation of libindy secrets from non-secrets

The idea is the following:

  • libindy will use specific prefix for all internal record types like “~did”
  • For non-secret interface it will be impossible to use this prefix
  • If user passes value with this prefix as entity type libindy will return validation error

Separation of libindy defined tags from user defined tags

The idea is the following:

  • libindy will use specific prefix for all internal record tags like “indy:tagName”
  • For all user interfaces it will be impossible to modify tags with this prefix
  • If user passes tag with this prefix to any function that modifies tags libindy will return validation error
  • Note that it will be possible for user application to read libindy defined tags and use these tags for searching

Wallet Encryption

Encryption schema is the following:

Encryption Schema

Payment Interface

This design proposes to make libindy aware about payments and tokens that can be implemented with Indy infrastructure.

Goals and ideas

  • Libindy should be aware about some details of payments in Indy infrastructure:
    • The idea of payments in general. Transactions might need to be paid for, transactions can be used for money/tokens transfer.
    • Concept of different Payment Methods that can be plugged to libindy (like Sovrin tokens, Bitcoin tokens, Visa and etc…). A payment method in libindy might be identified by prefix: “pay:sov” could be the prefix for the Sovrin token payment method, and “pay:xyz” could be the prefix for a different payment method.
    • Concept of Payment Address that is common for supported Payment Methods. Different payment use different format of Payment Address, but there is agreement on fully resolvable payment address format. This is very much like the notion of the general DID spec with sub-specs called DID method specs that are associated with a name. Payment addresses would be things like “pay:sov:12345”.
    • The idea of payments take inputs and outputs.
    • General payment errors that might happen (e.g., “insufficient funds”).
    • Proof of address control
  • Out-of-box libindy will not provide support of any payment method, but there will be API to register payment methods.
  • Each payment method should be aware about:
    • Its payment method prefix, and the format of its payment addresses.
    • How to create pure payment transactions, such as those that transfers sources, mint sources, or lookup balances.
    • How to modify an unsigned non-payment transaction (e.g., NYM) to pay fees.
    • How to modify transaction signing in a way that satisfies its payment method.
    • Possibly, special payment addresses that are significant to its method (e.g., the payment address at which the Sovrin Foundation receives fees).
  • Libindy should provide generic API for payment addresses creation, building of payment-related transactions, assigning fees to transactions. This API will look to registered payment methods and call corresponded handlers.
  • Payments interface must be interoperable as possible between different payment methods.

Payment Interface

Payment Method API

Payment Method API will allow to register custom payment method implementation by calling indy_register_payment_method call:

/// Register custom payment implementation.
///
/// It allows library user to provide custom payment method implementation as set of handlers.
///
/// #Params
/// command_handle: Command handle to map callback to caller context.
/// payment_method: The type of payment method also used as sub-prefix for fully resolvable payment address format ("sov" - for example)
/// create_payment_address: "create_payment_address" operation handler
/// add_request_fees: "add_request_fees" operation handler
/// parse_response_with_fees: "parse_response_with_fees" operation handler
/// build_get_payment_sources_request: "build_get_payment_sources_request" operation handler
/// parse_get_payment_sources_response: "parse_get_payment_sources_response" operation handler
/// build_payment_req: "build_payment_req" operation handler
/// parse_payment_response: "parse_payment_response" operation handler
/// build_mint_req: "build_mint_req" operation handler
/// build_set_txn_fees_req: "build_set_txn_fees_req" operation handler
/// build_get_txn_fees_req: "build_get_txn_fees_req" operation handler
/// parse_get_txn_fees_response: "parse_get_txn_fees_response" operation handler
/// build_verify_payment_req: "build_verify_payment_req" operation handler
/// parse_verify_payment_response: "parse_verify_payment_response" operation handler
///
/// #Returns
/// Error code
#[no_mangle]
pub extern fn indy_register_payment_method(command_handle: i32,
                                           payment_method: *const c_char,

                                           create_payment_address: Option<CreatePaymentAddressCB>,
                                           add_request_fees: Option<AddRequestFeesCB>,
                                           parse_response_with_fees: Option<ParseResponseWithFeesCB>,
                                           build_get_payment_sources_request: Option<BuildGetPaymentSourcesRequestCB>,
                                           parse_get_payment_sources_response: Option<ParseGetPaymentSourcesResponseCB>,
                                           build_payment_req: Option<BuildPaymentReqCB>,
                                           parse_payment_response: Option<ParsePaymentResponseCB>,
                                           build_mint_req: Option<BuildMintReqCB>,
                                           build_set_txn_fees_req: Option<BuildSetTxnFeesReqCB>,
                                           build_get_txn_fees_req: Option<BuildGetTxnFeesReqCB>,
                                           parse_get_txn_fees_response: Option<ParseGetTxnFeesResponseCB>,
                                           build_verify_req: Option<BuildVerifyReqCB>,
                                           parse_verify_response: Option<ParseVerifyResponseCB>,
                                           cb: Option<extern fn(command_handle_: i32,
                                                                err: ErrorCode) -> ErrorCode>) -> ErrorCode {}
Payment Method Handler Interface

Registered functions will be called by libindy as part of processing libindy API calls. Libindy will pass its own callback to the functions to retrieve result of the 3rd party function implementation. The list below is type description for registered calls.

/// Create the payment address for this payment method.
///
/// This method generates private part of payment address
/// and stores it in a secure place. Ideally it should be
/// secret in libindy wallet (see crypto module).
///
/// Note that payment method should be able to resolve this
/// secret by fully resolvable payment address format.
///
/// #Params
/// command_handle: command handle to map callback to context
/// wallet_handle: wallet handle where keys for signature are stored
/// config: payment address config as json:
///   {
///     seed: <str>, // allows deterministic creation of payment address
///   }
///
/// #Returns
/// payment_address - public identifier of payment address in fully resolvable payment address format
type CreatePaymentAddressCB = extern fn(command_handle: i32,
                                        wallet_handle: i32,
                                        config: *const c_char,
                                        cb: Option<extern fn(command_handle_: i32,
                                                             err: ErrorCode,
                                                             payment_address: *const c_char) -> ErrorCode>) -> ErrorCode;

/// Modifies Indy request by adding information how to pay fees for this transaction
/// according to this payment method.
///
/// This method consumes set of inputs and outputs. The difference between inputs balance
/// and outputs balance is the fee for this transaction.
///
/// Not that this method also produces correct fee signatures.
///
/// Format of inputs is specific for payment method. Usually it should reference payment transaction
/// with at least one output that corresponds to payment address that user owns.
///
/// #Params
/// command_handle: command handle to map callback to context
/// wallet_handle: wallet handle
/// submitter_did : DID of request sender
/// req_json: initial transaction request as json
/// inputs_json: The list of payment sources as json array:
///   ["source1", ...]
///   Note that each source should reference payment address
/// outputs_json: The list of outputs as json array:
///   [{
///     recipient: <str>, // payment address of recipient
///     amount: <int>, // amount
///   }]
/// extra: // optional information for payment operation
///
/// #Returns
/// req_with_fees_json - modified Indy request with added fees info
type AddRequestFeesCB = extern fn(command_handle: i32,
                                  wallet_handle: i32,
                                  submitter_did: *const c_char,
                                  req_json: *const c_char,
                                  inputs_json: *const c_char,
                                  outputs_json: *const c_char,
                                  cb: Option<extern fn(command_handle_: i32,
                                                       err: ErrorCode,
                                                       req_with_fees_json: *const c_char) -> ErrorCode>) -> ErrorCode;

/// Parses response for Indy request with fees.
///
/// #Params
/// command_handle: command handle to map callback to context
/// resp_json: response for Indy request with fees
///
/// #Returns
/// receipts_json - parsed (payment method and node version agnostic) receipts info as json:
///   [{
///      receipt: <str>, // receipt that can be used for payment referencing and verification
///      recipient: <str>, //payment address for this recipient
///      amount: <int>, // amount
///      extra: <str>, // optional data from payment transaction
///   }]
type ParseResponseWithFeesCB = extern fn(command_handle: i32,
                                         resp_json: *const c_char,
                                         cb: Option<extern fn(command_handle_: i32,
                                                              err: ErrorCode,
                                                              receipts_json: *const c_char) -> ErrorCode>) -> ErrorCode;
                                                       
/// Builds Indy request for getting sources list for payment address
/// according to this payment method.
///
/// #Params
/// command_handle: command handle to map callback to context
/// wallet_handle: wallet handle
/// submitter_did : DID of request sender
/// payment_address: target payment address
///
/// #Returns
/// get_sources_txn_json - Indy request for getting sources list for payment address
type BuildGetPaymentSourcesRequestCB = extern fn(command_handle: i32,
                                       wallet_handle: i32,
                                       submitter_did: *const c_char,
                                       payment_address: *const c_char,
                                       cb: Option<extern fn(command_handle_: i32,
                                                            err: ErrorCode,
                                                            get_sources_txn_json: *const c_char) -> ErrorCode>) -> ErrorCode;

/// Parses response for Indy request for getting sources list.
///
/// #Params
/// command_handle: command handle to map callback to context
/// resp_json: response for Indy request for getting sources list
///
/// #Returns
/// sources_json - parsed (payment method and node version agnostic) sources info as json:
///   [{
///      source: <str>, // source input
///      paymentAddress: <str>, //payment address for this source
///      amount: <int>, // amount
///      extra: <str>, // optional data from payment transaction
///   }]
type ParseGetPaymentSourcesResponseCB = extern fn(command_handle: i32,
                                        resp_json: *const c_char,
                                        cb: Option<extern fn(command_handle_: i32,
                                                             err: ErrorCode,
                                                             sources_json: *const c_char) -> ErrorCode>) -> ErrorCode;

/// Builds Indy request for doing payment
/// according to this payment method.
///
/// This method consumes set of inputs and outputs.
///
/// Format of inputs is specific for payment method. Usually it should reference payment transaction
/// with at least one output that corresponds to payment address that user owns.
///
/// #Params
/// command_handle: command handle to map callback to context
/// wallet_handle: wallet handle
/// submitter_did : DID of request sender
/// inputs_json: The list of payment sources as json array:
///   ["source1", ...]
///   Note that each source should reference payment address
/// outputs_json: The list of outputs as json array:
///   [{
///     recipient: <str>, // payment address of recipient
///     amount: <int>, // amount
///   }]
/// extra: // optional information for payment operation
///
/// #Returns
/// payment_req_json - Indy request for doing payment
type BuildPaymentReqCB = extern fn(command_handle: i32,
                                   wallet_handle: i32,
                                   submitter_did: *const c_char,
                                   inputs_json: *const c_char,
                                   outputs_json: *const c_char,
                                   cb: Option<extern fn(command_handle_: i32,
                                                        err: ErrorCode,
                                                        payment_req_json: *const c_char) -> ErrorCode>) -> ErrorCode;

/// Parses response for Indy request for payment txn.
///
/// #Params
/// command_handle: command handle to map callback to context
/// resp_json: response for Indy request for payment txn
///
/// #Returns
/// receipts_json - parsed (payment method and node version agnostic) receipts info as json:
///   [{
///      receipt: <str>, // receipt that can be used for payment referencing and verification
///      recipient: <str>, //payment address for this receipt
///      amount: <int>, // amount
///      extra: <str>, // optional data from payment transaction
///   }]
type ParsePaymentResponseCB = extern fn(command_handle: i32,
                                        resp_json: *const c_char,
                                        cb: Option<extern fn(command_handle_: i32,
                                                             err: ErrorCode,
                                                             receipts_json: *const c_char) -> ErrorCode>) -> ErrorCode;

/// Builds Indy request for doing minting
/// according to this payment method.
///
/// #Params
/// command_handle: command handle to map callback to context
/// wallet_handle: wallet handle
/// submitter_did : DID of request sender
/// outputs_json: The list of outputs as json array:
///   [{
///     recipient: <str>, // payment address of recipient
///     amount: <int>, // amount
///   }]
/// extra: // optional information for payment operation
///
/// #Returns
/// mint_req_json - Indy request for doing minting
type BuildMintReqCB = extern fn(command_handle: i32,
                                wallet_handle: i32,
                                submitter_did: *const c_char,
                                outputs_json: *const c_char,
                                cb: Option<extern fn(command_handle_: i32,
                                                     err: ErrorCode,
                                                     mint_req_json: *const c_char) -> ErrorCode>) -> ErrorCode;

/// Builds Indy request for setting fees for transactions in the ledger
///
/// # Params
/// command_handle: command handle to map callback to context
/// wallet_handle: wallet handle
/// submitter_did : DID of request sender
/// fees_json {
///   txnType1: amount1,
///   txnType2: amount2,
///   .................
///   txnTypeN: amountN,
/// }
///
/// # Return
/// set_txn_fees_json - Indy request for setting fees for transactions in the ledger
type BuildSetTxnFeesReqCB = extern fn(command_handle: i32,
                                      wallet_handle: i32,
                                      submitter_did: *const c_char,
                                      fees_json: *const c_char,
                                      cb: Option<extern fn(command_handle_: i32,
                                                           err: ErrorCode,
                                                           set_txn_fees_json: *const c_char) -> ErrorCode>) -> ErrorCode;

/// Builds Indy get request for getting fees for transactions in the ledger
///
/// # Params
/// command_handle: command handle to map callback to context
/// wallet_handle: wallet handle
/// submitter_did : DID of request sender
///
/// # Return
/// get_txn_fees_json - Indy request for getting fees for transactions in the ledger
type BuildGetTxnFeesReqCB = extern fn(command_handle: i32,
                                      wallet_handle: i32,
                                      submitter_did: *const c_char,
                                      cb: Option<extern fn(command_handle_: i32,
                                                           err: ErrorCode,
                                                           get_txn_fees_json: *const c_char) -> ErrorCode>) -> ErrorCode;

/// Parses response for Indy request for getting fees
///
/// # Params
/// command_handle: command handle to map callback to context
/// resp_json: response for Indy request for getting fees
///
/// # Return
/// fees_json {
///   txnType1: amount1,
///   txnType2: amount2,
///   .................
///   txnTypeN: amountN,
/// }                                          
type ParseGetTxnFeesResponseCB = extern fn(command_handle: i32,
                                           resp_json: *const c_char,
                                           cb: Option<extern fn(command_handle_: i32,
                                                                err: ErrorCode,
                                                                fees_json: *const c_char) -> ErrorCode>) -> ErrorCode;                                                       

/// Builds Indy request for getting information to verify the payment receipt
///
/// # Params
/// command_handle: command handle to map callback to context
/// wallet_handle: wallet handle
/// submitter_did : DID of request sender
/// receipt: payment receipt to verify
///
/// # Return
/// verify_txn_json -- request to be sent to ledger
pub type BuildVerifyPaymentReqCB = extern fn(command_handle: i32,
                                             wallet_handle: i32,
                                             submitter_did: *const c_char,
                                             receipt: *const c_char,
                                             cb: Option<extern fn(command_handle_: i32,
                                                                  err: ErrorCode,
                                                                  verify_txn_json: *const c_char) -> ErrorCode>) -> ErrorCode;

/// Parses Indy response with information to verify receipt
///
/// # Params
/// command_handle: command handle to map callback to context
/// resp_json: response for Indy request for information to verify the payment receipt
///
/// # Return
/// txn_json: {
///     sources: [<str>, ]
///     receipts: [ {
///         recipient: <str>, // payment address of recipient
///         receipt: <str>, // receipt that can be used for payment referencing and verification
///         amount: <int>, // amount
///     }, ]
///     extra: <str>, //optional data
/// }
pub type ParseVerifyPaymentResponseCB = extern fn(command_handle: i32,
                                                  resp_json: *const c_char,
                                                  cb: Option<extern fn(command_handle_: i32,
                                                                       err: ErrorCode,
                                                                       txn_json: *const c_char) -> ErrorCode>) -> ErrorCode;

Payment API

Some API calls have dedicated parameter to determine payment_method. Another part of calls use assumptions about input parameters format to determine payment_method

/// Create the payment address for specified payment method
///
///
/// This method generates private part of payment address
/// and stores it in a secure place. Ideally it should be
/// secret in libindy wallet (see crypto module).
///
/// Note that payment method should be able to resolve this
/// secret by fully resolvable payment address format.
///
/// #Params
/// command_handle: command handle to map callback to context
/// wallet_handle: wallet handle where to save new address
/// payment_method: Payment method to use (for example, 'sov')
/// config: payment address config as json:
///   {
///     seed: <str>, // allows deterministic creation of payment address
///   }
///
/// #Returns
/// payment_address - public identifier of payment address in fully resolvable payment address format
pub extern fn indy_create_payment_address(command_handle: i32,
                                          wallet_handle: i32,
                                          payment_method: *const c_char,
                                          config: *const c_char,
                                          cb: Option<extern fn(command_handle_: i32,
                                                               err: ErrorCode,
                                                               payment_address: *const c_char) -> ErrorCode>) -> ErrorCode {}
                                                               
/// Lists all payment addresses that are stored in the wallet
///
/// #Params
/// command_handle: command handle to map callback to context
/// wallet_handle: wallet to search for payment_addresses in
///
/// #Returns
/// payment_addresses_json - json array of string with json addresses
pub extern fn indy_list_payment_addresses(command_handle: i32,
                                          wallet_handle: i32,
                                          cb: Option<extern fn(command_handle_: i32,
                                                               err: ErrorCode,
                                                               payment_addresses_json: *const c_char)>) -> ErrorCode {}

/// Modifies Indy request by adding information how to pay fees for this transaction
/// according to this payment method.
///
/// This method consumes set of inputs and outputs. The difference between inputs balance
/// and outputs balance is the fee for this transaction.
///
/// Not that this method also produces correct fee signatures.
///
/// Format of inputs is specific for payment method. Usually it should reference payment transaction
/// with at least one output that corresponds to payment address that user owns.
///
/// #Params
/// command_handle: Command handle to map callback to caller context.
/// wallet_handle: wallet handle
/// submitter_did : DID of request sender
/// req_json: initial transaction request as json
/// inputs_json: The list of payment sources as json array:
///   ["source1", ...]
///     - each input should reference paymentAddress
///     - this param will be used to determine payment_method
/// outputs_json: The list of outputs as json array:
///   [{
///     recipient: <str>, // payment address of recipient
///     amount: <int>, // amount
///   }]
/// extra: // optional information for payment operation
///
/// #Returns
/// req_with_fees_json - modified Indy request with added fees info
/// payment_method - used payment method
pub extern fn indy_add_request_fees(command_handle: i32,
                                    wallet_handle: i32,
                                    submitter_did: *const c_char,
                                    req_json: *const c_char,
                                    inputs_json: *const c_char,
                                    outputs_json: *const c_char,
                                    extra: *const c_char,
                                    cb: Option<extern fn(command_handle_: i32,
                                                         err: ErrorCode,
                                                         req_with_fees_json: *const c_char,
                                                         payment_method: *const c_char) -> ErrorCode>) -> ErrorCode {}

/// Parses response for Indy request with fees.
///
/// #Params
/// command_handle: Command handle to map callback to caller context.
/// payment_method: payment method to use
/// resp_json: response for Indy request with fees
///
/// #Returns
/// receipts_json - parsed (payment method and node version agnostic) receipts info as json:
///   [{
///      receipt: <str>, // receipt that can be used for payment referencing and verification
///      recipient: <str>, //payment address of recipient
///      amount: <int>, // amount
///      extra: <str>, // optional data from payment transaction
///   }]
pub extern fn indy_parse_response_with_fees(command_handle: i32,
                                            payment_method: *const c_char,
                                            resp_json: *const c_char,
                                            cb: Option<extern fn(command_handle_: i32,
                                                                 err: ErrorCode,
                                                                 receipts_json: *const c_char) -> ErrorCode>) -> ErrorCode {}

/// Builds Indy request for getting sources list for payment address
/// according to this payment method.
///
/// #Params
/// command_handle: Command handle to map callback to caller context.
/// wallet_handle: wallet handle
/// submitter_did : DID of request sender
/// payment_address: target payment address
///
/// #Returns
/// get_sources_txn_json - Indy request for getting sources list for payment address
/// payment_method - used payment method
pub extern fn indy_build_get_payment_sources_request(command_handle: i32,
                                                     wallet_handle: i32,
                                                     submitter_did: *const c_char,
                                                     payment_address: *const c_char,
                                                     cb: Option<extern fn(command_handle_: i32,
                                                                          err: ErrorCode,
                                                                          get_sources_txn_json: *const c_char,
                                                                          payment_method: *const c_char) -> ErrorCode>) -> ErrorCode {}

/// Parses response for Indy request for getting sources list.
///
/// #Params
/// command_handle: Command handle to map callback to caller context.
/// payment_method: payment method to use.
/// resp_json: response for Indy request for getting sources list
///
/// #Returns
/// sources_json - parsed (payment method and node version agnostic) sources info as json:
///   [{
///      source: <str>, // source input
///      paymentAddress: <str>, //payment address for this source
///      amount: <int>, // amount
///      extra: <str>, // optional data from payment transaction
///   }]
pub extern fn indy_parse_get_payment_sources_response(command_handle: i32,
                                                      payment_method: *const c_char,
                                                      resp_json: *const c_char,
                                                      cb: Option<extern fn(command_handle_: i32,
                                                                           err: ErrorCode,
                                                                           sources_json: *const c_char) -> ErrorCode>) -> ErrorCode {}

/// Builds Indy request for doing payment
/// according to this payment method.
///
/// This method consumes set of inputs and outputs.
///
/// Format of inputs is specific for payment method. Usually it should reference payment transaction
/// with at least one output that corresponds to payment address that user owns.
///
/// #Params
/// command_handle: Command handle to map callback to caller context.
/// wallet_handle: wallet handle
/// submitter_did : DID of request sender
/// inputs_json: The list of payment sources as json array:
///   ["source1", ...]
///   Note that each source should reference payment address
/// outputs_json: The list of outputs as json array:
///   [{
///     recipient: <str>, // payment address of recipient
///     amount: <int>, // amount
///   }]
/// extra: // optional information for payment operation
///
/// #Returns
/// payment_req_json - Indy request for doing payment
/// payment_method - used payment method
pub extern fn indy_build_payment_req(command_handle: i32,
                                     wallet_handle: i32,
                                     submitter_did: *const c_char,
                                     inputs_json: *const c_char,
                                     outputs_json: *const c_char,
                                     extra: *const c_char,
                                     cb: Option<extern fn(command_handle_: i32,
                                                          err: ErrorCode,
                                                          payment_req_json: *const c_char,
                                                          payment_method: *const c_char) -> ErrorCode>) -> ErrorCode {}

/// Parses response for Indy request for payment txn.
///
/// #Params
/// command_handle: Command handle to map callback to caller context.
/// payment_method: payment method to use
/// resp_json: response for Indy request for payment txn
///
/// #Returns
/// receipts_json - parsed (payment method and node version agnostic) receipts info as json:
///   [{
///      receipt: <str>, // receipt that can be used for payment referencing and verification
///      recipient: <str>, // payment address of recipient
///      amount: <int>, // amount
///      extra: <str>, // optional data from payment transaction
///   }]
pub extern fn indy_parse_payment_response(command_handle: i32,
                                          payment_method: *const c_char,
                                          resp_json: *const c_char,
                                          cb: Option<extern fn(command_handle_: i32,
                                                               err: ErrorCode,
                                                               receipts_json_json: *const c_char) -> ErrorCode>) -> ErrorCode {}

/// Builds Indy request for doing minting
/// according to this payment method.
///
/// #Params
/// command_handle: Command handle to map callback to caller context.
/// wallet_handle: wallet handle
/// submitter_did : DID of request sender
/// outputs_json: The list of outputs as json array:
///   [{
///     recipient: <str>, // payment address of recipient
///     amount: <int>, // amount
///   }]
/// extra: // optional information for mint operation
///
/// #Returns
/// mint_req_json - Indy request for doing minting
/// payment_method - used payment method
pub extern fn indy_build_mint_req(command_handle: i32,
                                  wallet_handle: i32,
                                  submitter_did: *const c_char,
                                  outputs_json: *const c_char,
                                  extra: *const c_char,
                                  cb: Option<extern fn(command_handle_: i32,
                                                       err: ErrorCode,
                                                       mint_req_json: *const c_char,
                                                       payment_method: *const c_char) -> ErrorCode>) -> ErrorCode {}

/// Builds Indy request for setting fees for transactions in the ledger
///
/// # Params
/// command_handle: Command handle to map callback to caller context.
/// wallet_handle: wallet handle
/// submitter_did : DID of request sender
/// payment_method: payment method to use
/// fees_json {
///   txnType1: amount1,
///   txnType2: amount2,
///   .................
///   txnTypeN: amountN,
/// }
/// # Return
/// set_txn_fees_json - Indy request for setting fees for transactions in the ledger
pub extern fn indy_build_set_txn_fees_req(command_handle: i32,
                                          wallet_handle: i32,
                                          submitter_did: *const c_char,
                                          payment_method: *const c_char,
                                          fees_json: *const c_char,
                                          cb: Option<extern fn(command_handle_: i32,
                                                               err: ErrorCode,
                                                               set_txn_fees_json: *const c_char) -> ErrorCode>) -> ErrorCode {}

/// Builds Indy get request for getting fees for transactions in the ledger
///
/// # Params
/// command_handle: Command handle to map callback to caller context.
/// wallet_handle: wallet handle
/// submitter_did : DID of request sender
/// payment_method: payment method to use
///
/// # Return
/// get_txn_fees_json - Indy request for getting fees for transactions in the ledger
pub extern fn indy_build_get_txn_fees_req(command_handle: i32,
                                          wallet_handle: i32,
                                          submitter_did: *const c_char,
                                          payment_method: *const c_char,
                                          cb: Option<extern fn(command_handle_: i32,
                                                               err: ErrorCode,
                                                               get_txn_fees_json: *const c_char) -> ErrorCode>) -> ErrorCode {}

/// Parses response for Indy request for getting fees
///
/// # Params
/// command_handle: Command handle to map callback to caller context.
/// payment_method: payment method to use
/// resp_json: response for Indy request for getting fees
///
/// # Return
/// fees_json {
///   txnType1: amount1,
///   txnType2: amount2,
///   .................
///   txnTypeN: amountN,
/// }
pub extern fn indy_parse_get_txn_fees_response(command_handle: i32,
                                               payment_method: *const c_char,
                                               resp_json: *const c_char,
                                               cb: Option<extern fn(command_handle_: i32,
                                                                    err: ErrorCode,
                                                                    fees_json: *const c_char) -> ErrorCode>) -> ErrorCode {}

/// Builds Indy request for information to verify the payment receipt
///
/// # Params
/// command_handle: Command handle to map callback to caller context.
/// wallet_handle: wallet handle
/// submitter_did : DID of request sender
/// receipt: payment receipt to verify
///
/// # Return
/// verify_txn_json: Indy request for verification receipt
/// payment_method: used payment method
#[no_mangle]
pub extern fn indy_build_verify_payment_req(command_handle: i32,
                                            wallet_handle: i32,
                                            submitter_did: *const c_char,
                                            receipt: *const c_char,
                                            cb: Option<extern fn(command_handle_: i32,
                                                                 err: ErrorCode,
                                                                 verify_txn_json: *const c_char,
                                                                 payment_method: *const c_char)>) -> ErrorCode {}

/// Parses Indy response with information to verify receipt
///
/// # Params
/// command_handle: Command handle to map callback to caller context.
/// payment_method: payment method to use
/// resp_json: response of the ledger for verify txn
///
/// # Return
/// txn_json: {
///     sources: [<str>, ]
///     receipts: [ {
///         recipient: <str>, // payment address of recipient
///         receipt: <str>, // receipt that can be used for payment referencing and verification
///         amount: <int>, // amount
///     } ],
///     extra: <str>, //optional data
/// }
#[no_mangle]
pub extern fn indy_parse_verify_payment_response(command_handle: i32,
                                                 payment_method: *const c_char,
                                                 resp_json: *const c_char,
                                                 cb: Option<extern fn(command_handle_: i32,
                                                                      err: ErrorCode,
                                                                      txn_json: *const c_char)>) -> ErrorCode {}

/// Signs a message with a payment address.
///
/// #Params
/// command_handle: command handle to map callback to user context.
/// wallet_handle: wallet handler (created by open_wallet).
/// address: payment address of message signer. The key must be created by calling indy_create_address
/// message_raw: a pointer to first byte of message to be signed
/// message_len: a message length
/// cb: Callback that takes command result as parameter.
///
/// #Returns
/// a signature string
///
/// #Errors
/// Common*
/// Wallet*
/// Crypto*
#[no_mangle]
pub extern fn indy_sign_with_address(command_handle: CommandHandle,
                                     wallet_handle: WalletHandle,
                                     address: *const c_char,
                                     message_raw: *const u8,
                                     message_len: u32,
                                     cb: Option<extern fn(command_handle_: CommandHandle,
                                                          err: ErrorCode,
                                                          signature_raw: *const u8,
                                                          signature_len: u32)>) -> ErrorCode {
    trace!("indy_sign_with_address: >>> wallet_handle: {:?}, address: {:?}, message_raw: {:?}, message_len: {:?}",
           wallet_handle, address, message_raw, message_len);
    check_useful_c_str!(address, ErrorCode::CommonInvalidParam3);
    check_useful_c_byte_array!(message_raw, message_len, ErrorCode::CommonInvalidParam4, ErrorCode::CommonInvalidParam5);
    check_useful_c_callback!(cb, ErrorCode::CommonInvalidParam6);

    trace!("indy_sign_with_address: entities >>> wallet_handle: {:?}, address: {:?}, message_raw: {:?}, message_len: {:?}",
           wallet_handle, address, message_raw, message_len);

    let result = CommandExecutor::instance()
        .send(Command::Payments(
            PaymentsCommand::SignWithAddressReq(wallet_handle,
                                                address,
                                                message_raw,
                                                Box::new(move |result| {
                                                    let (err, signature) = prepare_result_1!(result, Vec::new());
                                                    trace!("indy_sign_with_address: signature: {:?}", signature);
                                                    let (signature_raw, signature_len) = ctypes::vec_to_pointer(&signature);
                                                    cb(command_handle, err, signature_raw, signature_len)
                                        }))
        ));


    let res = prepare_result!(result);

    trace!("indy_sign_with_address: <<< res: {:?}", res);

    res
}

/// Verify a signature with a payment address.
///
/// #Params
/// command_handle: command handle to map callback to user context.
/// address: payment address of the message signer
/// message_raw: a pointer to first byte of message that has been signed
/// message_len: a message length
/// signature_raw: a pointer to first byte of signature to be verified
/// signature_len: a signature length
/// cb: Callback that takes command result as parameter.
///
/// #Returns
/// valid: true - if signature is valid, false - otherwise
///
/// #Errors
/// Common*
/// Wallet*
/// Ledger*
/// Crypto*
#[no_mangle]
pub extern fn indy_verify_with_address(command_handle: CommandHandle,
                                       address: *const c_char,
                                       message_raw: *const u8,
                                       message_len: u32,
                                       signature_raw: *const u8,
                                       signature_len: u32,
                                       cb: Option<extern fn(command_handle_: CommandHandle,
                                                            err: ErrorCode,
                                                            result: bool)>) -> ErrorCode {
    trace!("indy_verify_with_address: >>> address: {:?}, message_raw: {:?}, message_len: {:?}, signature_raw: {:?}, signature_len: {:?}",
           address, message_raw, message_len, signature_raw, signature_len);

    check_useful_c_str!(address, ErrorCode::CommonInvalidParam2);
    check_useful_c_byte_array!(message_raw, message_len, ErrorCode::CommonInvalidParam3, ErrorCode::CommonInvalidParam4);
    check_useful_c_byte_array!(signature_raw, signature_len, ErrorCode::CommonInvalidParam5, ErrorCode::CommonInvalidParam6);
    check_useful_c_callback!(cb, ErrorCode::CommonInvalidParam7);

    trace!("indy_verify_with_address: entities >>> address: {:?}, message_raw: {:?}, message_len: {:?}, signature_raw: {:?}, signature_len: {:?}",
           address, message_raw, message_len, signature_raw, signature_len);

    let result = CommandExecutor::instance()
        .send(Command::Payments(PaymentsCommand::VerifyWithAddressReq(
            address,
            message_raw,
            signature_raw,
            Box::new(move |result| {
                let (err, valid) = prepare_result_1!(result, false);
                trace!("indy_verify_with_address: valid: {:?}", valid);
                cb(command_handle, err, valid)
            })
        )));

    let res = prepare_result!(result);

    trace!("indy_verify_with_address: <<< res: {:?}", res);

    res
}

Decentralized Key Management

Introduction

A decentralized key management system (DKMS) is an approach to cryptographic key management where there is no central authority. DKMS leverages the security, immutability, availability, and resiliency properties of distributed ledgers to provide highly scalable key distribution, verification, and recovery.

Key Types

DKMS uses the following key types:

  1. Master keys: Keys that are not cryptographically protected. They are distributed manually or initially installed and protected by procedural controls and physical or electronic isolation.
  2. Key encrypting keys: Symmetric or public keys used for key transport or storage of other keys.
  3. Data keys: Used to provide cryptographic operations on user data (e.g., encryption, authentication).

The keys at one level are used to protect items at a lower level. Consequently, special measures are used to protect master keys, including severely limiting access and use, hardware protection, and providing access to the key only under shared control.

Key Loss

Key loss means the owner no longer controls the key and it can assume there is no further risk of compromise. For example devices unable to function due to water, electricity, breaking, fire, hardware failure, acts of God, etc.

Compromise

Key compromise means that private keys and/or master keys have become or can become known either passively or actively.

Recovery

In decentralized identity management, recovery is important since identity owners have no “higher authority” to turn to for recovery.

  1. Offline recovery uses physical media or removable digital media to store recovery keys.
  2. Social recovery employs entities trusted by the identity owner called “trustees” who store recovery data on an identity owners behalf—typically in the trustees own agent(s).

These methods are not exclusive and should be combined with key rotation and revocation for proper security.

  1. Design and architecture
  2. Public Registry for Agent Authorization Policy. An identity owner create a policy on the ledger that defines its agents and their authorizations. Agents while acting on the behalf of the identity owner need to prove that they are authorised. More details

CLI plugins

This design proposes the way to support plugins in Indy CLI.

Goals and ideas

  • Libindy now allows to plug 2 type of functionality for the moment:
    • Custom wallet storage
    • Custom payment methods
  • To register plugins libindy provides API calls that allows to register C-handlers for each type of plugged operation
  • In current vision libraries that implement libindy plugins should provide some kind of public “init” function that will call internally libindy API to register handlers. So registration of plugin is just calling of C function.
  • “Init” function should have just one param. It is callback that returns libindy error code.
  • CLI can provide command line option that will allow to point the name of plugin dynamic library and the name of “init” function. On start CLI will call dlopen (or LoadLibrary) with right options to perform names linking. CLI will lookup for init function by name and call it.
  • Also we need to provide CLI command to load plugin similar way

Linking

Linking

Command line param to load plugins on start

indy-cli --plugins <lib-1-name>:<init-func-1-name>,...,<lib-n-name>:<init-func-n-name>

Example:

indy-cli --plugins libnullpay:nullpay_init,libstorage:storage_init

Command to load plugin

indy> load-plugin library=<library-name> initializer=<init-func-name>

Example:

indy> load-plugin library=libnullpay initializer=nullpay_init

Payment Interface

This design proposes the list of commands to Indy CLI to handle payments.

Goals and ideas

  • Indy CLI should provide ability to perform the main payments operations:
    • Creation of payment address
    • Listing of payment addresses
    • Getting list of sources for payment address
    • Sending payment transaction
    • Adding fees to transactions
    • Getting transactions fees amount
  • Abstraction level should correspond to Indy SDK. For example, don’t hide source abstraction. In the future we can add new commands to increase abstractions level.

New CLI commands

Create payment address

Create payment address for specific payment method in the wallet

indy> payment-address create payment_method=<payment-method> seed=[<seed>]

Returns:

  • Success or error message
List payment addresses

List payment addresses in the wallet

indy> payment-address list

Returns:

  • Table with columns: Payment Address, Payment Method
Send GET_PAYMENT_SOURCES request

Send request to get list of sources for specified payment addresses

indy> ledger get-payment-sources payment_address=<payment-address>

Returns:

  • Table with columns: Source, Payment Address, Amount, Extra
Send PAYMENT transaction

Send payment transaction

indy> ledger payment inputs=<source-1>,..,<source-n> outputs=(<recipient-0>,<amount>),..,(<recipient-n>,<amount>) [extra=<extra>]

Returns:

  • Table with columns: Receipt, Recipient Payment Address, Amount, Extra

Note that “source-n” is identifier presented in “Source” column of ledger get-sources command output

Send GET_FEES request

Send request to get fees amount for ledger transactions

indy> ledger get-fees payment_method=<payment_method>

Returns:

  • Table with columns: Transaction, Amount
Prepare MINT transaction

Prepare MINT transaction as json.

indy> ledger mint-prepare outputs=(<recipient-0>,<amount-0>),..,(<recipient-n>,<amount-n>) [extra=<extra>]

Returns:

  • MINT transaction json

Sending MINT process is the following:

  • Steward 1 calls ledger mint-prepare
  • Signs it by calling ledger sign-multi
  • Sends the request json to Steward 2 (now we have 1 signature)
  • Second Steward signs it by calling ledger sign-multi
  • Sends the request json to Steward 3 (now we have 2 signature)
  • All Stewards sign the request
  • The latest Steward calls ledger send-custom to send request signed by all Stewards
Prepare SET_FEES transaction

Prepare SET_FEES transaction as json.

indy> ledger set-fees-prepare payment_method=<payment_method> fees=<txn-type-1>:<amount-1>,...,<txn-type-n>:<amount-n>

Returns:

  • SET_FEES transaction json

Sending SET_FEES process is the following:

  • Steward 1 calls ledger set-fees-prepare
  • Signs it by calling ledger sign-multi
  • Sends the request json to Steward 2 (now we have 1 signature)
  • Second Steward signs it by calling ledger sign-multi
  • Sends the request json to Steward 3 (now we have 2 signature)
  • All Stewards sign the request
  • The latest Steward calls ledger send-custom to send request signed by all Stewards
Send VERIFY_PAYMENT_RECEIPT request

Send request to get information to verify the payment receipt

ledger verify-payment-receipt <receipts>

Returns:

  • Receipt info as json
Sign the transaction (for multi-sign case)

Add signature (for multi-sign case) by current DID to transaction json.

indy> ledger sign-multi txn=<txn-json>

Returns:

  • Transaction json with added signature

Existing commands update

All commands to send domain transactions require new optional params to add transactions fees:

[fees_inputs=<source-1>,..,<source-n>] [fees_outputs=(<recipient-0>,<amount>),..,(<recipient-n>,<amount>)] [extra=<extra>]

Note that “source-n” is identifier presented in “Source” column of ledger get-sources command output

Legend

There are some types of requests to Nodes in the Pool which allow the use of StateProof (SP) optimization in Client-Node communication. Instead of sending requests to all nodes in the Pool, a client can send a request to a single Node and expect a StateProof signed by a Boneh–Lynn–Shacham (BLS) multi-signature.

BLS multi-signature (BLS MS) guaranties that there was a consensus of Nodes which signed some State identified by the State RootHash. StateProof (SP) is small amount of data which allows the verification of particular values against the RootHash. The combination of BLS MS and SP allows clients to be sure that the response of single node is a part of the State signed by a sufficient number of Nodes.

Goals

Libindy also allows the building and sending of supported requests via a plugable interface. It is nice to have a way to support BLS MS and SP verification for these plugged transactions.

The implementation of math for SP verification is a bit complicated to include in plugin logic. Therefore, libindy should perform all of the math calculations inside the SDK. A plugin should provide a handler to parse the custom reply to a fixed data structure.

API

The signature of the handler is described below together with the custom free call to deallocate result data.

extern fn CustomTransactionParser(reply_from_node: *const c_char, parsed_sp: *mut *const c_char) -> ErrorCode;
extern fn CustomFree(data: *mut c_char) -> ErrorCode;

The libindy API will contain a call to register the handler for a specific transaction type:

extern fn indy_register_transaction_parser_for_sp(command_handle: i32,
                                                  txn_type: *const c_char,
                                                  parser: CustomTransactionParser,
                                                  free: CustomFree,
                                                  cb: extern fn(command_handle_: i32, err: ErrorCode)) -> ErrorCode;

Parsed Data structure

A plugin should parse reply_from_node and return back to libindy the parsed data as JSON string. Actually this data is an array of entities, each of them is described as a SP Trie and a set of key-value pairs to verify against this trie. It can be represented as Vec<ParsedSP> serialized to JSON.

/**
 Single item to verification:
 - SP Trie with RootHash
 - BLS MS
 - set of key-value to verify
*/
struct ParsedSP {
    /// encoded SP Trie transferred from Node to Client
    proof_nodes: String,
    /// RootHash of the Trie, start point for verification. Should be same with appropriate filed in BLS MS data
    root_hash: String,
    /// entities to verification against current SP Trie
    kvs_to_verify: KeyValuesInSP,
    /// BLS MS data for verification
    multi_signature: serde_json::Value,
}

/**
 Variants of representation for items to verify against SP Trie
 Right now 2 options are specified:
 - simple array of key-value pair
 - whole subtrie
*/
enum KeyValuesInSP {
    Simple(KeyValueSimpleData),
    SubTrie(KeyValuesSubTrieData),
}

/**
 Simple variant of `KeyValuesInSP`.

 All required data already present in parent SP Trie (built from `proof_nodes`).
 `kvs` can be verified directly in parent trie

 Encoding of `key` in `kvs` is defined by verification type
*/
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
pub struct KeyValueSimpleData {
    pub kvs: Vec<(String /* key */, Option<String /* val */>)>,
    #[serde(default)]
    pub verification_type: KeyValueSimpleDataVerificationType
}

/**
 Options of common state proof check process
*/
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
#[serde(tag = "type")]
pub enum KeyValueSimpleDataVerificationType {
    /* key should be base64-encoded string */
    Simple,
    /* key should be plain string */
    NumericalSuffixAscendingNoGaps(NumericalSuffixAscendingNoGapsData)
}

#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
pub struct NumericalSuffixAscendingNoGapsData {
    pub from: Option<u64>,
    pub next: Option<u64>,
    pub prefix: String
}

/**
 Subtrie variant of `KeyValuesInSP`.

 In this case Client (libindy) should construct subtrie and append it into trie based on `proof_nodes`.
 After this preparation each kv pair can be checked.
*/
struct KeyValuesSubTrieData {
    /// base64-encoded common prefix of each pair in `kvs`. Should be used to correct merging initial trie and subtrie
    sub_trie_prefix: Option<String>,
    kvs: Vec<(String /* b64-encoded key_suffix */, Option<String /* val */>)>,
}

Expected libindy and plugin workflow is the following:

  1. Libindy receives a reply from a Node, performs initial processing and passes raw reply to plugin.
  2. Plugin parses reply from the Node and specifies one (or more) SP Trie with metadata and items for verification.
  3. Each SP Trie is described by the plugin as ParsedSP:
    1. Set of encoded nodes of the SP Trie, received from Node - proof_nodes. May be fetched from response “as is”.
    2. RootHash of this Trie. May be fetched from the response “as is” also.
    3. BLS MS data. Again may be fetched from the response “as is”.
    4. Key-value items for verification. Here the plugin should define the correct keys (path in the trie) and their corresponding values.
  4. Plugin returns serialized as JSON array of ParsedSP
  5. For each ParsedSP libindy:
    1. build base trie from proof_nodes
    2. if item to verify is SubTrie, construct this subtrie from (key-suffix, value) pairs and merge it with trie from clause above
    3. iterate other key-value pairs and verify that trie (with signed root_hash) contains value at specified key
    4. verify multi-signature
  6. If any verification fails, libindy will ignore that particular SP + BLS MS and try to request the same data from another node, or collect a consensus of the same replies from a sufficient number of Nodes.

Below is the JSON structure for Simple case.

[
 {
   "proof_nodes": "string with serialized SP tree",
   "root_hash": "string with root hash",
   "kvs_to_verify": {
     "type": "simple",
     "kvs": [["key1", "value1"], ["key2", "value2"]]
   },
   "multi_signature": "JSON object from Node`s reply as is"
 }
]

Simple and SubTrie Verification

Some use cases require verification of multiple of key-value pairs in one Trie. Moreover, there is possible situation when a client would like to verify the whole subtrie. In this case, the amount of data transferred from Node to Client can be significantly reduced. Instead of including all nodes for SP verification to proof_nodes, a Node can include only a prefix path down to a subtrie. The entire subtrie to be verified can be restored on the Client side from key-value pairs and combined with the prefix part.

Wallet Export/Import Design

Currently all encryption of the wallet is performed in libindy. Storage implementations may be plugged in. This design proposes portable export/import functionality so the “lock-in” of the user to the software which created the wallet is avoided.

Goals and ideas

  • Alow users to export their wallets so the can do the backup or move their secret data to different agency or different device.
    • Export file will be encrypted with export key.
    • Export should contain the whole wallet data (including secrets).
    • Export should be done in a streaming way so big wallets may be exported on machines with conservative memory.
  • Alow users to import exported wallet.
    • Import is alowed only on empty wallet - Therefore import is done with one create + import operation.
    • User should provide key used for export, so export file may be decrypted.
    • User should provide new master key used for opening newly created wallet from import.
    • Import should be done in a streaming way so big wallets may be imported on machines with conservative memory.
  • Export/Import should work for all storage implementations.
  • Expose two public API functions, for export and for create + import wallet.

Public API

/// Exports opened wallet's content using key and path provided in export_config_json
///
/// #Params
/// command_handle: command handle to map callback to caller context
/// wallet_handle: wallet handle (created by open_wallet)
/// export_config_json: JSON containing settings for input operation.
///   {
///     "path": path of the file in which the wallet will be exported
///     "key": passphrase used to derive export key
///   }
///
/// #Returns
/// Error code
///
/// #Errors
/// Common*
/// Wallet*
extern pub fn indy_error_t indy_export_wallet(command_handle: i32,
                                              wallet_handle: i32,
                                              export_config_json: *const c_char,
                                              cb: Option<extern fn(xcommand_handle: i32, err: ErrorCode)>) -> ErrorCode {}

/// Creates a new secure wallet with the given unique name and then imports its content
/// according to fields provided in import_config
///
/// #Params
/// command_handle: command handle to map callback to caller context
/// pool_name: Name of the pool that corresponds to this wallet
/// name: Name of the wallet
/// storage_type(optional): Type of the wallet storage. Defaults to 'default'.
///                  Custom storage types can be registered with indy_register_wallet_storage call.
/// config(optional): Wallet configuration json.
///   {
///       "storage": <object>  List of supported keys are defined by wallet type.
///   }
/// credentials: Wallet credentials json
///   {
///       "key": string,
///       "storage": Optional<object>  List of supported keys are defined by wallet type.
///
///   }
/// import_config_json: JSON containing settings for input operation.
///   {
///     "path": path of the file that contains exported wallet content
///     "key": passphrase used to derive export key
///   }
///
/// #Returns
/// Error code
///
/// #Errors
/// Common*
/// Wallet*
extern pub fn indy_import_wallet(command_handle: i32,
                                 pool_name: *const c_char,
                                 name: *const c_char,
                                 storage_type: *const c_char,
                                 config: *const c_char,
                                 credentials: *const c_char,
                                 import_config_json: *const c_char,
                                 cb: Option<extern fn(xcommand_handle: i32, err: ErrorCode)>) -> ErrorCode {}

Deriving the key from passphrase

For deriving keys from passphrase Argon2 memory-hard function is used with random salt.

File format

— plain stream —

  • header_length: length of the serialized header as 4b unsigned little endian integer
  • header: MessagePack serialized header entity

— encrypted stream —

  • header_hash: 32B SHA-256 hash of the header.
  • record1_length: length the serialized record as 4b unsigned little endian integer
  • record1: MessagePack serialized record entity
  • recordN_length: length the serialized record as 4b unsigned little endian integer
  • recordN: MessagePack serialized record entity
  • STOP: 4 zero bytes. Allows to make sure that there was no truncation of export file

Where:

pub struct Header {
    pub encryption_method: EncryptionMethod, // Method of encryption for encrypted stram
    pub time: u64, // Export time in seconds from UNIX Epoch
    pub version: u32, // Version of header
}

pub enum EncryptionMethod {
    ChaCha20Poly1305IETF { // **ChaCha20-Poly1305-IETF** cypher in blocks per chunk_size bytes
        salt: Vec<u8>,  // pwhash_argon2i13::Salt as bytes. Random salt used for deriving of key from passphrase
        nonce: Vec<u8>, // chacha20poly1305_ietf::Nonce as bytes. Random start nonce. We increment nonce for each chunk to be sure in export file consistency
        chunk_size: usize, // size of encrypted chunk
    },
}

// Note that we use externally tagged enum serialization and header will be represented as:
//
// {
//   "encryption_method": {
//     "ChaCha20Poly1305IETF": {
//       "salt": ..,
//       "nonce": ..,
//       "chunk_size": ..,
//     },
//   },
//   "time": ..,
//   "version": ..,
// }

pub struct Record {
    #[serde(rename = "type")]
    pub type_: String, // Wallet record type
    pub id: String, // Wallet record id
    pub value: String, // Wallet record value
    pub tags: HashMap<String, String>, // Wallet record tags
}

The only supported from the beginning encryption method is ChaCha20-Poly1305-IETF cypher in blocks per 1024 bytes (to allow streaming). This is similar encryption as recommended in libsodium secretstream but secretstream was not available in Rust wrapper. Random salt used for deriving of key from passphrase. We increment nonce for each block to be sure in export file consistency. Also we use STOP message in encrypted stream that allows to make sure that there was no truncation of export file.

Payment Interface

This design proposes the list of commands to Indy CLI to handle export/import wallet operations.

Goals and ideas

Indy CLI should provide ability to perform following operation:

  • Allow users to export their wallets so the can do the backup or move to different device.
  • Allow users to import exported wallet.

New CLI commands

Export wallet

Exports opened wallet to the specified file.

indy> wallet export export_path=<path-to-file> export_key=[<export key>]

Returns:

  • Success or error message
Import wallet

Create new wallet and then import content from the specified file

indy> wallet import <wallet name> key=<key> export_path=<path-to-file> export_key=<key used for export>  [storage_type=<storage_type>] [storage_config={config json}]

Returns:

  • Success or error message

Wallet Query Language

This language will be used to define queries in Non-secrets, Anoncreds search APIs.

query = {subquery}
subquery = {subquery, ..., subquery} - WHERE subquery AND ... AND subquery
subquery = $or: [{subquery},..., {subquery}] - WHERE subquery OR ... OR subquery
subquery = $not: {subquery} - Where NOT (subquery)
subquery = "tagName": tagValue - WHERE tagName == tagValue
subquery = "tagName": {$neq: tagValue} - WHERE tagName != tagValue
subquery = "tagName": {$gt: tagValue} - WHERE tagName > tagValue
subquery = "tagName": {$gte: tagValue} - WHERE tagName >= tagValue
subquery = "tagName": {$lt: tagValue} - WHERE tagName < tagValue
subquery = "tagName": {$lte: tagValue} - WHERE tagName <= tagValue
subquery = "tagName": {$like: tagValue} - WHERE tagName LIKE tagValue
subquery = "tagName": {$in: [tagValue, ..., tagValue]} - WHERE tagName IN (tagValue, ..., tagValue)

Tag types

There are two types of tags:

  • Un-encrypted - Tag name starts with “~”. That tag will be stored un-encrypted that will allow usage of this tag in complex search queries (comparison, predicates).
  • Encrypted - That tag will be stored encrypted. The tag can be searched only for exact matching.

NOTE: Combinators $or, $and, $not can be used with both tag types.

Contributing

Review our MAINTAINERS file

Then peruse the following documents:

Release process

Indy SDK components

Indy SDK contains the following components managed as dedicated packages:

  • Libindy
  • Wrappers for programming languages:
    • Python
    • Java
    • ObjectiveC (iOS)
    • .Net
  • Indy CLI

Release artifacts

Indy SDK release process produces the following artifacts for components:

  • Libindy
    • Ubuntu deb package. Available in https://repo.sovrin.org/sdk/lib/apt/xenial/{master|stable|rc}/libindy_{version}.deb
    • Windows binaries as zip-archive with dependencies. Available in https://repo.sovrin.org/windows/libindy/{master,stable,rc}/{version}/libindy_{version}.zip
    • iOS Cocoapods package. Available in https://repo.sovrin.org/ios/libindy/{master|stable}/libindy-core/{version}/libindy.tar.gz. No support in CD pipeline now, but we perform manual builds periodically.
    • MacOS binaries planned, but no support in CD pipeline now.
    • RHEL binaries planned, but no support in CD pipeline now.
  • Wrappers for programming languages:
    • Python wrapper PyPy package. Available in PyPi as python3-indy package.
    • Java wrapper maven package. Available as org.hyperledger/indy package in https://repo.sovrin.org/repository/maven-public maven repo.
    • ObjectiveC (iOS) Cocoapods package. Available in https://repo.sovrin.org/ios/libindy/{master|stable}/libindy-objc/{version}/libindy-objc.tar.gz.
    • .Net. Packages planned, but no support in CD pipeline now.
  • Indy CLI tool
    • Ubuntu deb package. Available in https://repo.sovrin.org/sdk/lib/apt/xenial/{master|stable|rc}/indy-cli_{version}.deb
    • Windows binaries as zip-archive with dependencies. Available in https://repo.sovrin.org/windows/indy-cli/{master,stable,rc}/{version}/indy-cli_{version}.zip
    • MacOS binaries planned, but no support in CD pipeline now.
    • RHEL binaries planned, but no support in CD pipeline now.

Release channels

Indy SDK release process defines the following release channels:

  • master - development builds for each push to master branch
  • rc - release candidates
  • stable - stable releases These channels don’t correspond exactly to branches; the stable channel is just tags in the rc branch, right now. In the future, this may change.

Compatibility with indy-node

It’s important to understand the relationship between indy-sdk and indy-node as it relates to releases. The SDK generally focuses on adding new features at about the same time that indy-node does, and it spends most of its compatibility testing effort on proving that its new features work with the corresponding features in a newly evolved ledger. Sometimes the SDK may also be released with bug fixes, without the ledger changing– but this flow is less common. For this reason, the most important release sequence is one where the ledger (indy-node) gets a new feature, and the SDK gets the new feature very soon thereafter. The SDK has to prove that it is compatible with the new capability the ledger is exposing.

Thus, the SDK’s master branch may depend on either the latest stable build of indy-node, or on a recent (latest or close-to-latest) master build of indy-node. This means it is a bad idea for consumers of indy-sdk to use the latest master build, because that build might not be compatible with the version of indy-node that’s currently in stable production. Use indy-sdk master branch at your own risk.

Versioning

  • All components are always released together and for simplicity have the same version. It can be changed in the future.
  • Version has format {major}.{minor}.{patch}. We plan to follow Semver rules, but there are exceptions for few first releases to avoid major version increasing too much.
  • RC builds have rc number suffix that will be removed after making build stable
  • Master builds have version of the latest stable release with build number suffix. Note that master builds doesn’t follow Semver. Version increase according to Semver performed in the moment of rc creation.
  • Indy CLI and wrappers depend on libindy. For simplicity, we use exact libindy version that was produced during components release to describe this dependency. In the future after switching to strong Semver we can use less strong dependency to libindy based on Semver.

CI/CD pipelines

Builds creation is automated with deterministic CI/CD pipeline.

Releases process

  • The team uses GitHub flow process with 2 main branches:
    • master - development branch
    • rc - branch for release candidates. Some of release candidates will become stable.
  • The development is performed in GitHub forks with rising of PRs to the master branch.
  • For each PR to the master branch the team performs code review and CI pipeline executes unit and integration tests for all components. Merging is only available if all tests passed on the same commit that will be merged to the master branch.
  • After merge PR to master CD pipeline executes on merged commit. It executes the tests and build new package with increased build number suffix for master channel.
  • When we decide to perform release we:
    • Fork master branch
    • Analyse changes
    • Increment versions for all components. Note that version (at least patch) will be always incremented even if there were no changes in this component.
    • Update release notes
    • Rise the PR (master + versions changes) to rc branch
  • For each PR to rc team performs code review and CI pipeline executes unit and integration tests for all components. Merging is only available if all tests passed on the same commit that will be merged to rc.
  • After merge PR to rc CD pipeline executes on merged commit. It executes the tests and build new package with increased rc number suffix for rc channel. After this CD pipeline is paused and wait for approve to complete.
  • Team executes acceptance testing use created artifacts in release channel.
  • If there is no problems found team approves the release. CD pipeline resumes, moves artifacts to stable release channel and creates Git release tag on corresponded commit.
  • If some problems were found team declines the release and starts creation of hot-fix PR to rc branch. After PR is created release process resumes from rc PR stage.
  • After release performed the team back merges the rc branch to the master branch.

Signing commits

If you are here because you forgot to sign your commits, fear not. Check out how to sign previous commits

We use developer certificate of origin (DCO) in all hyperledger repositories, so to get your pull requests accepted, you must certify your commits by signing off on each commit.

Signing your current commit

  • $ git commit -s -m "your commit message"
  • To see if your commits have been signed off, run $ git log --show-signature
  • If you need to re-sign the most current commit, use $ git commit --amend --no-edit -s.

The -s flag signs the commit message with your name and email.

How to Sign Previous Commits

  1. Use git log --show-signature to see which commits need to be signed.

  2. Go into interactive rebase mode using $ git rebase -i HEAD~X where X is the number of commits up to the most current commit you would like to see.

  3. You will see a list of the commits in a text file. On the line after each commit you need to sign, add exec git commit --amend --no-edit -s with the lowercase -s adding a text signature in the commit body. Example that signs both commits:

    pick 12345 commit message
    exec git commit --amend --no-edit -s
    pick 67890 commit message
    exec git commit --amend --no-edit -s
    
1. If you need to re-sign a bunch of previous commits at once, find the earliest unsigned commit using `git log --show-signature` and use that the HASH of the commit before it in this command: `git rebase --exec 'git commit --amend --no-edit -n -s' -i HASH`. This will sign every commit from most recent to right before the HASH.
1. You will probably need to do a force push `git push -f` if you had previously pushed unsigned commits to remote.

Indy SDK test approach

Components

Indy SDK contains the following parts:

  • libindy - native library that provides high-level API for development of Sovrin-based applications. It provides methods for handling communication with Indy pool, secure wallet, agents communication, sign/verify, encrypt/decrypt, and anoncreds protocol
  • Python Wrapper - FFI based wrapper for native libindy that allows application development with python language
  • Java Wrapper - FFI based wrapper for native libindy that allows application development on Java platform
  • iOS Wrapper - Objective-C based wrapper for native libindy that allows application development on Java platform
  • .Net Wrapper - FFI based wrapper for native libindy that allows application development on .Net platform

Acceptance testing

The Acceptance test procedure consists of the following parts:

  • Functional testing
  • Installability testing
  • Interoperability testing
  • Documentation testing

Functional testing

Functional part of test procedure contains the set of automated system/integration tests that CI/CD execute for each merge commit to master or rc branch. Release artifacts will be created from the same libindy binaries that were used for tests execution. Creation of rc package assumes that automated functional tests are passed. See cd-pipeline.puml for details.

Note that libindy and wrappers also provide the set of unit tests, but they are mostly used to follow TDD approach and don’t follow formal test design.

In the future we expect extending functional test procedure with some manual steps for complex cases that will be performed after rc package is created. Mostly it is relevant for Low cases.

Functional specification

Specification to API calls for now are present as comments in interface parts of source code. See:

  • https://github.com/hyperledger/indy-sdk/tree/master/libindy/include/ (libindy docs)
  • https://github.com/hyperledger/indy-sdk/tree/master/wrappers/python/indy/ (python wrapper docs)
  • https://github.com/hyperledger/indy-sdk/tree/master/wrappers/java/src/main/java/org/hyperledger/indy/sdk/ (java wrapper docs)
  • https://github.com/hyperledger/indy-sdk/blob/master/wrappers/dotnet/indy-sdk-dotnet/Wrapper/ (.Net wrapper docs)
Test groups

We define the following test groups by priority:

  • High cases
  • Medium cases
  • Low cases
High cases

Successful completion of High cases tests indicates Alpha quality.

  • Normal cases. Note that there can be multiple execution branches. We need to cover at each branch. Branches examples:
    • Entity cached in the wallet
    • Entity should be taken from the ledger
  • Error cases that require an explicit recovering procedure. Examples:
    • Invalid wallet credentials
    • No entity found in the wallet
    • No entity found in the ledger
    • Transaction doesn’t allow for current identity
    • Unknown crypto
    • Claim doesn’t correspond to scheme, proof request doesn’t correspond to claim and etc…
    • Revocation registry is full and etc…
Medium cases

Successful completion of High and Medium cases tests indicates Beta quality.

  • Precondition checking:
    • Invalid handle
    • Wallet doesn’t correspond to pool
    • Invalid json format
    • Invalid json structure (missed fields and etc…)
    • Invalid base58
    • Invalid crypto keys length and format
    • Invalid crypto primitives (bigints, points)
    • Invalid complex crypto structures (anoncreds structures mostly)
    • Invalid responses from 3d parties (Ledger, Agent)
Low cases

Successful completion of High, Medium and Low cases tests indicates Production quality.

  • Cases that hard to test: Io errors, timeouts and etc…
Tests specification

Tests specification is provided as the list of test cases for each API call in code grouped by API group, API call, test level. Also there are dedicated “demo” tests mostly intended to provide usage examples.

For current moment we implemented High and Medium cases for libindy (except revocation part of Anoncreds). High cases for Python, Java, iOs, and .Net wrappers.

Note that High cases for wrappers contain the same tests as libindy and we keep this tests cases synced. Architecture of wrapper allows to claim that High cases coverage can be enough for Beta+ quality.

Note that test procedure was created by developers (not professional QA) and can require review and enhancements.

See:

  • https://github.com/hyperledger/indy-sdk/tree/master/libindy/tests (libindy tests)
  • https://github.com/hyperledger/indy-sdk/tree/master/wrappers/python/tests (python wrapper tests)
  • https://github.com/hyperledger/indy-sdk/tree/master/wrappers/java/src/test/java/org/hyperledger/indy/sdk (java wrapper tests)
  • https://github.com/hyperledger/indy-sdk/tree/master/wrappers/ios/libindy-pod/libindy-demoTests (iOS wrapper tests)

Installability testing

Functional testing is performed before artifacts packaging. libindy has some runtime dependencies and we need to be sure that package installation satisfy these dependencies.

I suggest the following:

  • Create simple demo projects on C, Python and Java that will depend on libindy artifacts and move our demo tests to these projects (in the future we need projects for iOS, .Net and NodeJS). We can try to do this in the current sprint.
  • Test libindy and wrapper installation on ubuntu and windows (in the future on macos, rhel, iOS too)
  • Test that these demo projects can be built and run with rc packages

For first release these steps can be performed manually and automated in the future.

Ubuntu testing
  • Run pool (see docker network option in ubuntu readme)
  • build docker image docker build -f ubuntu_acceptance.dockerfile --build-arg indy_sdk_deb="URL to download appropriate version of libindy.deb" .
  • start docker container from images built on previous step docker run -it -v <path/to/indy-sdk/samples>:/home/indy --network=<pool network name> <Image ID> /bin/bash
  • in docker container:
    • Check Java wrapper:
      • cd java
      • set version of indy dependency in pom.xml
      • TEST_POOL_IP=<pool ip> mvn clean compile exec:java -Dexec.mainClass="Main"
      • check results
      • cd ..
    • Check Python wrapper:
      • cd python
      • set version of python3-indy dependency in setup.py
      • python3.6 -m pip install -e .
      • TEST_POOL_IP=<pool ip> python3.6 src/main.py

Interoperability testing

The following interoperability cases are needed:

  • libindy - Node
    • Interoperability with latest Node version. We test it already with functional tests.
    • Backward compatibility of Node will be tested as part of Indy Node acceptance. (See these notes for a discussion about how compatibility relates to branches of indy-node and indy-sdk.)
  • libindy - pyindy:
    • Anoncreds protocol interoperability. It is already implemented as part of functional tests.
  • libindy - libindy
    • Persistent configuration backward compatibility for Major version (Requires test development, IS-312).
    • Persistent wallet backward compatibility for Major version (Requires test development, IS-312).
    • Persistent pool cache backward compatibility for Major version (Requires test development, IS-312).
    • Anoncreds protocol backward compatibility for Major version (Requires test development, IS-312).
    • Agent communication backward compatibility for Major version (Requires test development, IS-312).
  • libindy - wrappers
    • For first releases we plan to release wrappers as same package and claim only exact version interoperability. Current functional test procedure performs this interoperability checking with wrappers functional tests.

For first release we can move with existing functional tests, but future release will require creation of dedicated interoperability tests. These tests can be automated.

Documentation testing

  • Verify Changelog
  • Verify documentation update for all claimed changes