Advanced Usage Examples

This section has examples of using the Python Driver for more advanced use cases such as escrow.

Getting Started

First, make sure you have RethinkDB and BigchainDB installed and running, i.e. you installed them and you ran:

$ rethinkdb
$ bigchaindb configure
$ bigchaindb start

Don’t shut them down! In a new terminal, open a Python shell:

$ python

Now we can import the BigchainDB class and create an instance:

In [1]: from bigchaindb_driver import BigchainDB

In [2]: bdb = BigchainDB()

This instantiates an object bdb of class BigchainDB. When instantiating a BigchainDB object without arguments (as above), it sets the server node URL to http://localhost:9984/api/v1.

Create a Digital Asset

At a high level, a “digital asset” is something which can be represented digitally and can be assigned to a user. In BigchainDB, users are identified by their public key, and the data payload in a digital asset is represented using a generic Python dict.

In BigchainDB, digital assets can be created by doing a special kind of transaction: a CREATE transaction.

In [3]: from bigchaindb_driver.crypto import generate_keypair

Create a test user: alice

In [4]: alice = generate_keypair()

Define a digital asset data payload

In [5]: digital_asset_payload = {'data': {'msg': 'Hello BigchainDB!'}}

In [6]: tx = bdb.transactions.prepare(operation='CREATE',
   ...:                               owners_before=alice.verifying_key,
   ...:                               asset=digital_asset_payload)
   ...: 

All transactions need to be signed by the user creating the transaction.

In [7]: signed_tx = bdb.transactions.fulfill(tx, private_keys=alice.signing_key)

Write the transaction to the bigchain. The transaction will be stored in a backlog where it will be validated, included in a block, and written to the bigchain.

>>> sent_tx = bdb.transactions.send(signed_tx)

Note that the transaction payload returned by the BigchainDB node is equivalent to the signed transaction payload.

>>> sent_tx == signed_tx
True
In [8]: signed_tx
Out[8]: 
{'id': 'da8b099d83e5f1e4ba6ed5719cfe40a20029c2e9c1689029e981735fa49378e8',
 'transaction': {'asset': {'data': {'msg': 'Hello BigchainDB!'},
   'divisible': False,
   'id': '47f4e1f7-ef85-4e44-9de5-6877b9e1da64',
   'refillable': False,
   'updatable': False},
  'conditions': [{'amount': 1,
    'cid': 0,
    'condition': {'details': {'bitmask': 32,
      'public_key': '5M8MoLhoeJ9TtoMti2vvtg4T5pAKCqKD8MJjjakfZ3Cj',
      'signature': None,
      'type': 'fulfillment',
      'type_id': 4},
     'uri': 'cc:4:20:QJavLVcbFP5cpKXTgDo9awtelABrrLEXeWl-68Tw53A:96'},
    'owners_after': ['5M8MoLhoeJ9TtoMti2vvtg4T5pAKCqKD8MJjjakfZ3Cj']}],
  'fulfillments': [{'fid': 0,
    'fulfillment': 'cf:4:QJavLVcbFP5cpKXTgDo9awtelABrrLEXeWl-68Tw53BmQRdGhFoMdDr6ewlDMRan1yCpNeZC2sGZwPyq8iPej0g9-wmRdA68euVriGf2SFldF8TShKKKl9IKZM02guAG',
    'input': None,
    'owners_before': ['5M8MoLhoeJ9TtoMti2vvtg4T5pAKCqKD8MJjjakfZ3Cj']}],
  'metadata': None,
  'operation': 'CREATE'},
 'version': 1}

Read the Creation Transaction from the DB

After a couple of seconds, we can check if the transactions was included in the bigchain:

# Retrieve a transaction from the bigchain
>>> tx_retrieved = bdb.transactions.retrieve(tx['id'])

The new owner of the digital asset is now alice which is the public key, aka verifying key of alice.

In [9]: alice.verifying_key
Out[9]: '5M8MoLhoeJ9TtoMti2vvtg4T5pAKCqKD8MJjjakfZ3Cj'

Transfer the Digital Asset

Now that alice has a digital asset assigned to her, she can transfer it to another person. Transfer transactions require an input. The input will be the transaction id of a digital asset that was assigned to alice, which in our case is

In [10]: signed_tx['id']
Out[10]: 'da8b099d83e5f1e4ba6ed5719cfe40a20029c2e9c1689029e981735fa49378e8'

BigchainDB makes use of the crypto-conditions library to both cryptographically lock and unlock transactions. The locking script is referred to as a condition and a corresponding fulfillment unlocks the condition of the input_tx.

Since a transaction can have multiple outputs with each its own (crypto)condition, each transaction input should also refer to the condition index cid.

_images/tx_single_condition_single_fulfillment_v1.png

In order to prepare a transfer transaction, alice needs to provide at least three things:

  1. inputs – one or more conditions that will be fulfilled.
  2. asset – the asset being transferred.
  3. owners_after – one or more public keys representing the new owner(s).

To construct the input:

In [11]: cid = 0

In [12]: condition = tx['transaction']['conditions'][cid]

In [13]: input_ = {
   ....:     'fulfillment': condition['condition']['details'],
   ....:     'input': {
   ....:         'cid': cid,
   ....:         'txid': tx['id'],
   ....:     },
   ....:     'owners_before': condition['owners_after'],
   ....: }
   ....: 

The asset, can be directly retrieved from the input tx:

In [14]: asset = tx['transaction']['asset']

Create a second test user, bob:

In [15]: bob = generate_keypair()

In [16]: bob.verifying_key
Out[16]: 'HQ8qcX9qaXzYBV2Q5yBFZhn1mtKw1r8T6Q9bYnAvLH4E'

And prepare the transfer transaction:

In [17]: tx_transfer = bdb.transactions.prepare(
   ....:     operation='TRANSFER',
   ....:     inputs=input_,
   ....:     asset=asset,
   ....:     owners_after=bob.verifying_key,
   ....: )
   ....: 

The tx_transfer dictionary should look something like:

In [18]: tx_transfer
Out[18]: 
{'id': '23134ba6d2f5aedb5f7271403fb90108488bca923e5a5b061fadde537c1f4184',
 'transaction': {'asset': {'id': '47f4e1f7-ef85-4e44-9de5-6877b9e1da64'},
  'conditions': [{'amount': 1,
    'cid': 0,
    'condition': {'details': {'bitmask': 32,
      'public_key': 'HQ8qcX9qaXzYBV2Q5yBFZhn1mtKw1r8T6Q9bYnAvLH4E',
      'signature': None,
      'type': 'fulfillment',
      'type_id': 4},
     'uri': 'cc:4:20:86g2SNs5zFwPzhw6bHmeNeyVvpbzd5I3AfO0BdXXb6M:96'},
    'owners_after': ['HQ8qcX9qaXzYBV2Q5yBFZhn1mtKw1r8T6Q9bYnAvLH4E']}],
  'fulfillments': [{'fid': 0,
    'fulfillment': {'bitmask': 32,
     'public_key': '5M8MoLhoeJ9TtoMti2vvtg4T5pAKCqKD8MJjjakfZ3Cj',
     'signature': None,
     'type': 'fulfillment',
     'type_id': 4},
    'input': {'cid': 0,
     'txid': 'da8b099d83e5f1e4ba6ed5719cfe40a20029c2e9c1689029e981735fa49378e8'},
    'owners_before': ['5M8MoLhoeJ9TtoMti2vvtg4T5pAKCqKD8MJjjakfZ3Cj']}],
  'metadata': None,
  'operation': 'TRANSFER'},
 'version': 1}

Notice, bob‘s verifying key (public key), appearing in the above dict.

In [19]: bob.verifying_key
Out[19]: 'HQ8qcX9qaXzYBV2Q5yBFZhn1mtKw1r8T6Q9bYnAvLH4E'

The transaction now needs to be fulfilled by alice:

In [20]: signed_tx_transfer = bdb.transactions.fulfill(
   ....:     tx_transfer,
   ....:     private_keys=alice.signing_key,
   ....: )
   ....: 

If you look at the content of signed_tx_transfer you should see the added fulfilment uri, holding the signature:

In [21]: signed_tx_transfer
Out[21]: 
{'id': '23134ba6d2f5aedb5f7271403fb90108488bca923e5a5b061fadde537c1f4184',
 'transaction': {'asset': {'id': '47f4e1f7-ef85-4e44-9de5-6877b9e1da64'},
  'conditions': [{'amount': 1,
    'cid': 0,
    'condition': {'details': {'bitmask': 32,
      'public_key': 'HQ8qcX9qaXzYBV2Q5yBFZhn1mtKw1r8T6Q9bYnAvLH4E',
      'signature': None,
      'type': 'fulfillment',
      'type_id': 4},
     'uri': 'cc:4:20:86g2SNs5zFwPzhw6bHmeNeyVvpbzd5I3AfO0BdXXb6M:96'},
    'owners_after': ['HQ8qcX9qaXzYBV2Q5yBFZhn1mtKw1r8T6Q9bYnAvLH4E']}],
  'fulfillments': [{'fid': 0,
    'fulfillment': 'cf:4:QJavLVcbFP5cpKXTgDo9awtelABrrLEXeWl-68Tw53Bnh0UGNc1iIpGr6dTIrKXvTtMXbuUDswS_PKa2mYzGCXd5DfDrDKJhzGq2J7fpzRJvdSavRZggsKDEdW4yhj4C',
    'input': {'cid': 0,
     'txid': 'da8b099d83e5f1e4ba6ed5719cfe40a20029c2e9c1689029e981735fa49378e8'},
    'owners_before': ['5M8MoLhoeJ9TtoMti2vvtg4T5pAKCqKD8MJjjakfZ3Cj']}],
  'metadata': None,
  'operation': 'TRANSFER'},
 'version': 1}

More precisely:

In [22]: signed_tx_transfer['transaction']['fulfillments'][0]['fulfillment']
Out[22]: 'cf:4:QJavLVcbFP5cpKXTgDo9awtelABrrLEXeWl-68Tw53Bnh0UGNc1iIpGr6dTIrKXvTtMXbuUDswS_PKa2mYzGCXd5DfDrDKJhzGq2J7fpzRJvdSavRZggsKDEdW4yhj4C'

We have yet to send the transaction over to a BigchainDB node, as both preparing and fulfilling a transaction are done “offchain”, that is without the need to have a conenction to a BigchainDB federation.

sent_tx_transfer = bdb.transactions.send(signed_tx_transfer)

Again, as with the 'CREATE' transaction, notice how the payload returned by the server is equal to the signed one.

>>> sent_tx_transfer == signed_tx_transfer
True

Double Spends

BigchainDB makes sure that a user can’t transfer the same digital asset two or more times (i.e. it prevents double spends).

If we try to create another transaction with the same input as before, the transaction will be marked invalid and the validation will throw a double spend exception.

Let’s suppose that Alice tries to re-send the asset back to her “secret” account.

In [23]: alice_secret_stash = generate_keypair()

Create another transfer transaction with the same input

In [24]: tx_transfer_2 = bdb.transactions.prepare(
   ....:     operation='TRANSFER',
   ....:     inputs=input_,
   ....:     asset=asset,
   ....:     owners_after=alice_secret_stash.verifying_key,
   ....: )
   ....: 

Fulfill the transaction

In [25]: fulfilled_tx_transfer_2 = bdb.transactions.fulfill(
   ....:     tx_transfer_2,
   ....:     private_keys=alice.signing_key,
   ....: )
   ....: 

Send the transaction over to the node

>>> from bigchaindb_driver.exceptions import BigchaindbException
>>> try:
...     bdb.transactions.send(fulfilled_tx_transfer_2)
... except BigchaindbException as e:
...     print(e.info)

{'message': 'Invalid transaction', 'status': 400}

Todo

Update the above output once https://github.com/bigchaindb/bigchaindb/issues/664 is taken care of.

Multiple Owners

Say alice and bob own a car together:

In [26]: car_asset = {'data': {'car': {'vin': '5YJRE11B781000196'}}}

and they agree that alice will be the one issuing the asset. To create a new digital asset with multiple owners, one can simply provide a list of owners_after:

In [27]: car_creation_tx = bdb.transactions.prepare(
   ....:     operation='CREATE',
   ....:     owners_before=alice.verifying_key,
   ....:     owners_after=(alice.verifying_key, bob.verifying_key),
   ....:     asset=car_asset,
   ....: )
   ....: 

In [28]: signed_car_creation_tx = bdb.transactions.fulfill(
   ....:     car_creation_tx,
   ....:     private_keys=alice.signing_key,
   ....: )
   ....: 
sent_car_tx = bdb.transactions.send(signed_car_creation_tx

One day, alice and bob, having figured out how to teleport themselves, and realizing they no longer need their car, wish to transfer the ownership of their car over to carol:

In [29]: carol = generate_keypair()

In order to prepare the transfer transaction, alice and bob need the input:

In [30]: cid = 0

In [31]: condition = signed_car_creation_tx['transaction']['conditions'][cid]

In [32]: input_ = {
   ....:     'fulfillment': condition['condition']['details'],
   ....:     'input': {
   ....:         'cid': cid,
   ....:         'txid': signed_car_creation_tx['id'],
   ....:     },
   ....:     'owners_before': condition['owners_after'],
   ....: }
   ....: 

Let’s take a moment to contemplate what this input_ is:

In [33]: input_
Out[33]: 
{'fulfillment': {'bitmask': 41,
  'subfulfillments': [{'bitmask': 32,
    'public_key': '5M8MoLhoeJ9TtoMti2vvtg4T5pAKCqKD8MJjjakfZ3Cj',
    'signature': None,
    'type': 'fulfillment',
    'type_id': 4,
    'weight': 1},
   {'bitmask': 32,
    'public_key': 'HQ8qcX9qaXzYBV2Q5yBFZhn1mtKw1r8T6Q9bYnAvLH4E',
    'signature': None,
    'type': 'fulfillment',
    'type_id': 4,
    'weight': 1}],
  'threshold': 2,
  'type': 'fulfillment',
  'type_id': 2},
 'input': {'cid': 0,
  'txid': '34ee6ab27996b213bb7e51f17c3dc117690edf47f1ba341089417adfed7c150a'},
 'owners_before': ['5M8MoLhoeJ9TtoMti2vvtg4T5pAKCqKD8MJjjakfZ3Cj',
  'HQ8qcX9qaXzYBV2Q5yBFZhn1mtKw1r8T6Q9bYnAvLH4E']}

and the asset:

In [34]: asset = signed_car_creation_tx['transaction']['asset']

then alice can prepare the transfer:

In [35]: car_transfer_tx = bdb.transactions.prepare(
   ....:     operation='TRANSFER',
   ....:     owners_after=carol.verifying_key,
   ....:     asset=asset,
   ....:     inputs=input_,
   ....: )
   ....: 

The asset can be transfered as soon as each of the owners_after fulfills the transaction, that is alice and bob.

To do so, simply provide a list of all private keys to the fulfill method.

Danger

We are currently working to support partial fulfillments, such that not all keys of all parties involved need to be supplied at once. The issue bigchaindb/bigchaindb/issues/729 addresses the current limitation. Your feedback is welcome!

In [36]: signed_car_transfer_tx = bdb.transactions.fulfill(
   ....:     car_transfer_tx, private_keys=[alice.signing_key, bob.signing_key]
   ....: )
   ....: 

Note, that if one the signing keys is missing, the fulfillment will fail. If we omit bob:

In [37]: from bigchaindb_driver.exceptions import MissingSigningKeyError

In [38]: try:
   ....:     signed_car_transfer_tx = bdb.transactions.fulfill(
   ....:         car_transfer_tx,
   ....:         private_keys=alice.signing_key,
   ....:     )
   ....: except MissingSigningKeyError as e:
   ....:     print(e, e.__cause__, sep='\n')
   ....: 
A signing key is missing!
Public key HQ8qcX9qaXzYBV2Q5yBFZhn1mtKw1r8T6Q9bYnAvLH4E is not a pair to any of the private keys

Notice bob‘s public key in the above message:

In [39]:  bob.verifying_key
Out[39]: 'HQ8qcX9qaXzYBV2Q5yBFZhn1mtKw1r8T6Q9bYnAvLH4E'

And the same goes for alice. Try it!

Sending the transaction over to a BigchainDB node:

sent_car_transfer_tx = bdb.transactions.send(signed_car_transfer_tx)

if alice and bob wish to check the status of the transfer they may use the status() endpoint:

>>> bdb.transactions.status(sent_car_transfer_tx['id'])
{'status': 'valid'}

Done!

Happy, alice and bob have successfully transferred the ownership of their car to carol, and can go on exploring the countless galaxies of the universe using their new teleportation skills.

Crypto-Conditions (Advanced)

Introduction

Crypto-conditions provide a mechanism to describe a signed message such that multiple actors in a distributed system can all verify the same signed message and agree on whether it matches the description.

This provides a useful primitive for event-based systems that are distributed on the Internet since we can describe events in a standard deterministic manner (represented by signed messages) and therefore define generic authenticated event handlers.

Crypto-conditions are part of the Interledger protocol and the full specification can be found here.

Implementations of the crypto-conditions are available in Python and JavaScript.

Threshold Conditions

Threshold conditions introduce multi-signatures, m-of-n signatures or even more complex binary Merkle trees to BigchainDB.

Setting up a generic threshold condition is a bit more elaborate than regular transaction signing but allow for flexible signing between multiple parties or groups.

The basic workflow for creating a more complex cryptocondition is the following:

  1. Create a transaction template that includes the public key of all (nested) parties as owners_after
  2. Set up the threshold condition using the cryptocondition library
  3. Update the condition and hash in the transaction template

We’ll illustrate this by a threshold condition where 2 out of 3 owners_after need to sign the transaction:

Todo

Stay tuned. Will soon be documented once

is taken care of.

The transaction can now be transfered by fulfilling the threshold condition.

The fulfillment involves:

  1. Create a transaction template that includes the public key of all (nested) parties as owners_before
  2. Parsing the threshold condition into a fulfillment using the cryptocondition library
  3. Signing all necessary subfulfillments and updating the fulfillment field in the transaction

Todo

Stay tuned. Will soon be documented once

are taken care of.

Hash-locked Conditions

A hash-lock condition on an asset is like a password condition: anyone with the secret preimage (like a password) can fulfill the hash-lock condition and transfer the asset to themselves.

Under the hood, fulfilling a hash-lock condition amounts to finding a string (a “preimage”) which, when hashed, results in a given value. It’s easy to verify that a given preimage hashes to the given value, but it’s computationally difficult to find a string which hashes to the given value. The only practical way to get a valid preimage is to get it from the original creator (possibly via intermediaries).

One possible use case is to distribute preimages as “digital vouchers.” The first person to redeem a voucher will get the associated asset.

A federation node can create an asset with a hash-lock condition and no owners_after. Anyone who can fullfill the hash-lock condition can transfer the asset to themselves.

Todo

Stay tuned. Will soon be documented once

are taken care of.

In order to redeem the asset, one needs to create a fulfillment with the correct secret:

Todo

Stay tuned. Will soon be documented once

are taken care of.

Timeout Conditions

Timeout conditions allow assets to expire after a certain time. The primary use case of timeout conditions is to enable Escrow.

The condition can only be fulfilled before the expiry time. Once expired, the asset is lost and cannot be fulfilled by anyone.

Note

The timeout conditions are BigchainDB-specific and not (yet) supported by the ILP standard.

Important

Caveat: The times between nodes in a BigchainDB federation may (and will) differ slightly. In this case, the majority of the nodes will decide.

Todo

Stay tuned. Will soon be documented once

are taken care of.

The following demonstrates that the transaction invalidates once the timeout occurs:

Todo

Stay tuned. Will soon be documented once

are taken care of.

If you were fast enough, you should see the following output:

Todo

Stay tuned. Will soon be documented once

are taken care of.

Escrow

Escrow is a mechanism for conditional release of assets.

This means that the assets are locked up by a trusted party until an execute condition is presented. In order not to tie up the assets forever, the escrow foresees an abort condition, which is typically an expiry time.

BigchainDB and cryptoconditions provides escrow out-of-the-box, without the need of a trusted party.

A threshold condition is used to represent the escrow, since BigchainDB transactions cannot have a pending state.

_images/tx_escrow_execute_abort.png

The logic for switching between execute and abort conditions is conceptually simple:

if timeout_condition.validate(utcnow()):
    execute_fulfillment.validate(msg) == True
    abort_fulfillment.validate(msg) == False
else:
    execute_fulfillment.validate(msg) == False
    abort_fulfillment.validate(msg) == True

The above switch can be implemented as follows using threshold cryptoconditions:

_images/cc_escrow_execute_abort.png

The inverted timeout is denoted by a -1 threshold, which negates the output of the fulfillment.

inverted_fulfillment.validate(msg) == not fulfillment.validate(msg)

Note

inverted thresholds are BigchainDB-specific and not supported by the ILP standard. The main reason is that it’s difficult to tell whether the fulfillment was negated, or just omitted.

The following code snippet shows how to create an escrow condition:

In the case of bob, we create the abort fulfillment:

The following demonstrates that the transaction validation switches once the timeout occurs:

If you execute in a timely fashion, you should see the following:

Of course, when the execute transaction was accepted in-time by bigchaindb, then writing the abort transaction after expiry will yield a Doublespend error.