BigchainDB Python Driver

Quickstart / Installation

Warning

The instructions below were tested on Ubuntu 16.04 LTS. They should also work on other Linux distributions and on macOS. For other operating systems, we recommend using an Ubuntu virtual machine (VM).

The BigchainDB Python Driver depends on:

  1. Python 3.5+
  2. A recent Python 3 version of pip
  3. A recent Python 3 version of setuptools
  4. cryptography and cryptoconditions

If you’re missing one of those, then see “How to Install the Dependencies” below before proceeding.

Next, to install the latest stable BigchainDB Python Driver (bigchaindb_driver) use:

$ pip3 install bigchaindb_driver

If you want to install an Alpha, Beta or RC version of the Python Driver, use something like:

$ pip3 install bigchaindb_driver==0.5.0a4

The above command will install version 0.5.0a4 (Alpha 4). You can find a list of all versions in the release history page on PyPI.

Next: determine the BigchainDB Root URL of the BigchainDB node or cluster you want to connect to.

How to Install the Dependencies

Dependency 1: Python 3.5+

The BigchainDB Python Driver uses Python 3.5+. You can check your version of Python using:

$ python --version

OR

$ python3 --version

An easy way to install a specific version of Python, and to switch between versions of Python, is to use virtualenv. Another option is conda.

Dependency 2: pip

You also need to get a recent, Python 3 version of pip, the Python package manager.

If you’re using virtualenv or conda, then each virtual environment should include an appropriate version of pip.

You can check your version of pip using:

$ pip --version

OR

$ pip3 --version

pip was at version 9.0.0 as of November 2016. If you need to upgrade your version of pip, then see the pip documentation.

Dependency 3: setuptools

Once you have a recent Python 3 version of pip, you should be able to upgrade setuptools using:

$ pip install --upgrade setuptools

OR

$ pip3 install --upgrade setuptools

Dependency 4: cryptography and cryptoconditions

BigchainDB(server and driver) also depends on cryptography and cryptoconditions.

On Ubuntu 14.04 and 16.04, this works:

$ sudo apt-get update

$ sudo apt-get install python3-dev libssl-dev libffi-dev

For other operating systems, please refer to the cryptography installation guide.

Installing the Driver

Now you can install the BigchainDB Python Driver (bigchaindb_driver) using:

$ pip install bigchaindb_driver

OR

$ pip3 install bigchaindb_driver

Next: determine the BigchainDB Root URL of the BigchainDB node or cluster you want to connect to.

Advanced Installation Options

See the Advanced Installation Options page.

Installation Guide for Developers

Here’s how to set up bigchaindb-driver for local development.

  1. Fork the bigchaindb-driver repo on GitHub.

  2. Clone your fork locally and enter into the project:

    $ git clone git@github.com:your_name_here/bigchaindb-driver.git
    $ cd bigchaindb-driver/
    
  3. Create a branch for local development:

    $ git checkout -b name-of-your-bugfix-or-feature
    

    Now you can make your changes locally.

  4. When you’re done making changes, check that your changes pass flake8 and the tests. For the tests, you’ll need to start the MongoDB, Tendermint and BigchainDB servers:

    $ docker-compose up -d bigchaindb
    
  5. flake8 check:

    $ docker-compose run --rm bigchaindb-driver flake8 bigchaindb_driver tests
    
  6. To run the tests:

    $ docker-compose run --rm bigchaindb-driver pytest -v
    
  7. Commit your changes and push your branch to GitHub:

    $ git add .
    $ git commit -m "Your detailed description of your changes."
    $ git push origin name-of-your-bugfix-or-feature
    
We use pre-commit which should be triggered with every commit. Some hooks will change files but others will give errors that needs to be fixed. Every time a hook is failing you need to add the changed files again. The hooks we use can be found in the yaml config file.
  1. Submit a pull request through the GitHub website.

Determine the BigchainDB Root URL

If you want to use the BigchainDB Python Driver to communicate with a BigchainDB node or cluster, then you will need its BigchainDB Root URL. This page is to help you determine it.

Case 1: BigchainDB on localhost

If a BigchainDB node is running locally (and the BIGCHAINDB_SERVER_BIND setting wasn’t changed from the default localhost:9984), then the BigchainDB Root URL is:

bdb_root_url = 'http://localhost:9984'

Case 2: A Cluster Hosted by Someone Else

If you’re connecting to a BigchainDB cluster hosted by someone else, then they’ll tell you their BigchaindB Root URL. It can take many forms. It can use HTTP or HTTPS. It can use a hostname or an IP address. The port might not be 9984. Here are some examples:

bdb_root_url = 'http://example.com:9984'
bdb_root_url = 'http://api.example.com:9984'
bdb_root_url = 'http://example.com:1234'
bdb_root_url = 'http://example.com'  # http is port 80 by default
bdb_root_url = 'https://example.com'  # https is port 443 by default
bdb_root_url = 'http://12.34.56.123:9984'
bdb_root_url = 'http://12.34.56.123:5000'

Case 3: Docker Container on localhost

If you are running the Docker-based dev setup that comes along with the bigchaindb_driver repository (see Development Environment with Docker for more information), and wish to connect to it from the bigchaindb-driver linked (container) service, use:

bdb_root_url = 'http://bdb-server:9984'

Alternatively, you may connect to the containerized BigchainDB node from “outside”, in which case you need to know the port binding:

$ docker-compose port bigchaindb 9984
0.0.0.0:32780

or you can use the command specified in the Makefile:

$ make root-url
0.0.0.0:32780
bdb_root_url = 'http://0.0.0.0:32780'

Next, try some of the basic usage examples.

Basic Usage Examples

For the examples on this page, we assume you’re using a Python 3 version of IPython (or similar), you’ve installed the bigchaindb_driver Python package, and you have determined the BigchainDB Root URL of the node or cluster you want to connect to.

Getting Started

We begin by creating an object of class BigchainDB:

In [1]: from bigchaindb_driver import BigchainDB

In [2]: bdb_root_url = 'https://example.com:9984'  # Use YOUR BigchainDB Root URL here

If the BigchainDB node or cluster doesn’t require authentication tokens, you can do:

In [3]: bdb = BigchainDB(bdb_root_url)

If it does require authentication tokens, you can do put them in a dict like so:

In [4]: tokens = {'app_id': 'your_app_id', 'app_key': 'your_app_key'}

In [5]: bdb = BigchainDB(bdb_root_url, headers=tokens)

Digital Asset Definition

As an example, let’s consider the creation and transfer of a digital asset that represents a bicycle:

In [6]: bicycle = {
   ...:     'data': {
   ...:         'bicycle': {
   ...:             'serial_number': 'abcd1234',
   ...:             'manufacturer': 'bkfab',
   ...:         },
   ...:     },
   ...: }
   ...: 

We’ll suppose that the bike belongs to Alice, and that it will be transferred to Bob.

In general, you may use any dictionary for the 'data' property.

Metadata Definition (optional)

You can optionally add metadata to a transaction. Any dictionary is accepted.

For example:

In [7]: metadata = {'planet': 'earth'}

Cryptographic Identities Generation

Alice and Bob are represented by public/private key pairs. The private key is used to sign transactions, meanwhile the public key is used to verify that a signed transaction was indeed signed by the one who claims to be the signee.

In [8]: from bigchaindb_driver.crypto import generate_keypair

In [9]: alice, bob = generate_keypair(), generate_keypair()

Asset Creation

We’re now ready to create the digital asset. First, let’s prepare the transaction:

In [10]: prepared_creation_tx = bdb.transactions.prepare(
   ....:     operation='CREATE',
   ....:     signers=alice.public_key,
   ....:     asset=bicycle,
   ....:     metadata=metadata,
   ....: )
   ....: 

The prepared_creation_tx dictionary should be similar to:

In [11]: prepared_creation_tx
Out[11]: 
{'asset': {'data': {'bicycle': {'manufacturer': 'bkfab',
    'serial_number': 'abcd1234'}}},
 'id': None,
 'inputs': [{'fulfillment': {'public_key': 'DUK7xdSboFp8Zc85JM2hRPR9orP4df8A5nPPpH8DHWQK',
    'type': 'ed25519-sha-256'},
   'fulfills': None,
   'owners_before': ['DUK7xdSboFp8Zc85JM2hRPR9orP4df8A5nPPpH8DHWQK']}],
 'metadata': {'planet': 'earth'},
 'operation': 'CREATE',
 'outputs': [{'amount': '1',
   'condition': {'details': {'public_key': 'DUK7xdSboFp8Zc85JM2hRPR9orP4df8A5nPPpH8DHWQK',
     'type': 'ed25519-sha-256'},
    'uri': 'ni:///sha-256;dD-Wnnr8RzsqVOb85S7J3p60HygiYKvvYDt6177qHjc?fpt=ed25519-sha-256&cost=131072'},
   'public_keys': ['DUK7xdSboFp8Zc85JM2hRPR9orP4df8A5nPPpH8DHWQK']}],
 'version': '2.0'}

The transaction now needs to be fulfilled by signing it with Alice’s private key:

In [12]: fulfilled_creation_tx = bdb.transactions.fulfill(
   ....:     prepared_creation_tx, private_keys=alice.private_key)
   ....: 
In [13]: fulfilled_creation_tx
Out[13]: 
{'asset': {'data': {'bicycle': {'manufacturer': 'bkfab',
    'serial_number': 'abcd1234'}}},
 'id': '7ac83ca96673609ec935e4420b55014970474b3286e3d2386fc5dfcbd459528a',
 'inputs': [{'fulfillment': 'pGSAILlLac2VNIVJuQFX0_sbKmEnqyqpBTo06n4-w3Rcy6B8gUCh2etTnpS01Cxi0hff0M3PHSJVocHoSpF4D7eGBoS8jKUYxXQ45EnsZMyN6U-EJGzn0b7FmBkcSIZfXWhLON0J',
   'fulfills': None,
   'owners_before': ['DUK7xdSboFp8Zc85JM2hRPR9orP4df8A5nPPpH8DHWQK']}],
 'metadata': {'planet': 'earth'},
 'operation': 'CREATE',
 'outputs': [{'amount': '1',
   'condition': {'details': {'public_key': 'DUK7xdSboFp8Zc85JM2hRPR9orP4df8A5nPPpH8DHWQK',
     'type': 'ed25519-sha-256'},
    'uri': 'ni:///sha-256;dD-Wnnr8RzsqVOb85S7J3p60HygiYKvvYDt6177qHjc?fpt=ed25519-sha-256&cost=131072'},
   'public_keys': ['DUK7xdSboFp8Zc85JM2hRPR9orP4df8A5nPPpH8DHWQK']}],
 'version': '2.0'}

And sent over to a BigchainDB node:

>>> sent_creation_tx = bdb.transactions.send_commit(fulfilled_creation_tx)

Warning

The method .send will be deprecated in the next release of the driver, please use .send_commit, .send_sync, or .send_async instead. More info

Note that the response from the node should be the same as that which was sent:

>>> sent_creation_tx == fulfilled_creation_tx
True

Notice the transaction id:

In [14]: txid = fulfilled_creation_tx['id']

In [15]: txid
Out[15]: '7ac83ca96673609ec935e4420b55014970474b3286e3d2386fc5dfcbd459528a'

Check if the Transaction was sent successfully

After a couple of seconds, we can check if the transaction was included in a block.

# Retrieve the block height
>>> block_height = bdb.blocks.get(txid=signed_tx['id'])

This will return the block height containing the transaction. If the transaction is not in any block then None is returned. If it is None it can have different reasons for example the transaction was not valid or is still in the queue and you can try again later. If the transaction was invalid or could not be sent an exception is raised.

If we want to see the whole block we can use the block height to retrieve the block itself.

# Retrieve the block that contains the transaction
>>> block = bdb.blocks.retrieve(str(block_height))

Asset Transfer

Imagine some time goes by, during which Alice is happy with her bicycle, and one day, she meets Bob, who is interested in acquiring her bicycle. The timing is good for Alice as she had been wanting to get a new bicycle.

To transfer the bicycle (asset) to Bob, Alice must consume the transaction in which the Bicycle asset was created.

Alice could retrieve the transaction:

>>>  creation_tx = bdb.transactions.retrieve(txid)

or simply use fulfilled_creation_tx:

In [16]: creation_tx = fulfilled_creation_tx

In order to prepare the transfer transaction, we first need to know the id of the asset we’ll be transferring. Here, because Alice is consuming a CREATE transaction, we have a special case in that the asset id is NOT found on the asset itself, but is simply the CREATE transaction’s id:

In [17]: asset_id = creation_tx['id']

In [18]: transfer_asset = {
   ....:     'id': asset_id,
   ....: }
   ....: 

Let’s now prepare the transfer transaction:

In [19]: output_index = 0

In [20]: output = creation_tx['outputs'][output_index]

In [21]: transfer_input = {
   ....:     'fulfillment': output['condition']['details'],
   ....:     'fulfills': {
   ....:          'output_index': output_index,
   ....:          'transaction_id': creation_tx['id'],
   ....:      },
   ....:      'owners_before': output['public_keys'],
   ....: }
   ....: 

In [22]: prepared_transfer_tx = bdb.transactions.prepare(
   ....:     operation='TRANSFER',
   ....:     asset=transfer_asset,
   ....:     inputs=transfer_input,
   ....:     recipients=bob.public_key,
   ....: )
   ....: 

fulfill it:

In [23]: fulfilled_transfer_tx = bdb.transactions.fulfill(
   ....:     prepared_transfer_tx,
   ....:     private_keys=alice.private_key,
   ....: )
   ....: 

The fulfilled_transfer_tx dictionary should look something like:

In [24]: fulfilled_transfer_tx
Out[24]: 
{'asset': {'id': '7ac83ca96673609ec935e4420b55014970474b3286e3d2386fc5dfcbd459528a'},
 'id': '8709752ea307c1d2a3d599ef94bbfc75d7c3114db774c7ffe868f74a089d7ce8',
 'inputs': [{'fulfillment': 'pGSAILlLac2VNIVJuQFX0_sbKmEnqyqpBTo06n4-w3Rcy6B8gUCxqyl7NN18mWEUavc8RqUxe9H2cVyJNZgkRqYYXHOm8WuBA9QbQf9k5jKshcf1O26AmZeXS4EdWVsvNLUABtUM',
   'fulfills': {'output_index': 0,
    'transaction_id': '7ac83ca96673609ec935e4420b55014970474b3286e3d2386fc5dfcbd459528a'},
   'owners_before': ['DUK7xdSboFp8Zc85JM2hRPR9orP4df8A5nPPpH8DHWQK']}],
 'metadata': None,
 'operation': 'TRANSFER',
 'outputs': [{'amount': '1',
   'condition': {'details': {'public_key': '9a4ynyU3i1kNJ8qsaD1DPWScJEPB5EuZuPvWyBTL2JPM',
     'type': 'ed25519-sha-256'},
    'uri': 'ni:///sha-256;KKbKJAgYQib-SFpRKqJ3pRtK0Wpy-VJlTubjjxl27Mk?fpt=ed25519-sha-256&cost=131072'},
   'public_keys': ['9a4ynyU3i1kNJ8qsaD1DPWScJEPB5EuZuPvWyBTL2JPM']}],
 'version': '2.0'}

and finally send it to the connected BigchainDB node:

>>> sent_transfer_tx = bdb.transactions.send_commit(fulfilled_transfer_tx)

>>> sent_transfer_tx == fulfilled_transfer_tx
True

Warning

The method .send will be deprecated in the next release of the driver, please use .send_commit, .send_sync, or .send_async instead. More info

Bob is the new owner:

In [25]: fulfilled_transfer_tx['outputs'][0]['public_keys'][0] == bob.public_key
Out[25]: True

Alice is the former owner:

In [26]: fulfilled_transfer_tx['inputs'][0]['owners_before'][0] == alice.public_key
Out[26]: True

Note

Obtaining asset ids:

You might have noticed that we considered Alice’s case of consuming a CREATE transaction as a special case. In order to obtain the asset id of a CREATE transaction, we had to use the CREATE transaction’s id:

transfer_asset_id = create_tx['id']

If you instead wanted to consume TRANSFER transactions (for example, fulfilled_transfer_tx), you could obtain the asset id to transfer from the asset['id'] property:

transfer_asset_id = transfer_tx['asset']['id']

Recap: Asset Creation & Transfer

from bigchaindb_driver import BigchainDB
from bigchaindb_driver.crypto import generate_keypair
from time import sleep
from sys import exit

alice, bob = generate_keypair(), generate_keypair()

bdb_root_url = 'https://example.com:9984'  # Use YOUR BigchainDB Root URL here

bdb = BigchainDB(bdb_root_url)

bicycle_asset = {
    'data': {
        'bicycle': {
            'serial_number': 'abcd1234',
            'manufacturer': 'bkfab'
        },
    },
}

bicycle_asset_metadata = {
    'planet': 'earth'
}

prepared_creation_tx = bdb.transactions.prepare(
    operation='CREATE',
    signers=alice.public_key,
    asset=bicycle_asset,
    metadata=bicycle_asset_metadata
)

fulfilled_creation_tx = bdb.transactions.fulfill(
    prepared_creation_tx,
    private_keys=alice.private_key
)

sent_creation_tx = bdb.transactions.send_commit(fulfilled_creation_tx)

txid = fulfilled_creation_tx['id']

asset_id = txid

transfer_asset = {
    'id': asset_id
}

output_index = 0
output = fulfilled_creation_tx['outputs'][output_index]

transfer_input = {
    'fulfillment': output['condition']['details'],
    'fulfills': {
        'output_index': output_index,
        'transaction_id': fulfilled_creation_tx['id']
    },
    'owners_before': output['public_keys']
}

prepared_transfer_tx = bdb.transactions.prepare(
    operation='TRANSFER',
    asset=transfer_asset,
    inputs=transfer_input,
    recipients=bob.public_key,
)

fulfilled_transfer_tx = bdb.transactions.fulfill(
    prepared_transfer_tx,
    private_keys=alice.private_key,
)

sent_transfer_tx = bdb.transactions.send_commit(fulfilled_transfer_tx)

print("Is Bob the owner?",
    sent_transfer_tx['outputs'][0]['public_keys'][0] == bob.public_key)

print("Was Alice the previous owner?",
    fulfilled_transfer_tx['inputs'][0]['owners_before'][0] == alice.public_key)

Divisible Assets

All assets in BigchainDB become implicitly divisible if a transaction contains more than one of that asset (we’ll see how this happens shortly).

Let’s continue with the bicycle example. Bob is now the proud owner of the bicycle and he decides he wants to rent the bicycle. Bob starts by creating a time sharing token in which one token corresponds to one hour of riding time:

In [27]: bicycle_token = {
   ....:     'data': {
   ....:         'token_for': {
   ....:             'bicycle': {
   ....:                 'serial_number': 'abcd1234',
   ....:                 'manufacturer': 'bkfab'
   ....:             }
   ....:         },
   ....:         'description': 'Time share token. Each token equals one hour of riding.',
   ....:     },
   ....: }
   ....: 

Bob has now decided to issue 10 tokens and assigns them to Carly. Notice how we denote Carly as receiving 10 tokens by using a tuple: ([carly.public_key], 10).

In [28]: bob, carly = generate_keypair(), generate_keypair()

In [29]: prepared_token_tx = bdb.transactions.prepare(
   ....:     operation='CREATE',
   ....:     signers=bob.public_key,
   ....:     recipients=[([carly.public_key], 10)],
   ....:     asset=bicycle_token,
   ....: )
   ....: 

In [30]: fulfilled_token_tx = bdb.transactions.fulfill(
   ....:     prepared_token_tx, private_keys=bob.private_key)
   ....: 

The fulfilled_token_tx dictionary should look something like:

In [31]: fulfilled_token_tx
Out[31]: 
{'asset': {'data': {'description': 'Time share token. Each token equals one hour of riding.',
   'token_for': {'bicycle': {'manufacturer': 'bkfab',
     'serial_number': 'abcd1234'}}}},
 'id': '90834b801e33babaa972f946b36f6ddbacb76178d02f4fbd4d019726dd1fb1cc',
 'inputs': [{'fulfillment': 'pGSAIKds4Gu9u7G3pfGriX4ksect1LhvpBMoFyut9XjWSnE0gUBEfKTLwTOB805Gjq0pMD1IWQrjIb2kU3wRaVRUnqLzs_tEA7HmQ2h-J4kXQ7NaSEkm4oEKNE0hcb62RXH7KqIM',
   'fulfills': None,
   'owners_before': ['CGZNqpfucgT9msUbrkJJgh7XWPC4U3FvN6KoaPbEfhW3']}],
 'metadata': None,
 'operation': 'CREATE',
 'outputs': [{'amount': '10',
   'condition': {'details': {'public_key': '2LcMpjQVUnVgJxG7VRqT3YnKi3iBjsYWtpUGcYaiBcUp',
     'type': 'ed25519-sha-256'},
    'uri': 'ni:///sha-256;-dyWQUGEsjiYY5KwZfhzOJvSpa270coqj33547w4S3s?fpt=ed25519-sha-256&cost=131072'},
   'public_keys': ['2LcMpjQVUnVgJxG7VRqT3YnKi3iBjsYWtpUGcYaiBcUp']}],
 'version': '2.0'}

Sending the transaction:

>>> sent_token_tx = bdb.transactions.send_commit(fulfilled_token_tx)

>>> sent_token_tx == fulfilled_token_tx
True

Warning

The method .send will be deprecated in the next release of the driver, please use .send_commit, .send_sync, or .send_async instead. More info

Note

Defining recipients:

To create divisible assets, we need to specify an amount >1 together with the public keys. The way we do this is by passing a list of tuples in recipients where each tuple corresponds to an output.

For instance, instead of creating a transaction with one output containing amount=10 we could have created a transaction with two outputs each holding amount=5:

recipients=[([carly.public_key], 5), ([carly.public_key], 5)]

The reason why the addresses are contained in lists is because each output can have multiple recipients. For instance, we can create an output with amount=10 in which both Carly and Alice are recipients (of the same asset):

recipients=[([carly.public_key, alice.public_key], 10)]

Bob is the issuer:

In [32]: fulfilled_token_tx['inputs'][0]['owners_before'][0] == bob.public_key
Out[32]: True

Carly is the owner of 10 tokens:

In [33]: fulfilled_token_tx['outputs'][0]['public_keys'][0] == carly.public_key
Out[33]: True

In [34]: fulfilled_token_tx['outputs'][0]['amount'] == '10'
Out[34]: True

Now in possession of the tokens, Carly wants to ride the bicycle for two hours. To do so, she needs to send two tokens to Bob:

In [35]: output_index = 0

In [36]: output = fulfilled_token_tx['outputs'][output_index]

In [37]: transfer_input = {
   ....:     'fulfillment': output['condition']['details'],
   ....:     'fulfills': {
   ....:         'output_index': output_index,
   ....:         'transaction_id': fulfilled_token_tx['id'],
   ....:     },
   ....:     'owners_before': output['public_keys'],
   ....: }
   ....: 

In [38]: transfer_asset = {
   ....:     'id': fulfilled_token_tx['id'],
   ....: }
   ....: 

In [39]: prepared_transfer_tx = bdb.transactions.prepare(
   ....:     operation='TRANSFER',
   ....:     asset=transfer_asset,
   ....:     inputs=transfer_input,
   ....:     recipients=[([bob.public_key], 2), ([carly.public_key], 8)]
   ....: )
   ....: 

In [40]: fulfilled_transfer_tx = bdb.transactions.fulfill(
   ....:     prepared_transfer_tx, private_keys=carly.private_key)
   ....: 

Notice how Carly needs to reassign the remaining eight tokens to herself if she wants to only transfer two tokens (out of the available 10) to Bob. BigchainDB ensures that the amount being consumed in each transaction (with divisible assets) is the same as the amount being output. This ensures that no amounts are lost.

The fulfilled_transfer_tx dictionary should have two outputs, one with amount='2' and the other with amount='8':

In [41]: fulfilled_transfer_tx
Out[41]: 
{'asset': {'id': '90834b801e33babaa972f946b36f6ddbacb76178d02f4fbd4d019726dd1fb1cc'},
 'id': 'c340ba9a6c43b7ebad11d5f6f3eef71335a4a56fdc74af20d0596707c3fa76d3',
 'inputs': [{'fulfillment': 'pGSAIBPhs-8HzH5WuvOt_u2tad9bOSi7Ai0N0g2v-uigpDs5gUBx725znkAwMnO6B4CnEC9EoYAoDa9L5PBi2VY3L0S46a8hxlWiyR56bpoNg9JvFvtJKBvN7PfGJCavnm6OUP0J',
   'fulfills': {'output_index': 0,
    'transaction_id': '90834b801e33babaa972f946b36f6ddbacb76178d02f4fbd4d019726dd1fb1cc'},
   'owners_before': ['2LcMpjQVUnVgJxG7VRqT3YnKi3iBjsYWtpUGcYaiBcUp']}],
 'metadata': None,
 'operation': 'TRANSFER',
 'outputs': [{'amount': '2',
   'condition': {'details': {'public_key': 'CGZNqpfucgT9msUbrkJJgh7XWPC4U3FvN6KoaPbEfhW3',
     'type': 'ed25519-sha-256'},
    'uri': 'ni:///sha-256;J6Bwjl2g9rfF0AfQzhUS0TOMMPgNFCe5uMoC26sU-zY?fpt=ed25519-sha-256&cost=131072'},
   'public_keys': ['CGZNqpfucgT9msUbrkJJgh7XWPC4U3FvN6KoaPbEfhW3']},
  {'amount': '8',
   'condition': {'details': {'public_key': '2LcMpjQVUnVgJxG7VRqT3YnKi3iBjsYWtpUGcYaiBcUp',
     'type': 'ed25519-sha-256'},
    'uri': 'ni:///sha-256;-dyWQUGEsjiYY5KwZfhzOJvSpa270coqj33547w4S3s?fpt=ed25519-sha-256&cost=131072'},
   'public_keys': ['2LcMpjQVUnVgJxG7VRqT3YnKi3iBjsYWtpUGcYaiBcUp']}],
 'version': '2.0'}
>>> sent_transfer_tx = bdb.transactions.send_commit(fulfilled_transfer_tx)

>>> sent_transfer_tx == fulfilled_transfer_tx
True

Warning

The method .send will be deprecated in the next release of the driver, please use .send_commit, .send_sync, or .send_async instead. More info

Querying for Assets

BigchainDB allows you to query for assets using simple text search. This search is applied to all the strings inside the asset payload and returns all the assets that match a given text search string.

Let’s create 3 assets:

from bigchaindb_driver import BigchainDB
from bigchaindb_driver.crypto import generate_keypair

bdb_root_url = 'https://example.com:9984'

bdb = BigchainDB(bdb_root_url)

alice = generate_keypair()

hello_1 = {'data': {'msg': 'Hello BigchainDB 1!'},}
hello_2 = {'data': {'msg': 'Hello BigchainDB 2!'},}
hello_3 = {'data': {'msg': 'Hello BigchainDB 3!'},}

# set the metadata to query for it in an example below
metadata={'planet': 'earth'}

prepared_creation_tx = bdb.transactions.prepare(
    operation='CREATE',
    signers=alice.public_key,
    asset=hello_1
)
fulfilled_creation_tx = bdb.transactions.fulfill(
    prepared_creation_tx, private_keys=alice.private_key)
bdb.transactions.send_commit(fulfilled_creation_tx)

prepared_creation_tx = bdb.transactions.prepare(
    operation='CREATE',
    signers=alice.public_key,
    asset=hello_2
)
fulfilled_creation_tx = bdb.transactions.fulfill(
    prepared_creation_tx, private_keys=alice.private_key)
bdb.transactions.send_commit(fulfilled_creation_tx)

prepared_creation_tx = bdb.transactions.prepare(
    operation='CREATE',
    signers=alice.public_key,
    asset=hello_3
)
fulfilled_creation_tx = bdb.transactions.fulfill(
    prepared_creation_tx, private_keys=alice.private_key)
bdb.transactions.send_commit(fulfilled_creation_tx)

Let’s perform a text search for all assets that contain the word bigchaindb:

>> bdb.assets.get(search='bigchaindb')
[
    {
        'data': {'msg': 'Hello BigchainDB 1!'},
        'id': '7582d7a81652d0230fefb47dafc360ff09b2c2566b68f05c3a004d57e7fe7610'
    },
    {
        'data': {'msg': 'Hello BigchainDB 2!'},
        'id': 'e40f4b6ac70b9c1b3b237ec13f4174384fd4d54d36dfde25520171577c49caa4'
    },
    {
        'data': {'msg': 'Hello BigchainDB 3!'},
        'id': '748f6c30daaf771c9020d84db9ad8ac4d1f7c8de7013db55e16f10ba090f7013'
    }
]

This call returns all the assets that match the string bigchaindb, sorted by text score, as well as the asset id. This is the same id of the transaction that created the asset.

It’s also possible to limit the amount of returned results using the limit argument:

>> bdb.assets.get(search='bigchaindb', limit=2)
[
    {
        'data': {'msg': 'Hello BigchainDB 1!'},
        'id': '7582d7a81652d0230fefb47dafc360ff09b2c2566b68f05c3a004d57e7fe7610'
    },
    {
        'data': {'msg': 'Hello BigchainDB 2!'},
        'id': 'e40f4b6ac70b9c1b3b237ec13f4174384fd4d54d36dfde25520171577c49caa4'
    }
]

Querying for Transactions

For this query we need to provide an asset_id and we will get back a list of transactions that use the asset with the ID asset_id.

Note

Please note that the id of an asset in BigchainDB is actually the id of the transaction which created the asset. In other words, when querying for an asset id with the operation set to CREATE, only one transaction should be expected. This transaction will be the transaction in which the asset was created, and the transaction id will be equal to the given asset id.

We will use the id of our last example Divisible Assets. Let’s try it:

>>> bdb.transactions.get(asset_id=sent_token_tx['id'])
[{'asset': {'data': {'description': 'Time share token. Each token equals one '
                                'hour of riding.',
                 'token_for': {'bicycle': {'manufacturer': 'bkfab',
                                           'serial_number': 'abcd1234'}}}},
'id': 'b2403bb6bb7f9c0af2bc2b5b03b291a378fd8499f44cade4aa14dd5419e5b7c7',
'inputs': [{'fulfillment': 'pGSAIFetX0Fz6ZUN20tJp_dWJKs0_nDDz7oOmTaToGrzzw5zgUBPJsUGHcm8R-ntQSHvK3tgoyHIvCrrNrI6lJkud81cZKWFb9XehNAvWswPWSx1_6EwFKVYV-fjlxPvExm8XZIH',
          'fulfills': None,
          'owners_before': ['6uFoT6vd38qGqo2dRMBQsSojytUadyijBH4wgZGrPhZt']}],
'metadata': None,
'operation': 'CREATE',
'outputs': [{'amount': '10',
           'condition': {'details': {'public_key': '8sKzvruHPhH3LKoGZDJE9MRzpgfFQJGZhzHTghebbFne',
                                     'type': 'ed25519-sha-256'},
                         'uri': 'ni:///sha-256;PN3UO9GztlEBitIZf5m4iYNgyexvOk6Sdjq3PANsxko?fpt=ed25519-sha-256&cost=131072'},
           'public_keys': ['8sKzvruHPhH3LKoGZDJE9MRzpgfFQJGZhzHTghebbFne']}],
'version': '2.0'},
{'asset': {'id': 'b2403bb6bb7f9c0af2bc2b5b03b291a378fd8499f44cade4aa14dd5419e5b7c7'},
'id': '3ce3a5d4d984ca92f4a34967a2c181dbe8da8d6e4477220d7869ada9379dc410',
'inputs': [{'fulfillment': 'pGSAIHTmVLbdfDFHTBx6gVr4NczRN-D1MhHltB0nn79luYlfgUCrppCotKAZoVW7nKye4I2HzGxlgwjmx47w_HxGXOFVbvCppNTLeVX4NrHYFRJlv8QKgj_ZaLctHpT6HPLLYIIG',
          'fulfills': {'output_index': 0,
                       'transaction_id': 'b2403bb6bb7f9c0af2bc2b5b03b291a378fd8499f44cade4aa14dd5419e5b7c7'},
          'owners_before': ['8sKzvruHPhH3LKoGZDJE9MRzpgfFQJGZhzHTghebbFne']}],
'metadata': None,
'operation': 'TRANSFER',
'outputs': [{'amount': '2',
           'condition': {'details': {'public_key': '6uFoT6vd38qGqo2dRMBQsSojytUadyijBH4wgZGrPhZt',
                                     'type': 'ed25519-sha-256'},
                         'uri': 'ni:///sha-256;HapGwR7oqOS3oZSICryoGJL0SfQF2LcSJe98jBKmdqo?fpt=ed25519-sha-256&cost=131072'},
           'public_keys': ['6uFoT6vd38qGqo2dRMBQsSojytUadyijBH4wgZGrPhZt']},
          {'amount': '8',
           'condition': {'details': {'public_key': '8sKzvruHPhH3LKoGZDJE9MRzpgfFQJGZhzHTghebbFne',
                                     'type': 'ed25519-sha-256'},
                         'uri': 'ni:///sha-256;PN3UO9GztlEBitIZf5m4iYNgyexvOk6Sdjq3PANsxko?fpt=ed25519-sha-256&cost=131072'},
           'public_keys': ['8sKzvruHPhH3LKoGZDJE9MRzpgfFQJGZhzHTghebbFne']}],
'version': '2.0'}]

If you were busy sharing your bicycle with the whole city you might have a really long list. So let’s limit the results and just see the CREATE transaction.

>>> bdb.transactions.get(asset_id=sent_token_tx['id'], operation='CREATE')
[{'asset': {'data': {'description': 'Time share token. Each token equals one '
                                'hour of riding.',
                 'token_for': {'bicycle': {'manufacturer': 'bkfab',
                                           'serial_number': 'abcd1234'}}}},
'id': 'b2403bb6bb7f9c0af2bc2b5b03b291a378fd8499f44cade4aa14dd5419e5b7c7',
'inputs': [{'fulfillment': 'pGSAIFetX0Fz6ZUN20tJp_dWJKs0_nDDz7oOmTaToGrzzw5zgUBPJsUGHcm8R-ntQSHvK3tgoyHIvCrrNrI6lJkud81cZKWFb9XehNAvWswPWSx1_6EwFKVYV-fjlxPvExm8XZIH',
          'fulfills': None,
          'owners_before': ['6uFoT6vd38qGqo2dRMBQsSojytUadyijBH4wgZGrPhZt']}],
'metadata': None,
'operation': 'CREATE',
'outputs': [{'amount': '10',
           'condition': {'details': {'public_key': '8sKzvruHPhH3LKoGZDJE9MRzpgfFQJGZhzHTghebbFne',
                                     'type': 'ed25519-sha-256'},
                         'uri': 'ni:///sha-256;PN3UO9GztlEBitIZf5m4iYNgyexvOk6Sdjq3PANsxko?fpt=ed25519-sha-256&cost=131072'},
           'public_keys': ['8sKzvruHPhH3LKoGZDJE9MRzpgfFQJGZhzHTghebbFne']}],
'version': '2.0'}]

Querying for Metadata

This query is similar to the asset query. The search is applied to all the strings inside the metadata and returns all the metadata that match a given text search string. The only difference is the returned id. The id of the asset query is the same id of the transaction that created the asset. Whereas the id of the metadata is the same id of the transaction where it was defined.

In the Querying for Assets example we already set the metadata for the three transactions. Let’s perform a text search for all metadata that contain the word earth:

>> bdb.metadata.get(search='earth')
[
    {
        'id': '3677de9c637e8e7848dd415058525306693d44cc3578d0ae4812e3570e9e6f9b',
        'metadata': {'planet': 'earth'}
    },
    {
        'id': 'd818741e0b9550dfe4b9ff0745c036664ab2b2e6e7d82a7508f2e79d587595ff',
        'metadata': {'planet': 'earth'}
    },
    {
        'id': '85a82b6fbceb08f79796cb0c286156aac25e92729d377220d65b14be90334c25',
        'metadata': {'planet': 'earth'}
    }
]

This call returns all the metadata that match the string earth, sorted by text score, as well as the transaction id.

It’s also possible to limit the amount of returned results using the limit argument:

>> bdb.metadata.get(search='earth', limit=2)
[
    {
        'id': '3677de9c637e8e7848dd415058525306693d44cc3578d0ae4812e3570e9e6f9b',
        'metadata': {'planet': 'earth'}
    },
    {
        'id': 'd818741e0b9550dfe4b9ff0745c036664ab2b2e6e7d82a7508f2e79d587595ff',
        'metadata': {'planet': 'earth'}
    }
]

Advanced Usage Examples

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

Todo

This is work in progress. More examples will gradually appear as issues like

are taken care of.

For the examples on this page, we assume you’re using a Python 3 version of IPython (or similar), you’ve installed the bigchaindb_driver Python package, and you have determined the BigchainDB Root URL of the node or cluster you want to connect to.

Connect to Multiple Nodes

You can optionally configure multiple nodes to connect to.

In [1]: from bigchaindb_driver import BigchainDB

In [2]: first_node = 'https://first.example.com:9984'

In [3]: second_node = 'https://second.example.com:9984'

In [4]: headers = {'app_id': 'your_app_id', 'app_key': 'your_app_key'}

In [5]: bdb = BigchainDB(first_node, second_node, headers=headers)

Each node can have its specific headers in addition to headers specified for all nodes, if any.

In [6]: first_node = 'https://first.example.com:9984'

In [7]: second_node = 'https://second.example.com:9984'

In [8]: common_headers = {'app_id': 'your_app_id', 'app_key': 'your_app_key'}

In [9]: second_node_headers = {'node_header': 'node_header_value'}

In [10]: bdb = BigchainDB(first_node,
   ....:                  {'endpoint': second_node, 'headers': second_node_headers},
   ....:                  headers=common_headers)
   ....: 

The driver switches nodes on connection failures in a round robin fashion. Connection failures are intermittent network issues like DNS failures or “refused connection” errors.

The driver switches nodes and repeats requests until the specified timeout is expired. The default timeout is 20 seconds. When timeout expires, an instance of bigchaindb_driver.exceptions.TimeoutError is raised. Specify timeout=None to repeat connection errors indefinitely.

In [11]: bdb = BigchainDB(first_node, second_node, headers=headers, timeout=None)

Also, the driver takes care of the exponential backoff. If a connection error occurs, the driver ensures at least half of a second is passed before the request to the same node is repeated. The intervals increase exponentially when multiple connection errors occur in a row. The user-specified timeout is always respected.

Warning

Every node the driver communicates with is supposed to be trusted. The driver does not currently implement any light client protocols.

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 [12]: from bigchaindb_driver.crypto import generate_keypair

Create a test user: alice

In [13]: alice = generate_keypair()

Define a digital asset data payload

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

In [15]: tx = bdb.transactions.prepare(operation='CREATE',
   ....:                               signers=alice.public_key,
   ....:                               asset=digital_asset_payload)
   ....: 

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

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

In [17]: signed_tx
Out[17]: 
{'asset': {'data': {'msg': 'Hello BigchainDB!'}},
 'id': '88b9be54bb731afef242cbacb304bee2273441be2b340e44122dd38348525aed',
 'inputs': [{'fulfillment': 'pGSAINLZ_2u5L294tr8md_u51rTjvCcFJfVA88eu_pku7QZpgUATSfQTC1Z8lkBTEn29bJo36e2eGV-L-hda4V5UgepOMR1eFtoWd3rpx3r5dFHhGGsBKwuegzAOKK3OFKDwxB0G',
   'fulfills': None,
   'owners_before': ['FC5R4W6KMgmdV75sRJpGxSXztG9zkAuu5MKwRAhfBTt4']}],
 'metadata': None,
 'operation': 'CREATE',
 'outputs': [{'amount': '1',
   'condition': {'details': {'public_key': 'FC5R4W6KMgmdV75sRJpGxSXztG9zkAuu5MKwRAhfBTt4',
     'type': 'ed25519-sha-256'},
    'uri': 'ni:///sha-256;e8CFL4ytsSeoKYikLP3VOn6pn8yvmr9kxV6nGNZcp5I?fpt=ed25519-sha-256&cost=131072'},
   'public_keys': ['FC5R4W6KMgmdV75sRJpGxSXztG9zkAuu5MKwRAhfBTt4']}],
 'version': '2.0'}

Write the transaction to BigchainDB. The transaction will be stored in a backlog where it will be validated before being included in a block.

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

Warning

The method .send will be deprecated in the next release of the driver, please use .send_commit, .send_sync, or .send_async instead. More info

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

>>> sent_tx == signed_tx
True

Recap: Asset Creation

from bigchaindb_driver import BigchainDB
from bigchaindb_driver.crypto import generate_keypair

bdb_root_url = 'https://example.com:9984'  # Use YOUR BigchainDB Root URL here
bdb = BigchainDB(bdb_root_url)

alice = generate_keypair()

digital_asset_payload = {'data': {'msg': 'Hello BigchainDB!'}}
tx = bdb.transactions.prepare(operation='CREATE',
                          signers=alice.public_key,
                          asset=digital_asset_payload)

signed_tx = bdb.transactions.fulfill(tx, private_keys=alice.private_key)
sent_tx = bdb.transactions.send_commit(signed_tx)
sent_tx == signed_tx

Check if the Transaction was sent successfully

After a couple of seconds, we can check if the transaction was included in a block.

# Retrieve the block height
>>> block_height = bdb.blocks.get(txid=signed_tx['id'])

This will return the block height containing the transaction. If the transaction is not in any block then None is returned. If it is None it can have different reasons for example the transaction was invalid or is still in the queue and you can try again later. If the transaction was invalid or could not be sent an exception is raised.

If we want to see the whole block we can use the block height to retrieve the block itself.

# Retrieve the block that contains the transaction
>>> block = bdb.blocks.retrieve(str(block_height))

The new owner of the digital asset is now Alice (or more correctly, her public key):

In [18]: alice.public_key
Out[18]: 'FC5R4W6KMgmdV75sRJpGxSXztG9zkAuu5MKwRAhfBTt4'

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 [19]: signed_tx['id']
Out[19]: '88b9be54bb731afef242cbacb304bee2273441be2b340e44122dd38348525aed'

BigchainDB makes use of the crypto-conditions library to cryptographically lock and unlock transactions. The locking script is referred to as a condition (put inside an “output”) and a corresponding fulfillment (put inside an “input”) unlocks the output condition of an input_tx.

Since a transaction can have multiple outputs each with their own (crypto)condition, each transaction input is required to refer to the output condition that they fulfill via fulfills['output_index'].

_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 fulfillments that fulfill a prior transaction’s output conditions.
  2. asset['id'] – the id of the asset being transferred.
  3. Recipient public_keys – one or more public keys representing the new recipients(s).

To construct the input:

In [20]: output_index = 0

In [21]: output = signed_tx['outputs'][output_index]

In [22]: input_ = {
   ....:     'fulfillment': output['condition']['details'],
   ....:     'fulfills': {
   ....:         'output_index': output_index,
   ....:         'transaction_id': signed_tx['id'],
   ....:     },
   ....:     'owners_before': output['public_keys'],
   ....: }
   ....: 

The asset in a TRANSFER transaction must be a dictionary with an id key denoting the asset to transfer. This asset id is either the id of the CREATE transaction of the asset (as it is in this case), or is the asset['id'] property in a TRANSFER transaction (note that this value simply points to the id of the asset’s CREATE transaction):

In [23]: transfer_asset_id = signed_tx['id']

In [24]: transfer_asset = {
   ....:     'id': transfer_asset_id,
   ....: }
   ....: 

Create a second test user, bob:

In [25]: bob = generate_keypair()

In [26]: bob.public_key
Out[26]: '7FB8YWr338DB7eRCDSoWd2xPCzVduaLBVtAYumAR5odr'

And prepare the transfer transaction:

In [27]: tx_transfer = bdb.transactions.prepare(
   ....:     operation='TRANSFER',
   ....:     inputs=input_,
   ....:     asset=transfer_asset,
   ....:     recipients=bob.public_key,
   ....: )
   ....: 

The tx_transfer dictionary should look something like:

In [28]: tx_transfer
Out[28]: 
{'asset': {'id': '88b9be54bb731afef242cbacb304bee2273441be2b340e44122dd38348525aed'},
 'id': None,
 'inputs': [{'fulfillment': {'public_key': 'FC5R4W6KMgmdV75sRJpGxSXztG9zkAuu5MKwRAhfBTt4',
    'type': 'ed25519-sha-256'},
   'fulfills': {'output_index': 0,
    'transaction_id': '88b9be54bb731afef242cbacb304bee2273441be2b340e44122dd38348525aed'},
   'owners_before': ['FC5R4W6KMgmdV75sRJpGxSXztG9zkAuu5MKwRAhfBTt4']}],
 'metadata': None,
 'operation': 'TRANSFER',
 'outputs': [{'amount': '1',
   'condition': {'details': {'public_key': '7FB8YWr338DB7eRCDSoWd2xPCzVduaLBVtAYumAR5odr',
     'type': 'ed25519-sha-256'},
    'uri': 'ni:///sha-256;THo2ZB5Gki0w1LlEEtbEvXV3PfZC1VG4lFXdg2p-KQQ?fpt=ed25519-sha-256&cost=131072'},
   'public_keys': ['7FB8YWr338DB7eRCDSoWd2xPCzVduaLBVtAYumAR5odr']}],
 'version': '2.0'}

Notice, bob’s public key, appearing in the above dict.

In [29]: tx_transfer['outputs'][0]['public_keys'][0]
Out[29]: '7FB8YWr338DB7eRCDSoWd2xPCzVduaLBVtAYumAR5odr'

In [30]: bob.public_key
Out[30]: '7FB8YWr338DB7eRCDSoWd2xPCzVduaLBVtAYumAR5odr'

The transaction now needs to be fulfilled by alice:

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

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

In [32]: signed_tx_transfer
Out[32]: 
{'asset': {'id': '88b9be54bb731afef242cbacb304bee2273441be2b340e44122dd38348525aed'},
 'id': '71d959dd872a069a339a06ee0488ab0a2a285e899b851b87a3b1aeb1f3ce8a11',
 'inputs': [{'fulfillment': 'pGSAINLZ_2u5L294tr8md_u51rTjvCcFJfVA88eu_pku7QZpgUBRFPK3-wCT_SPWwYteyXwa8vcX8JnGpqmpfJoha9KYTUtP1Gj9KuEWZFEfZWAlD1zV5ayoABDWauxbzvwKffcD',
   'fulfills': {'output_index': 0,
    'transaction_id': '88b9be54bb731afef242cbacb304bee2273441be2b340e44122dd38348525aed'},
   'owners_before': ['FC5R4W6KMgmdV75sRJpGxSXztG9zkAuu5MKwRAhfBTt4']}],
 'metadata': None,
 'operation': 'TRANSFER',
 'outputs': [{'amount': '1',
   'condition': {'details': {'public_key': '7FB8YWr338DB7eRCDSoWd2xPCzVduaLBVtAYumAR5odr',
     'type': 'ed25519-sha-256'},
    'uri': 'ni:///sha-256;THo2ZB5Gki0w1LlEEtbEvXV3PfZC1VG4lFXdg2p-KQQ?fpt=ed25519-sha-256&cost=131072'},
   'public_keys': ['7FB8YWr338DB7eRCDSoWd2xPCzVduaLBVtAYumAR5odr']}],
 'version': '2.0'}

More precisely:

In [33]: signed_tx_transfer['inputs'][0]['fulfillment']
Out[33]: 'pGSAINLZ_2u5L294tr8md_u51rTjvCcFJfVA88eu_pku7QZpgUBRFPK3-wCT_SPWwYteyXwa8vcX8JnGpqmpfJoha9KYTUtP1Gj9KuEWZFEfZWAlD1zV5ayoABDWauxbzvwKffcD'

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 connection to a BigchainDB federation.

>>> sent_tx_transfer = bdb.transactions.send_commit(signed_tx_transfer)

Warning

The method .send will be deprecated in the next release of the driver, please use .send_commit, .send_sync, or .send_async instead. More info

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

Recap: Asset Transfer

output_index = 0
output = signed_tx['outputs'][output_index]
input_ = {
    'fulfillment': output['condition']['details'],
    'fulfills': {
        'output_index': output_index,
        'transaction_id': signed_tx['id'],
    },
    'owners_before': output['public_keys'],
}
transfer_asset_id = signed_tx['id']
transfer_asset = {
    'id': transfer_asset_id,
}
bob = generate_keypair()
tx_transfer = bdb.transactions.prepare(
    operation='TRANSFER',
    inputs=input_,
    asset=transfer_asset,
    recipients=bob.public_key,
)
signed_tx_transfer = bdb.transactions.fulfill(
    tx_transfer,
    private_keys=alice.private_key,
)
sent_tx_transfer = bdb.transactions.send_commit(signed_tx_transfer)

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 [34]: alice_secret_stash = generate_keypair()

Create another transfer transaction with the same input

In [35]: tx_transfer_2 = bdb.transactions.prepare(
   ....:     operation='TRANSFER',
   ....:     inputs=input_,
   ....:     asset=transfer_asset,
   ....:     recipients=alice_secret_stash.public_key,
   ....: )
   ....: 

Fulfill the transaction

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

Send the transaction over to the node and an error will occur, informing the user of the double spend and specifying the matching asset_id.

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

{'message': 'Invalid transaction (DoubleSpend): input `20401005e1ad1745cdb3716715749475dce3c8358189af37d1a6676a52733e16` was already spent', 'status': 400}

Multiple Owners

Say alice and bob own a car together:

from bigchaindb_driver import BigchainDB
from bigchaindb_driver.crypto import generate_keypair

bdb_root_url = 'https://example.com:9984' # Use YOUR BigchainDB Root URL here
bdb = BigchainDB(bdb_root_url)

alice, bob = generate_keypair(), generate_keypair()
In [37]: 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 or tuple of recipients:

In [38]: car_creation_tx = bdb.transactions.prepare(
   ....:     operation='CREATE',
   ....:     signers=alice.public_key,
   ....:     recipients=(alice.public_key, bob.public_key),
   ....:     asset=car_asset,
   ....: )
   ....: 

In [39]: signed_car_creation_tx = bdb.transactions.fulfill(
   ....:     car_creation_tx,
   ....:     private_keys=alice.private_key,
   ....: )
   ....: 
>>> sent_car_tx = bdb.transactions.send_commit(signed_car_creation_tx)

>>> sent_car_tx == signed_car_creation_tx
True

Warning

The method .send will be deprecated in the next release of the driver, please use .send_commit, .send_sync, or .send_async instead. More info

Let’s see how the example looks like when alice and bob are the issuers:

from bigchaindb_driver import BigchainDB
from bigchaindb_driver.crypto import generate_keypair

bdb_root_url = 'https://example.com:9984'
bdb = BigchainDB(bdb_root_url)

alice, bob = generate_keypair(), generate_keypair()

car_asset = {
    'data': {
        'car': {
            'vin': '5YJRE11B781000196'
        }
    }
}
car_creation_tx = bdb.transactions.prepare(
    operation='CREATE',
    signers=(alice.public_key, bob.public_key),
    recipients=(alice.public_key, bob.public_key),
    asset=car_asset,
)
signed_car_creation_tx = bdb.transactions.fulfill(
    car_creation_tx,
    private_keys=[alice.private_key, bob.private_key],
)
sent_car_tx = bdb.transactions.send_commit(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 [40]: carol = generate_keypair()

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

In [41]: output_index = 0

In [42]: output = signed_car_creation_tx['outputs'][output_index]

In [43]: input_ = {
   ....:     'fulfillment': output['condition']['details'],
   ....:     'fulfills': {
   ....:         'output_index': output_index,
   ....:         'transaction_id': signed_car_creation_tx['id'],
   ....:     },
   ....:     'owners_before': output['public_keys'],
   ....: }
   ....: 

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

In [44]: input_
Out[44]: 
{'fulfillment': {'subconditions': [{'public_key': 'FC5R4W6KMgmdV75sRJpGxSXztG9zkAuu5MKwRAhfBTt4',
    'type': 'ed25519-sha-256'},
   {'public_key': '7FB8YWr338DB7eRCDSoWd2xPCzVduaLBVtAYumAR5odr',
    'type': 'ed25519-sha-256'}],
  'threshold': 2,
  'type': 'threshold-sha-256'},
 'fulfills': {'output_index': 0,
  'transaction_id': 'ae3c56d395a62bfcfc49c60169d3c96a14434ef60813de6f86821219fd740ded'},
 'owners_before': ['FC5R4W6KMgmdV75sRJpGxSXztG9zkAuu5MKwRAhfBTt4',
  '7FB8YWr338DB7eRCDSoWd2xPCzVduaLBVtAYumAR5odr']}

and the asset (because it’s a CREATE transaction):

In [45]: transfer_asset = {
   ....:     'id': signed_car_creation_tx['id'],
   ....: }
   ....: 

then alice can prepare the transfer:

In [46]: car_transfer_tx = bdb.transactions.prepare(
   ....:     operation='TRANSFER',
   ....:     recipients=carol.public_key,
   ....:     asset=transfer_asset,
   ....:     inputs=input_,
   ....: )
   ....: 

The asset can be transfered as soon as each of the original transaction’s signers fulfills the transaction, that is alice and bob.

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

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

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!

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

In [48]: from bigchaindb_driver.exceptions import MissingPrivateKeyError

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

Notice bob’s public key in the above message:

In [50]:  bob.public_key
Out[50]: '7FB8YWr338DB7eRCDSoWd2xPCzVduaLBVtAYumAR5odr'

And the same goes for alice. Try it!

Sending the transaction over to a BigchainDB node:

sent_car_transfer_tx = bdb.transactions.send_commit(signed_car_transfer_tx)

Warning

The method .send will be deprecated in the next release of the driver, please use .send_commit, .send_sync, or .send_async instead. More info

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, JavaScript, and Java.

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 allows 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 (signers) in the output’s public_keys
  2. Set up the threshold condition using the cryptocondition library
  3. Update the output’s condition and hash in the transaction template

We’ll illustrate this with a threshold condition where 2 out of 3 of the signers 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 (signers) in the inputs’s owners_before
  2. Parsing the threshold condition into a fulfillment using the cryptocondition library
  3. Signing all necessary subfulfillments and updating the inputs of 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 (i.e. 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.

Handcrafting Transactions

For those who wish to assemble transaction payloads “by hand”, with examples in Python.

Overview

Submitting a transaction to a BigchainDB node consists of three main steps:

  1. Preparing the transaction payload;
  2. Fulfilling the prepared transaction payload; and
  3. Sending the transaction payload via HTTPS.

Step 1 and 2 can be performed offline on the client. That is, they do not require any connection to any BigchainDB node.

For convenience’s sake, some utilities are provided to prepare and fulfill a transaction via the BigchainDB class, and via the offchain module. For an introduction on using these utilities, see the Basic Usage Examples or Advanced Usage Examples sections.

The rest of this document will guide you through completing steps 1 and 2 manually by revisiting some of the examples provided in the usage sections. We will:

  • provide all values, including the default ones;
  • generate the transaction id;
  • learn to use crypto-conditions to generate a condition that locks the transaction, hence protecting it from being consumed by an unauthorized user;
  • learn to use crypto-conditions to generate a fulfillment that unlocks the transaction asset, and consequently enact an ownership transfer.

In order to perform all of the above, we’ll use the following Python libraries:

  • json: to serialize the transaction dictionary into a JSON formatted string;
  • sha3: to hash the serialized transaction; and
  • cryptoconditions: to create conditions and fulfillments

With BigchainDB Server version 2.0 some changes on how to handcraft a transaction were introduced. You can read about the changes to the BigchainDB Server in our blog post.

High-level view of a transaction in Python

For detailed documentation on the transaction schema, please consult The Transaction Model and The Transaction Schema.

From the point of view of Python, a transaction is simply a dictionary:

{
    'operation': 'CREATE',
    'asset': {
        'data': {
            'bicycle': {
                'manufacturer': 'bkfab',
                'serial_number': 'abcd1234'
            }
        }
    },
    'version': '2.0',
    'outputs': [
        {
            'condition': {
                'details': {
                    'public_key': '2GoYB8cMZQrUBZzx9BH9Bq92eGWXBy3oanDXbRK3YRpW',
                    'type': 'ed25519-sha-256'
                },
                'uri': 'ni:///sha-256;1hBHivh6Nxhgi2b1ndUbP55ZlyUFdLC9BipPUBWth7U?fpt=ed25519-sha-256&cost=131072'
            },
            'public_keys': [
                '2GoYB8cMZQrUBZzx9BH9Bq92eGWXBy3oanDXbRK3YRpW'
            ],
            'amount': '1'
        }
    ],
    'inputs': [
        {
            'fulfills': None,
            'owners_before': [
                '2GoYB8cMZQrUBZzx9BH9Bq92eGWXBy3oanDXbRK3YRpW'
            ],
            'fulfillment': {
                'public_key': '2GoYB8cMZQrUBZzx9BH9Bq92eGWXBy3oanDXbRK3YRpW',
                'type': 'ed25519-sha-256'
            }
        }
    ],
    'id': None,
    'metadata': {
        'planet': 'earth'
    }
}

Because a transaction must be signed before being sent, the fulfillment must be provided by the client.

Important

Implications of Signed Payloads

Because BigchainDB relies on cryptographic signatures, the payloads need to be fully prepared and signed on the client side. This prevents the server(s) from tampering with the provided data.

This enhanced security puts more work on the clients, as various values that could traditionally be generated on the server side need to be generated on the client side.

Bicycle Asset Creation Revisited

We begin by creating a test user: alice

In [1]: from bigchaindb_driver.crypto import generate_keypair

In [2]: alice = generate_keypair()

The Prepared Transaction

Recall that in order to prepare a transaction, we had to do something similar to:

In [3]: from bigchaindb_driver.offchain import prepare_transaction

In [4]: bicycle = {
   ...:     'data': {
   ...:         'bicycle': {
   ...:             'serial_number': 'abcd1234',
   ...:             'manufacturer': 'bkfab',
   ...:         },
   ...:     },
   ...: }
   ...: 

In [5]: metadata = {'planet': 'earth'}

In [6]: prepared_creation_tx = prepare_transaction(
   ...:     operation='CREATE',
   ...:     signers=alice.public_key,
   ...:     asset=bicycle,
   ...:     metadata=metadata,
   ...: )
   ...: 

and the payload of the prepared transaction looked similar to:

In [7]: prepared_creation_tx
Out[7]: 
{'asset': {'data': {'bicycle': {'manufacturer': 'bkfab',
    'serial_number': 'abcd1234'}}},
 'id': None,
 'inputs': [{'fulfillment': {'public_key': '6V9Udb6fyhjkB4WJFnhzR58r9h9WvASPqYneCjbuoxfx',
    'type': 'ed25519-sha-256'},
   'fulfills': None,
   'owners_before': ['6V9Udb6fyhjkB4WJFnhzR58r9h9WvASPqYneCjbuoxfx']}],
 'metadata': {'planet': 'earth'},
 'operation': 'CREATE',
 'outputs': [{'amount': '1',
   'condition': {'details': {'public_key': '6V9Udb6fyhjkB4WJFnhzR58r9h9WvASPqYneCjbuoxfx',
     'type': 'ed25519-sha-256'},
    'uri': 'ni:///sha-256;Bt1JvTpJNwHmuKzUzNJx6a7hKE-NT4YB9XmHnpiI9ME?fpt=ed25519-sha-256&cost=131072'},
   'public_keys': ['6V9Udb6fyhjkB4WJFnhzR58r9h9WvASPqYneCjbuoxfx']}],
 'version': '2.0'}

Note alice’s public key is listed in the public keys of outputs:

In [8]: alice.public_key
Out[8]: '6V9Udb6fyhjkB4WJFnhzR58r9h9WvASPqYneCjbuoxfx'

In [9]: prepared_creation_tx['outputs'][0]['public_keys'][0] == alice.public_key
Out[9]: True

We are now going to craft this payload by hand.

version

As of BigchainDB 2.0, the transaction version is set to 2.0.

In [10]: version = '2.0'
asset

Because this is a CREATE transaction, we provide the data payload for the asset to the transaction (see the transfer example below for how to construct assets in TRANSFER transactions):

In [11]: asset = {
   ....:     'data': {
   ....:         'bicycle': {
   ....:             'manufacturer': 'bkfab',
   ....:             'serial_number': 'abcd1234',
   ....:         },
   ....:     },
   ....: }
   ....: 
metadata
In [12]: metadata = {'planet': 'earth'}
operation
In [13]: operation = 'CREATE'

Important

Case sensitive; all letters must be capitalized.

outputs

The purpose of the output condition is to lock the transaction, such that a valid input fulfillment is required to unlock it. In the case of signature-based schemes, the lock is basically a public key, such that in order to unlock the transaction one needs to have the private key.

Let’s review the output payload of the prepared transaction, to see what we are aiming for:

In [14]: prepared_creation_tx['outputs'][0]
Out[14]: 
{'amount': '1',
 'condition': {'details': {'public_key': '6V9Udb6fyhjkB4WJFnhzR58r9h9WvASPqYneCjbuoxfx',
   'type': 'ed25519-sha-256'},
  'uri': 'ni:///sha-256;Bt1JvTpJNwHmuKzUzNJx6a7hKE-NT4YB9XmHnpiI9ME?fpt=ed25519-sha-256&cost=131072'},
 'public_keys': ['6V9Udb6fyhjkB4WJFnhzR58r9h9WvASPqYneCjbuoxfx']}

The difficult parts are the condition details and URI. We’ll now see how to generate them using the cryptoconditions library:

Note

In BigchainDB keys are encoded in base58 but the cryptoconditions library expects an unencoded byte string so we will have to decode the base58 key before we can use it with cryptoconditions.

In [15]: import base58

A base58 encoded key:

In [16]: alice.public_key
Out[16]: '6V9Udb6fyhjkB4WJFnhzR58r9h9WvASPqYneCjbuoxfx'

Becomes:

In [17]: base58.b58decode(alice.public_key)
Out[17]: b'Q\x80H\x15E\xeb\x87\xdb\n\x07\xf2)\x89\xb1\xae;K\xc9\x8d\xa0\x9f\x07\xa4@\xecs\xd67\xe4\x92\x82?'
In [18]: from cryptoconditions import Ed25519Sha256

In [19]: ed25519 = Ed25519Sha256(public_key=base58.b58decode(alice.public_key))

generate the condition URI:

In [20]: ed25519.condition_uri
Out[20]: 'ni:///sha-256;Bt1JvTpJNwHmuKzUzNJx6a7hKE-NT4YB9XmHnpiI9ME?fpt=ed25519-sha-256&cost=131072'

So now you have a condition URI for Alice’s public key.

As for the details:

In [21]: condition_details = {
   ....:     'type': ed25519.TYPE_NAME,
   ....:     'public_key': base58.b58encode(ed25519.public_key).decode()
   ....: }
   ....: 

We can now easily assemble the dict for the output:

In [22]: output = {
   ....:     'amount': '1',
   ....:     'condition': {
   ....:         'details': condition_details,
   ....:         'uri': ed25519.condition_uri,
   ....:     },
   ....:     'public_keys': (alice.public_key,),
   ....: }
   ....: 

Let’s recap and set the outputs key with our self-constructed condition:

In [23]: from cryptoconditions import Ed25519Sha256

In [24]: ed25519 = Ed25519Sha256(public_key=base58.b58decode(alice.public_key))

In [25]: output = {
   ....:     'amount': '1',
   ....:     'condition': {
   ....:         'details': {
   ....:             'type': ed25519.TYPE_NAME,
   ....:             'public_key': base58.b58encode(ed25519.public_key).decode(),
   ....:         },
   ....:         'uri': ed25519.condition_uri,
   ....:     },
   ....:     'public_keys': (alice.public_key,),
   ....: }
   ....: 

In [26]: outputs = (output,)

The key part is the condition URI:

In [27]: ed25519.condition_uri
Out[27]: 'ni:///sha-256;Bt1JvTpJNwHmuKzUzNJx6a7hKE-NT4YB9XmHnpiI9ME?fpt=ed25519-sha-256&cost=131072'

To know more about its meaning, you may read the cryptoconditions internet draft.

inputs

The input fulfillment for a CREATE operation is somewhat special, and simplified:

In [28]: input_ = {
   ....:     'fulfillment': None,
   ....:     'fulfills': None,
   ....:     'owners_before': (alice.public_key,)
   ....: }
   ....: 
  • The fulfills field is empty because it’s a CREATE operation;
  • The 'fulfillment' value is None as it will be set during the fulfillment step; and
  • The 'owners_before' field identifies the issuer(s) of the asset that is being created.

The inputs value is simply a list or tuple of all inputs:

In [29]: inputs = (input_,)

Note

You may rightfully observe that the input generated in prepared_creation_tx via prepare_transaction() differs:

In [30]: prepared_creation_tx['inputs'][0]
Out[30]: 
{'fulfillment': {'public_key': '6V9Udb6fyhjkB4WJFnhzR58r9h9WvASPqYneCjbuoxfx',
  'type': 'ed25519-sha-256'},
 'fulfills': None,
 'owners_before': ['6V9Udb6fyhjkB4WJFnhzR58r9h9WvASPqYneCjbuoxfx']}

More precisely, the value of 'fulfillment' is not None:

In [31]: prepared_creation_tx['inputs'][0]['fulfillment']
Out[31]: 
{'public_key': '6V9Udb6fyhjkB4WJFnhzR58r9h9WvASPqYneCjbuoxfx',
 'type': 'ed25519-sha-256'}

The quick answer is that it simply is not needed, and can be set to None.

Up to now

Putting it all together:

In [32]: handcrafted_creation_tx = {
   ....:     'asset': asset,
   ....:     'metadata': metadata,
   ....:     'operation': operation,
   ....:     'outputs': outputs,
   ....:     'inputs': inputs,
   ....:     'version': version,
   ....:     'id': None,
   ....: }
   ....: 

Note how handcrafted_creation_tx includes a key-value pair 'id': None. The ‘id’ value is None as it will be set during the fulfillment step.

In [33]: handcrafted_creation_tx
Out[33]: 
{'asset': {'data': {'bicycle': {'manufacturer': 'bkfab',
    'serial_number': 'abcd1234'}}},
 'id': None,
 'inputs': ({'fulfillment': None,
   'fulfills': None,
   'owners_before': ('6V9Udb6fyhjkB4WJFnhzR58r9h9WvASPqYneCjbuoxfx',)},),
 'metadata': {'planet': 'earth'},
 'operation': 'CREATE',
 'outputs': ({'amount': '1',
   'condition': {'details': {'public_key': '6V9Udb6fyhjkB4WJFnhzR58r9h9WvASPqYneCjbuoxfx',
     'type': 'ed25519-sha-256'},
    'uri': 'ni:///sha-256;Bt1JvTpJNwHmuKzUzNJx6a7hKE-NT4YB9XmHnpiI9ME?fpt=ed25519-sha-256&cost=131072'},
   'public_keys': ('6V9Udb6fyhjkB4WJFnhzR58r9h9WvASPqYneCjbuoxfx',)},),
 'version': '2.0'}

You may observe that

In [34]: handcrafted_creation_tx == prepared_creation_tx
Out[34]: False
In [35]: from copy import deepcopy

In [36]: # back up

In [37]: prepared_creation_tx_bk = deepcopy(prepared_creation_tx)

In [38]: # set input fulfillment to None

In [39]: prepared_creation_tx['inputs'][0]['fulfillment'] = None

In [40]: handcrafted_creation_tx == prepared_creation_tx
Out[40]: False

Are still not equal because we used tuples instead of lists.

In [41]: import json

In [42]: # serialize to json str

In [43]: json_str_handcrafted_tx = json.dumps(handcrafted_creation_tx, sort_keys=True)

In [44]: json_str_prepared_tx = json.dumps(prepared_creation_tx, sort_keys=True)
In [45]: json_str_handcrafted_tx == json_str_prepared_tx
Out[45]: True

In [46]: prepared_creation_tx = prepared_creation_tx_bk

Let’s recap how we’ve put all the code together to generate the above payload:

from cryptoconditions import Ed25519Sha256
from bigchaindb_driver.crypto import generate_keypair
import base58

alice = generate_keypair()

operation = 'CREATE'

version = '2.0'

asset = {
    'data': {
        'bicycle': {
            'manufacturer': 'bkfab',
            'serial_number': 'abcd1234',
        },
    },
}

metadata = {'planet': 'earth'}

ed25519 = Ed25519Sha256(public_key=base58.b58decode(alice.public_key))

output = {
    'amount': '1',
    'condition': {
        'details': {
            'type': ed25519.TYPE_NAME,
            'public_key': base58.b58encode(ed25519.public_key).decode(),
        },
        'uri': ed25519.condition_uri,
    },
    'public_keys': (alice.public_key,),
}
outputs = (output,)

input_ = {
    'fulfillment': None,
    'fulfills': None,
    'owners_before': (alice.public_key,)
}
inputs = (input_,)

handcrafted_creation_tx = {
    'asset': asset,
    'metadata': metadata,
    'operation': operation,
    'outputs': outputs,
    'inputs': inputs,
    'version': version,
    'id': None,
}

The Fulfilled Transaction

In [47]: from cryptoconditions.crypto import Ed25519SigningKey

In [48]: import json

In [49]: from sha3 import sha3_256

In [50]: # fulfill prepared transaction

In [51]: from bigchaindb_driver.offchain import fulfill_transaction

In [52]: fulfilled_creation_tx = fulfill_transaction(
   ....:     prepared_creation_tx,
   ....:     private_keys=alice.private_key,
   ....: )
   ....: 

In [53]: # fulfill handcrafted transaction (with our previously built ED25519 fulfillment)

In [54]: ed25519.to_dict()
Out[54]: 
{'public_key': b'6V9Udb6fyhjkB4WJFnhzR58r9h9WvASPqYneCjbuoxfx',
 'signature': None,
 'type': 'ed25519-sha-256'}

In [55]: message = json.dumps(
   ....:     handcrafted_creation_tx,
   ....:     sort_keys=True,
   ....:     separators=(',', ':'),
   ....:     ensure_ascii=False,
   ....: )
   ....: 

In [56]: message = sha3_256(message.encode())

In [57]: ed25519.sign(message.digest(), base58.b58decode(alice.private_key))
Out[57]: b'\xc4\x08vT\x8aH\x0e\xad,\x03\xef}\xdc\xac\xe9\x8a\x9e\xeeX"\xf8\xa0@\xc3\x08\xd5\x00\xcb\xd1\xbaQ\xc3*\x02\xed\xed\xc0f\x9av\xd43\xe2\xf5>\xc5yk\x7fN\xf8\xd0fB\x94%\x02\xc2\x8c\xd5\x8fW\xa4\x08'

In [58]: fulfillment_uri = ed25519.serialize_uri()

In [59]: handcrafted_creation_tx['inputs'][0]['fulfillment'] = fulfillment_uri
id

The transaction’s id is essentially a SHA3-256 hash of the entire transaction (up to now), with a few additional tweaks:

In [60]: import json

In [61]: from sha3 import sha3_256

In [62]: json_str_tx = json.dumps(
   ....:     handcrafted_creation_tx,
   ....:     sort_keys=True,
   ....:     separators=(',', ':'),
   ....:     ensure_ascii=False,
   ....: )
   ....: 

In [63]: creation_txid = sha3_256(json_str_tx.encode()).hexdigest()

In [64]: handcrafted_creation_tx['id'] = creation_txid

Compare this to the txid of the transaction generated via prepare_transaction():

In [65]: creation_txid == fulfilled_creation_tx['id']
Out[65]: True

Let’s check this:

In [66]: fulfilled_creation_tx['inputs'][0]['fulfillment'] == fulfillment_uri
Out[66]: True

In [67]: json.dumps(fulfilled_creation_tx, sort_keys=True) == json.dumps(handcrafted_creation_tx, sort_keys=True)
Out[67]: True

The fulfilled transaction, ready to be sent over to a BigchainDB node:

In [68]: fulfilled_creation_tx
Out[68]: 
{'asset': {'data': {'bicycle': {'manufacturer': 'bkfab',
    'serial_number': 'abcd1234'}}},
 'id': '8c98d58b5630de5d4a191f68e765456672b1fa2d25041f764955e7ccdfc44b55',
 'inputs': [{'fulfillment': 'pGSAIFGASBVF64fbCgfyKYmxrjtLyY2gnwekQOxz1jfkkoI_gUDECHZUikgOrSwD733crOmKnu5YIvigQMMI1QDL0bpRwyoC7e3AZpp21DPi9T7FeWt_TvjQZkKUJQLCjNWPV6QI',
   'fulfills': None,
   'owners_before': ['6V9Udb6fyhjkB4WJFnhzR58r9h9WvASPqYneCjbuoxfx']}],
 'metadata': {'planet': 'earth'},
 'operation': 'CREATE',
 'outputs': [{'amount': '1',
   'condition': {'details': {'public_key': '6V9Udb6fyhjkB4WJFnhzR58r9h9WvASPqYneCjbuoxfx',
     'type': 'ed25519-sha-256'},
    'uri': 'ni:///sha-256;Bt1JvTpJNwHmuKzUzNJx6a7hKE-NT4YB9XmHnpiI9ME?fpt=ed25519-sha-256&cost=131072'},
   'public_keys': ['6V9Udb6fyhjkB4WJFnhzR58r9h9WvASPqYneCjbuoxfx']}],
 'version': '2.0'}

In a nutshell

Handcrafting a CREATE transaction can be done as follows:

import json

import base58
import sha3
from cryptoconditions import Ed25519Sha256

from bigchaindb_driver.crypto import generate_keypair


alice = generate_keypair()

operation = 'CREATE'

version = '2.0'

asset = {
    'data': {
        'bicycle': {
            'manufacturer': 'bkfab',
            'serial_number': 'abcd1234',
        },
    },
}

metadata = {'planet': 'earth'}

ed25519 = Ed25519Sha256(public_key=base58.b58decode(alice.public_key))

output = {
    'amount': '1',
    'condition': {
        'details': {
            'type': ed25519.TYPE_NAME,
            'public_key': base58.b58encode(ed25519.public_key).decode(),
        },
        'uri': ed25519.condition_uri,
    },
    'public_keys': (alice.public_key,),
}
outputs = (output,)

input_ = {
    'fulfillment': None,
    'fulfills': None,
    'owners_before': (alice.public_key,)
}
inputs = (input_,)

handcrafted_creation_tx = {
    'asset': asset,
    'metadata': metadata,
    'operation': operation,
    'outputs': outputs,
    'inputs': inputs,
    'version': version,
    'id': None,
}

message = json.dumps(
    handcrafted_creation_tx,
    sort_keys=True,
    separators=(',', ':'),
    ensure_ascii=False,
)

message = sha3.sha3_256(message.encode())

ed25519.sign(message.digest(), base58.b58decode(alice.private_key))

fulfillment_uri = ed25519.serialize_uri()

handcrafted_creation_tx['inputs'][0]['fulfillment'] = fulfillment_uri

json_str_tx = json.dumps(
handcrafted_creation_tx,
sort_keys=True,
separators=(',', ':'),
ensure_ascii=False,
)

creation_txid = sha3.sha3_256(json_str_tx.encode()).hexdigest()

handcrafted_creation_tx['id'] = creation_txid
send the transaction

To send it over to BigchainDB we have different options. You can chose from three different methods to change the broadcasting API used in Tendermint. By choosing a mode, a new transaction can be pushed with a different mode. The recommended mode for basic usages is commit, which will wait until the transaction is committed to a block or a timeout is reached. The sync mode will return after the transaction is validated, while async will return right away.

Warning

The method .send will be deprecated in the next release of the driver, please use .send_commit, .send_sync, or .send_async instead.

from bigchaindb_driver import BigchainDB

bdb = BigchainDB('http://bdb-server:9984')
returned_creation_tx = bdb.transactions.send_async(handcrafted_creation_tx)

A quick check:

>>> json.dumps(returned_creation_tx, sort_keys=True) == json.dumps(handcrafted_creation_tx, sort_keys=True)
True

Bicycle Asset Transfer Revisited

In the bicycle transfer example , we showed that the transfer transaction was prepared and fulfilled as follows:

In [69]: from bigchaindb_driver import BigchainDB

In [70]: from bigchaindb_driver.offchain import fulfill_transaction, prepare_transaction

In [71]: from bigchaindb_driver.crypto import generate_keypair

In [72]: alice, bob = generate_keypair(), generate_keypair()

In [73]: bdb = BigchainDB('https://example.com:9984') # Use YOUR BigchainDB Root URL here

In [74]: bicycle_asset = {
   ....:     'data': {
   ....:          'bicycle': {
   ....:               'serial_number': 'abcd1234',
   ....:               'manufacturer': 'bkfab'
   ....:          },
   ....:     },
   ....: }
   ....: 

In [75]: bicycle_asset_metadata = {
   ....:     'planet': 'earth'
   ....: }
   ....: 

In [76]: prepared_creation_tx = bdb.transactions.prepare(
   ....:     operation='CREATE',
   ....:     signers=alice.public_key,
   ....:     asset=bicycle_asset,
   ....:     metadata=bicycle_asset_metadata
   ....: )
   ....: 

In [77]: fulfilled_creation_tx = bdb.transactions.fulfill(
   ....:     prepared_creation_tx,
   ....:     private_keys=alice.private_key
   ....: )
   ....: 

In [78]: creation_tx = fulfilled_creation_tx

In [79]: output_index = 0

In [80]: output = creation_tx['outputs'][output_index]

In [81]: transfer_input = {
   ....:     'fulfillment': output['condition']['details'],
   ....:     'fulfills': {
   ....:          'output_index': output_index,
   ....:          'transaction_id': creation_tx['id'],
   ....:     },
   ....:     'owners_before': output['public_keys'],
   ....: }
   ....: 

In [82]: transfer_asset = {
   ....:     'id': creation_tx['id'],
   ....: }
   ....: 

In [83]: prepared_transfer_tx = prepare_transaction(
   ....:     operation='TRANSFER',
   ....:     asset=transfer_asset,
   ....:     inputs=transfer_input,
   ....:     recipients=bob.public_key,
   ....: )
   ....: 

In [84]: fulfilled_transfer_tx = fulfill_transaction(
   ....:     prepared_transfer_tx,
   ....:     private_keys=alice.private_key,
   ....: )
   ....: 

In [85]: fulfilled_transfer_tx
Out[85]: 
{'asset': {'id': 'c9937546611878270d521c252f9aacc350a365644405bddc2e395b7d218792d4'},
 'id': 'ebf2ad06cd8519a18285b6e2b4c62166a2704b8c999e1a7456e4d41230f9af2a',
 'inputs': [{'fulfillment': 'pGSAID2O1-RfCLUoW5D29la-pBDhz_ljzCOi-Aw_cWPEgmkLgUB_5sUMxdt5yz7kyo0At1TmbhbgY-l9f7fIGNKmPUf4neJttgOa9FwPnXmC-Zw0T_e3uwB62291jCyeULkP8VQC',
   'fulfills': {'output_index': 0,
    'transaction_id': 'c9937546611878270d521c252f9aacc350a365644405bddc2e395b7d218792d4'},
   'owners_before': ['59JCevK3PfQDJHEHvFa2FPxH5S9X1m11sgx9H2jzXysQ']}],
 'metadata': None,
 'operation': 'TRANSFER',
 'outputs': [{'amount': '1',
   'condition': {'details': {'public_key': '2fP9mKs4fwHMiNQCh1izhWKtwxz7KwhUkTz8PTLvbnnd',
     'type': 'ed25519-sha-256'},
    'uri': 'ni:///sha-256;OJyRif5lWMqXQJ0kRlzs4MWFlbqAirR3tNxFgsL5Tug?fpt=ed25519-sha-256&cost=131072'},
   'public_keys': ['2fP9mKs4fwHMiNQCh1izhWKtwxz7KwhUkTz8PTLvbnnd']}],
 'version': '2.0'}

Our goal is now to handcraft a payload equal to fulfilled_transfer_tx with the help of

  • json: to serialize the transaction dictionary into a JSON formatted string.
  • sha3: to hash the serialized transaction
  • cryptoconditions: to create conditions and fulfillments

The Prepared Transaction

version
In [86]: version = '2.0'
asset

The asset payload for TRANSFER transaction is a dict with only the asset id (i.e. the id of the CREATE transaction for the asset):

In [87]: asset = {'id': creation_tx['id']}
metadata
In [88]: metadata = None
operation
In [89]: operation = 'TRANSFER'
outputs
In [90]: from cryptoconditions import Ed25519Sha256

In [91]: import base58

In [92]: ed25519 = Ed25519Sha256(public_key=base58.b58decode(bob.public_key))

In [93]: output = {
   ....:     'amount': '1',
   ....:     'condition': {
   ....:         'details': {
   ....:             'type': ed25519.TYPE_NAME,
   ....:             'public_key': base58.b58encode(ed25519.public_key).decode(),
   ....:         },
   ....:         'uri': ed25519.condition_uri,
   ....:     },
   ....:     'public_keys': (bob.public_key,),
   ....: }
   ....: 

In [94]: outputs = (output,)
fulfillments
In [95]: input_ = {
   ....:     'fulfillment': None,
   ....:     'fulfills': {
   ....:         'transaction_id': creation_tx['id'],
   ....:         'output_index': 0,
   ....:     },
   ....:     'owners_before': (alice.public_key,)
   ....: }
   ....: 

In [96]: inputs = (input_,)

A few notes:

  • The fulfills field points to the condition (in a transaction) that needs to be fulfilled;
  • The 'fulfillment' value is None as it will be set during the fulfillment step; and
  • The 'owners_before' field identifies the fulfiller(s).

Putting it all together:

In [97]: handcrafted_transfer_tx = {
   ....:     'asset': asset,
   ....:     'metadata': metadata,
   ....:     'operation': operation,
   ....:     'outputs': outputs,
   ....:     'inputs': inputs,
   ....:     'version': version,
   ....:     'id': None,
   ....: }
   ....: 

In [98]: handcrafted_transfer_tx
Out[98]: 
{'asset': {'id': 'c9937546611878270d521c252f9aacc350a365644405bddc2e395b7d218792d4'},
 'id': None,
 'inputs': ({'fulfillment': None,
   'fulfills': {'output_index': 0,
    'transaction_id': 'c9937546611878270d521c252f9aacc350a365644405bddc2e395b7d218792d4'},
   'owners_before': ('59JCevK3PfQDJHEHvFa2FPxH5S9X1m11sgx9H2jzXysQ',)},),
 'metadata': None,
 'operation': 'TRANSFER',
 'outputs': ({'amount': '1',
   'condition': {'details': {'public_key': '2fP9mKs4fwHMiNQCh1izhWKtwxz7KwhUkTz8PTLvbnnd',
     'type': 'ed25519-sha-256'},
    'uri': 'ni:///sha-256;OJyRif5lWMqXQJ0kRlzs4MWFlbqAirR3tNxFgsL5Tug?fpt=ed25519-sha-256&cost=131072'},
   'public_keys': ('2fP9mKs4fwHMiNQCh1izhWKtwxz7KwhUkTz8PTLvbnnd',)},),
 'version': '2.0'}

Note how handcrafted_creation_tx includes a key-value pair 'id': None. The ‘id’ value is None as it will be set during the fulfillment step.

You may observe that

In [99]: handcrafted_transfer_tx == prepared_transfer_tx
Out[99]: False
In [100]: from copy import deepcopy

In [101]: # back up

In [102]: prepared_transfer_tx_bk = deepcopy(prepared_transfer_tx)

In [103]: # set fulfillment to None

In [104]: prepared_transfer_tx['inputs'][0]['fulfillment'] = None

In [105]: handcrafted_transfer_tx == prepared_transfer_tx
Out[105]: False

Are still not equal because we used tuples instead of lists.

In [106]: # serialize to json str

In [107]: import json

In [108]: json_str_handcrafted_tx = json.dumps(handcrafted_transfer_tx, sort_keys=True)

In [109]: json_str_prepared_tx = json.dumps(prepared_transfer_tx, sort_keys=True)
In [110]: json_str_handcrafted_tx == json_str_prepared_tx
Out[110]: True

In [111]: prepared_transfer_tx = prepared_transfer_tx_bk
Up to now

Let’s recap how we got here:

from cryptoconditions import Ed25519Sha256
from bigchaindb_driver.crypto import CryptoKeypair
import base58

bob = CryptoKeypair(
    public_key=bob.public_key,
    private_key=bob.private_key,
)

operation = 'TRANSFER'
version = '2.0'
asset = {'id': handcrafted_creation_tx['id']}
metadata = None

ed25519 = Ed25519Sha256(public_key=base58.b58decode(bob.public_key))

output = {
    'amount': '1',
    'condition': {
        'details': {
            'type': ed25519.TYPE_NAME,
            'public_key': base58.b58encode(ed25519.public_key).decode(),
        },
        'uri': ed25519.condition_uri,
    },
    'public_keys': (bob.public_key,),
}
outputs = (output,)

input_ = {
    'fulfillment': None,
    'fulfills': {
        'transaction_id': handcrafted_creation_tx['id'],
        'output_index': 0,
    },
    'owners_before': (alice.public_key,)
}
inputs = (input_,)

handcrafted_transfer_tx = {
    'asset': asset,
    'metadata': metadata,
    'operation': operation,
    'outputs': outputs,
    'inputs': inputs,
    'version': version,
    'id': None,
}

The Fulfilled Transaction

In [112]: from bigchaindb_driver.offchain import fulfill_transaction

In [113]: from sha3 import sha3_256

In [114]: # fulfill prepared transaction

In [115]: fulfilled_transfer_tx = fulfill_transaction(
   .....:     prepared_transfer_tx,
   .....:     private_keys=alice.private_key,
   .....: )
   .....: 

In [116]: # fulfill handcrafted transaction (with our previously built ED25519 fulfillment)

In [117]: ed25519.to_dict()
Out[117]: 
{'public_key': b'2fP9mKs4fwHMiNQCh1izhWKtwxz7KwhUkTz8PTLvbnnd',
 'signature': None,
 'type': 'ed25519-sha-256'}

In [118]: message = json.dumps(
   .....:     handcrafted_transfer_tx,
   .....:     sort_keys=True,
   .....:     separators=(',', ':'),
   .....:     ensure_ascii=False,
   .....: )
   .....: 

In [119]: message = sha3_256(message.encode())

In [120]: message.update('{}{}'.format(
   .....:     handcrafted_transfer_tx['inputs'][0]['fulfills']['transaction_id'],
   .....:     handcrafted_transfer_tx['inputs'][0]['fulfills']['output_index']).encode()
   .....: )
   .....: 

In [121]: ed25519.sign(message.digest(), base58.b58decode(alice.private_key))
Out[121]: b'\x7f\xe6\xc5\x0c\xc5\xdby\xcb>\xe4\xca\x8d\x00\xb7T\xe6n\x16\xe0c\xe9}\x7f\xb7\xc8\x18\xd2\xa6=G\xf8\x9d\xe2m\xb6\x03\x9a\xf4\\\x0f\x9dy\x82\xf9\x9c4O\xf7\xb7\xbb\x00z\xdbou\x8c,\x9eP\xb9\x0f\xf1T\x02'

In [122]: fulfillment_uri = ed25519.serialize_uri()

In [123]: handcrafted_transfer_tx['inputs'][0]['fulfillment'] = fulfillment_uri
id
In [124]: import json

In [125]: from sha3 import sha3_256

In [126]: json_str_tx = json.dumps(
   .....:     handcrafted_transfer_tx,
   .....:     sort_keys=True,
   .....:     separators=(',', ':'),
   .....:     ensure_ascii=False,
   .....: )
   .....: 

In [127]: transfer_txid = sha3_256(json_str_tx.encode()).hexdigest()

In [128]: handcrafted_transfer_tx['id'] = transfer_txid

Compare this to the txid of the transaction generated via prepare_transaction()

In [129]: transfer_txid == fulfilled_transfer_tx['id']
Out[129]: True

Let’s check this:

In [130]: fulfilled_transfer_tx['inputs'][0]['fulfillment'] == fulfillment_uri
Out[130]: True

In [131]: json.dumps(fulfilled_transfer_tx, sort_keys=True) == json.dumps(handcrafted_transfer_tx, sort_keys=True)
Out[131]: True

In a nutshell

import json

import base58
import sha3
from cryptoconditions import Ed25519Sha256

from bigchaindb_driver.crypto import generate_keypair

bob = generate_keypair()

operation = 'TRANSFER'
version = '2.0'
asset = {'id': handcrafted_creation_tx['id']}
metadata = None

ed25519 = Ed25519Sha256(public_key=base58.b58decode(bob.public_key))

output = {
    'amount': '1',
    'condition': {
        'details': {
            'type': ed25519.TYPE_NAME,
            'public_key': base58.b58encode(ed25519.public_key).decode(),
        },
        'uri': ed25519.condition_uri,
    },
    'public_keys': (bob.public_key,),
}
outputs = (output,)

input_ = {
    'fulfillment': None,
    'fulfills': {
        'transaction_id': handcrafted_creation_tx['id'],
        'output_index': 0,
    },
    'owners_before': (alice.public_key,)
}
inputs = (input_,)

handcrafted_transfer_tx = {
    'asset': asset,
    'metadata': metadata,
    'operation': operation,
    'outputs': outputs,
    'inputs': inputs,
    'version': version,
    'id': None,
}

message = json.dumps(
    handcrafted_transfer_tx,
    sort_keys=True,
    separators=(',', ':'),
    ensure_ascii=False,
)

message = sha3.sha3_256(message.encode())

message.update('{}{}'.format(
    handcrafted_transfer_tx['inputs'][0]['fulfills']['transaction_id'],
    handcrafted_transfer_tx['inputs'][0]['fulfills']['output_index']).encode()
)

ed25519.sign(message.digest(), base58.b58decode(alice.private_key))

fulfillment_uri = ed25519.serialize_uri()

handcrafted_transfer_tx['inputs'][0]['fulfillment'] = fulfillment_uri

json_str_tx = json.dumps(
    handcrafted_transfer_tx,
    sort_keys=True,
    separators=(',', ':'),
    ensure_ascii=False,
)

transfer_txid = sha3.sha3_256(json_str_tx.encode()).hexdigest()

handcrafted_transfer_tx['id'] = transfer_txid

To send it over to BigchainDB we have different options. You can chose from three different methods to change the broadcasting API used in Tendermint. By choosing a mode, a new transaction can be pushed with a different mode. The recommended mode for basic usages is commit, which will wait until the transaction is committed to a block or a timeout is reached. The sync mode will return after the transaction is validated, while async will return right away.

Warning

The method .send will be deprecated in the next release of the driver, please use .send_commit, .send_sync, or .send_async instead.

from bigchaindb_driver import BigchainDB

bdb = BigchainDB('http://bdb-server:9984')
returned_transfer_tx = bdb.transactions.send_async(handcrafted_transfer_tx)

A quick check:

>>> json.dumps(returned_transfer_tx, sort_keys=True) == json.dumps(handcrafted_transfer_tx, sort_keys=True)
True

Bicycle Sharing Revisited

Handcrafting the CREATE transaction for our bicycle sharing example:

import json

import base58
import sha3
from cryptoconditions import Ed25519Sha256

from bigchaindb_driver.crypto import generate_keypair


bob, carly = generate_keypair(), generate_keypair()
version = '2.0'

bicycle_token = {
    'data': {
        'token_for': {
            'bicycle': {
                'serial_number': 'abcd1234',
                'manufacturer': 'bkfab'
            }
        },
        'description': 'Time share token. Each token equals one hour of riding.',
    },
}

# CRYPTO-CONDITIONS: instantiate an Ed25519 crypto-condition for carly
ed25519 = Ed25519Sha256(public_key=base58.b58decode(carly.public_key))

# CRYPTO-CONDITIONS: generate the condition uri
condition_uri = ed25519.condition.serialize_uri()

# CRYPTO-CONDITIONS: construct an unsigned fulfillment dictionary
unsigned_fulfillment_dict = {
    'type': ed25519.TYPE_NAME,
    'public_key': base58.b58encode(ed25519.public_key).decode(),
}

output = {
    'amount': '10',
    'condition': {
        'details': unsigned_fulfillment_dict,
        'uri': condition_uri,
    },
    'public_keys': (carly.public_key,),
}

input_ = {
    'fulfillment': None,
    'fulfills': None,
    'owners_before': (bob.public_key,)
}

token_creation_tx = {
    'operation': 'CREATE',
    'asset': bicycle_token,
    'metadata': None,
    'outputs': (output,),
    'inputs': (input_,),
    'version': version,
    'id': None,
}

# JSON: serialize the transaction-without-id to a json formatted string
message = json.dumps(
    token_creation_tx,
    sort_keys=True,
    separators=(',', ':'),
    ensure_ascii=False,
)

message = sha3.sha3_256(message.encode())

# CRYPTO-CONDITIONS: sign the serialized transaction-without-id
ed25519.sign(message.digest(), base58.b58decode(bob.private_key))

# CRYPTO-CONDITIONS: generate the fulfillment uri
fulfillment_uri = ed25519.serialize_uri()

# add the fulfillment uri (signature)
token_creation_tx['inputs'][0]['fulfillment'] = fulfillment_uri

# JSON: serialize the id-less transaction to a json formatted string
json_str_tx = json.dumps(
    token_creation_tx,
    sort_keys=True,
    separators=(',', ':'),
    ensure_ascii=False,
)

# SHA3: hash the serialized id-less transaction to generate the id
shared_creation_txid = sha3.sha3_256(json_str_tx.encode()).hexdigest()

# add the id
token_creation_tx['id'] = shared_creation_txid

To send it over to BigchainDB we have different options. You can chose from three different methods to change the broadcasting API used in Tendermint. By choosing a mode, a new transaction can be pushed with a different mode. The recommended mode for basic usages is commit, which will wait until the transaction is committed to a block or a timeout is reached. The sync mode will return after the transaction is validated, while async will return right away.

Warning

The method .send will be deprecated in the next release of the driver, please use .send_commit, .send_sync, or .send_async instead.

from bigchaindb_driver import BigchainDB

bdb = BigchainDB('http://bdb-server:9984')
returned_creation_tx = bdb.transactions.send_async(token_creation_tx)

A few checks:

>>> json.dumps(returned_creation_tx, sort_keys=True) == json.dumps(token_creation_tx, sort_keys=True)
True

>>> token_creation_tx['inputs'][0]['owners_before'][0] == bob.public_key
True

>>> token_creation_tx['outputs'][0]['public_keys'][0] == carly.public_key
True

>>> token_creation_tx['outputs'][0]['amount'] == '10'
True

Now Carly wants to ride the bicycle for 2 hours so she needs to send 2 tokens to Bob:

# CRYPTO-CONDITIONS: instantiate an Ed25519 crypto-condition for carly
bob_ed25519 = Ed25519Sha256(public_key=base58.b58decode(bob.public_key))

# CRYPTO-CONDITIONS: instantiate an Ed25519 crypto-condition for carly
carly_ed25519 = Ed25519Sha256(public_key=base58.b58decode(carly.public_key))

# CRYPTO-CONDITIONS: generate the condition uris
bob_condition_uri = bob_ed25519.condition.serialize_uri()
carly_condition_uri = carly_ed25519.condition.serialize_uri()

# CRYPTO-CONDITIONS: get the unsigned fulfillment dictionary (details)
bob_unsigned_fulfillment_dict = {
    'type': bob_ed25519.TYPE_NAME,
    'public_key': base58.b58encode(bob_ed25519.public_key).decode(),
}

carly_unsigned_fulfillment_dict = {
    'type': carly_ed25519.TYPE_NAME,
    'public_key': base58.b58encode(carly_ed25519.public_key).decode(),
}

bob_output = {
    'amount': '2',
    'condition': {
        'details': bob_unsigned_fulfillment_dict,
        'uri': bob_condition_uri,
    },
    'public_keys': (bob.public_key,),
}

carly_output = {
    'amount': '8',
    'condition': {
        'details': carly_unsigned_fulfillment_dict,
        'uri': carly_condition_uri,
    },
    'public_keys': (carly.public_key,),
}

input_ = {
    'fulfillment': None,
    'fulfills': {
        'transaction_id': token_creation_tx['id'],
        'output_index': 0,
    },
    'owners_before': (carly.public_key,)
}

token_transfer_tx = {
    'operation': 'TRANSFER',
    'asset': {'id': token_creation_tx['id']},
    'metadata': None,
    'outputs': (bob_output, carly_output),
    'inputs': (input_,),
    'version': version,
    'id': None,
}

# JSON: serialize the transaction-without-id to a json formatted string
message = json.dumps(
    token_transfer_tx,
    sort_keys=True,
    separators=(',', ':'),
    ensure_ascii=False,
)

message = sha3.sha3_256(message.encode())

message.update('{}{}'.format(
    token_transfer_tx['inputs'][0]['fulfills']['transaction_id'],
    token_transfer_tx['inputs'][0]['fulfills']['output_index']).encode()
)

# CRYPTO-CONDITIONS: sign the serialized transaction-without-id for bob
carly_ed25519.sign(message.digest(), base58.b58decode(carly.private_key))

# CRYPTO-CONDITIONS: generate bob's fulfillment uri
fulfillment_uri = carly_ed25519.serialize_uri()

# add bob's fulfillment uri (signature)
token_transfer_tx['inputs'][0]['fulfillment'] = fulfillment_uri

# JSON: serialize the id-less transaction to a json formatted string
json_str_tx = json.dumps(
    token_transfer_tx,
    sort_keys=True,
    separators=(',', ':'),
    ensure_ascii=False,
)

# SHA3: hash the serialized id-less transaction to generate the id
shared_transfer_txid = sha3.sha3_256(json_str_tx.encode()).hexdigest()

# add the id
token_transfer_tx['id'] = shared_transfer_txid

To send it over to BigchainDB we have different options. You can chose from three different methods to change the broadcasting API used in Tendermint. By choosing a mode, a new transaction can be pushed with a different mode. The recommended mode for basic usages is commit, which will wait until the transaction is committed to a block or a timeout is reached. The sync mode will return after the transaction is validated, while async will return right away.

Warning

The method .send will be deprecated in the next release of the driver, please use .send_commit, .send_sync, or .send_async instead.

from bigchaindb_driver import BigchainDB

bdb = BigchainDB('http://bdb-server:9984')
returned_transfer_tx = bdb.transactions.send_async(token_transfer_tx)

A few checks:

>>> json.dumps(returned_transfer_tx, sort_keys=True) == json.dumps(token_transfer_tx, sort_keys=True)
True

>>> token_transfer_tx['inputs'][0]['owners_before'][0] == carly.public_key
True

Multiple Owners Revisited

Walkthrough

We’ll re-use the example of Alice and Bob owning a car together to handcraft transactions with multiple owners.

Create test user: alice and bob

In [132]: from bigchaindb_driver.crypto import generate_keypair

In [133]: alice, bob = generate_keypair(), generate_keypair()

Say alice and bob own a car together:

In [134]: from bigchaindb_driver import offchain

In [135]: from bigchaindb_driver import BigchainDB

In [136]: bdb_root_url = 'https://example.com:9984' # Use YOUR BigchainDB Root URL here

In [137]: bdb = BigchainDB(bdb_root_url)

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

In [139]: car_creation_tx = offchain.prepare_transaction(
   .....:     operation='CREATE',
   .....:     signers=alice.public_key,
   .....:     recipients=(alice.public_key, bob.public_key),
   .....:     asset=car_asset,
   .....: )
   .....: 

In [140]: signed_car_creation_tx = offchain.fulfill_transaction(
   .....:     car_creation_tx,
   .....:     private_keys=alice.private_key,
   .....: )
   .....: 

In [141]: signed_car_creation_tx
Out[141]: 
{'asset': {'data': {'car': {'vin': '5YJRE11B781000196'}}},
 'id': '50c7d6ec16a57ca8b9c3e9c79e187eef79e3d8efca62cb66f8ffb0fd09fc1340',
 'inputs': [{'fulfillment': 'pGSAIDAEchZ-rhmplKn5KF7tQEaalfQG6RIwUxGvCPEEdm3AgUDKqMoxngCO5OsG21Ptyp6ZX-IdsiIKGc9vRIeYXkln2L9k-DAk1MNlpChBDQsTPV1C4ObXtD2iWFhCzvxRqf4J',
   'fulfills': None,
   'owners_before': ['4ESW5DUkzRLJn4nybSh6XXdEpsuGtb2x126FRfRJJayu']}],
 'metadata': None,
 'operation': 'CREATE',
 'outputs': [{'amount': '1',
   'condition': {'details': {'subconditions': [{'public_key': '4ESW5DUkzRLJn4nybSh6XXdEpsuGtb2x126FRfRJJayu',
       'type': 'ed25519-sha-256'},
      {'public_key': '3BPyskoWjJYZFvQxUu1E8wH7Aa9jBQB5www3JC9Tajy7',
       'type': 'ed25519-sha-256'}],
     'threshold': 2,
     'type': 'threshold-sha-256'},
    'uri': 'ni:///sha-256;yxLQl2VnIBP_E9BIn4hz8dxKr5sMd8WgpIYw_aNMhW0?fpt=threshold-sha-256&cost=264192&subtypes=ed25519-sha-256'},
   'public_keys': ['4ESW5DUkzRLJn4nybSh6XXdEpsuGtb2x126FRfRJJayu',
    '3BPyskoWjJYZFvQxUu1E8wH7Aa9jBQB5www3JC9Tajy7']}],
 'version': '2.0'}

To send it over to BigchainDB we have different options. You can chose from three different methods to change the broadcasting API used in Tendermint. By choosing a mode, a new transaction can be pushed with a different mode. The recommended mode for basic usages is commit, which will wait until the transaction is committed to a block or a timeout is reached. The sync mode will return after the transaction is validated, while async will return right away.

Warning

The method .send will be deprecated in the next release of the driver, please use .send_commit, .send_sync, or .send_async instead.

sent_car_tx = bdb.transactions.send_async(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 [142]: carol = generate_keypair()

In [143]: output_index = 0

In [144]: output = signed_car_creation_tx['outputs'][output_index]

In [145]: input_ = {
   .....:     'fulfillment': output['condition']['details'],
   .....:     'fulfills': {
   .....:         'output_index': output_index,
   .....:         'transaction_id': signed_car_creation_tx['id'],
   .....:     },
   .....:     'owners_before': output['public_keys'],
   .....: }
   .....: 

In [146]: asset = signed_car_creation_tx['id']

In [147]: car_transfer_tx = offchain.prepare_transaction(
   .....:     operation='TRANSFER',
   .....:     recipients=carol.public_key,
   .....:     asset={'id': asset},
   .....:     inputs=input_,
   .....: )
   .....: 

In [148]: signed_car_transfer_tx = offchain.fulfill_transaction(
   .....:     car_transfer_tx, private_keys=[alice.private_key, bob.private_key]
   .....: )
   .....: 

In [149]: signed_car_transfer_tx
Out[149]: 
{'asset': {'id': '50c7d6ec16a57ca8b9c3e9c79e187eef79e3d8efca62cb66f8ffb0fd09fc1340'},
 'id': 'd10938492b841e2a315a39068d2927d9bab5dea4390aed385c4b6cf5dcad3cae',
 'inputs': [{'fulfillment': 'ooHRoIHMpGSAICBhKovYSBhvgE2cgq5c9phbhPCXu9dAEhcaLrNdH_GmgUAzaRG2xkB0849SEc3ynPyNle65L3feu1UfoU0cHHyzSUQBcrJmJtJe1nQIvXhAM4mBtC02eSF8US_vMPNrO1gIpGSAIDAEchZ-rhmplKn5KF7tQEaalfQG6RIwUxGvCPEEdm3AgUCtlmv7wC1_V585-4_Hz7-HUHYeqxXbIvtZw6HGpkxfTGPYzyR846lDz3-EtaHQl_SLQx8kcGdnVUTyqhlmnLsNoQA',
   'fulfills': {'output_index': 0,
    'transaction_id': '50c7d6ec16a57ca8b9c3e9c79e187eef79e3d8efca62cb66f8ffb0fd09fc1340'},
   'owners_before': ['4ESW5DUkzRLJn4nybSh6XXdEpsuGtb2x126FRfRJJayu',
    '3BPyskoWjJYZFvQxUu1E8wH7Aa9jBQB5www3JC9Tajy7']}],
 'metadata': None,
 'operation': 'TRANSFER',
 'outputs': [{'amount': '1',
   'condition': {'details': {'public_key': '8D7ZidqCVbTF4DjURDdjXadtTLTfGUBwH1CjXkeXfKiS',
     'type': 'ed25519-sha-256'},
    'uri': 'ni:///sha-256;au7KHkHZFta2H55ePECfO-6ziQUTR3OmWiQAxN-huxw?fpt=ed25519-sha-256&cost=131072'},
   'public_keys': ['8D7ZidqCVbTF4DjURDdjXadtTLTfGUBwH1CjXkeXfKiS']}],
 'version': '2.0'}
sent_car_transfer_tx = bdb.transactions.send_async(signed_car_transfer_tx)
Doing this manually

In order to do this manually, let’s first import the necessary tools (json, sha3, and cryptoconditions):

In [150]: import json

In [151]: import base58

In [152]: from sha3 import sha3_256

In [153]: from cryptoconditions import Ed25519Sha256, ThresholdSha256

Create the asset, setting all values:

In [154]: car_asset = {
   .....:     'data': {
   .....:         'car': {
   .....:             'vin': '5YJRE11B781000196',
   .....:         },
   .....:     },
   .....: }
   .....: 

Generate the output condition:

In [155]: alice_ed25519 = Ed25519Sha256(public_key=base58.b58decode(alice.public_key))

In [156]: bob_ed25519 = Ed25519Sha256(public_key=base58.b58decode(bob.public_key))

In [157]: threshold_sha256 = ThresholdSha256(threshold=2)

In [158]: threshold_sha256.add_subfulfillment(alice_ed25519)

In [159]: threshold_sha256.add_subfulfillment(bob_ed25519)

In [160]: condition_uri = threshold_sha256.condition.serialize_uri()

In [161]: condition_details = {
   .....:     'subconditions': [
   .....:         {'type': s['body'].TYPE_NAME,
   .....:          'public_key': base58.b58encode(s['body'].public_key).decode()}
   .....:         for s in threshold_sha256.subconditions
   .....:         if (s['type'] == 'fulfillment' and
   .....:             s['body'].TYPE_NAME == 'ed25519-sha-256')
   .....:      ],
   .....:     'threshold': threshold_sha256.threshold,
   .....:     'type': threshold_sha256.TYPE_NAME,
   .....: }
   .....: 

In [162]: output = {
   .....:     'amount': '1',
   .....:     'condition': {
   .....:         'details': condition_details,
   .....:         'uri': condition_uri,
   .....:     },
   .....:     'public_keys': (alice.public_key, bob.public_key),
   .....: }
   .....: 

Tip

The condition uri could have been generated in a slightly different way, which may be more intuitive to you. You can think of the threshold condition containing sub conditions:

In [163]: alt_threshold_sha256 = ThresholdSha256(threshold=2)

In [164]: alt_threshold_sha256.add_subcondition(alice_ed25519.condition)

In [165]: alt_threshold_sha256.add_subcondition(bob_ed25519.condition)

In [166]: alt_threshold_sha256.condition.serialize_uri() == condition_uri
Out[166]: True

The details on the other hand hold the associated fulfillments not yet fulfilled.

The yet to be fulfilled input:

In [167]: input_ = {
   .....:     'fulfillment': None,
   .....:     'fulfills': None,
   .....:     'owners_before': (alice.public_key,),
   .....: }
   .....: 

Craft the payload:

In [168]: version = '2.0'

In [169]: handcrafted_car_creation_tx = {
   .....:     'operation': 'CREATE',
   .....:     'asset': car_asset,
   .....:     'metadata': None,
   .....:     'outputs': (output,),
   .....:     'inputs': (input_,),
   .....:     'version': version,
   .....:     'id': None,
   .....: }
   .....: 

Sign the transaction:

In [170]: message = json.dumps(
   .....:     handcrafted_car_creation_tx,
   .....:     sort_keys=True,
   .....:     separators=(',', ':'),
   .....:     ensure_ascii=False,
   .....: )
   .....: 

In [171]: alice_ed25519.sign(message.encode(), base58.b58decode(alice.private_key))
Out[171]: b"\xa5\xa2\xf9\xfaR1\x0ew\x99\x8a\x1e\n\xff\xf5\xc2\xb5'\x1c\xe2kG\x10\xab\xf8\x82\xc5`\x84S\xe2\xa2\x92w\xa1\x88u\xe6B\x9bo8\x8a\xf5$\xc3\x0cJ\xd0\x02\x0e\xc7\x14\xf3*\x1d\xfaq?\x0b\xe3W\x99\xce\x06"

In [172]: fulfillment_uri = alice_ed25519.serialize_uri()

In [173]: handcrafted_car_creation_tx['inputs'][0]['fulfillment'] = fulfillment_uri

Generate the id, by hashing the encoded json formatted string representation of the transaction:

In [174]: json_str_tx = json.dumps(
   .....:     handcrafted_car_creation_tx,
   .....:     sort_keys=True,
   .....:     separators=(',', ':'),
   .....:     ensure_ascii=False,
   .....: )
   .....: 

In [175]: car_creation_txid = sha3_256(json_str_tx.encode()).hexdigest()

In [176]: handcrafted_car_creation_tx['id'] = car_creation_txid

Let’s make sure our txid is the same as the one provided by the driver:

In [177]: handcrafted_car_creation_tx['id'] == signed_car_creation_tx['id']
Out[177]: False

Compare our CREATE transaction with the driver’s:

In [178]: (json.dumps(handcrafted_car_creation_tx, sort_keys=True) ==
   .....:  json.dumps(signed_car_creation_tx, sort_keys=True))
   .....: 
Out[178]: False

The transfer to Carol:

In [179]: alice_ed25519 = Ed25519Sha256(public_key=base58.b58decode(alice.public_key))

In [180]: bob_ed25519 = Ed25519Sha256(public_key=base58.b58decode(bob.public_key))

In [181]: carol_ed25519 = Ed25519Sha256(public_key=base58.b58decode(carol.public_key))

In [182]: unsigned_fulfillments_dict = {
   .....:     'type': carol_ed25519.TYPE_NAME,
   .....:     'public_key': base58.b58encode(carol_ed25519.public_key).decode(),
   .....: }
   .....: 

In [183]: condition_uri = carol_ed25519.condition.serialize_uri()

In [184]: output = {
   .....:     'amount': '1',
   .....:     'condition': {
   .....:         'details': unsigned_fulfillments_dict,
   .....:         'uri': condition_uri,
   .....:     },
   .....:     'public_keys': (carol.public_key,),
   .....: }
   .....: 

The yet to be fulfilled input:

In [185]: input_ = {
   .....:     'fulfillment': None,
   .....:     'fulfills': {
   .....:         'transaction_id': handcrafted_car_creation_tx['id'],
   .....:         'output_index': 0,
   .....:     },
   .....:     'owners_before': (alice.public_key, bob.public_key),
   .....: }
   .....: 

Craft the payload:

In [186]: handcrafted_car_transfer_tx = {
   .....:     'operation': 'TRANSFER',
   .....:     'asset': {'id': handcrafted_car_creation_tx['id']},
   .....:     'metadata': None,
   .....:     'outputs': (output,),
   .....:     'inputs': (input_,),
   .....:     'version': version,
   .....:     'id': None,
   .....: }
   .....: 

Sign the transaction:

In [187]: message = json.dumps(
   .....:     handcrafted_car_transfer_tx,
   .....:     sort_keys=True,
   .....:     separators=(',', ':'),
   .....:     ensure_ascii=False,
   .....: )
   .....: 

In [188]: threshold_sha256 = ThresholdSha256(threshold=2)

In [189]: alice_ed25519.sign(message=message.encode(),
   .....:     private_key=base58.b58decode(alice.private_key))
   .....: 
Out[189]: b'\x193\xdcv\x1e\x9b\x1d\x1f/\x07\xfa\xbfo\xd0q~\x17F\x17\xb6\x94b\x12-P\xe1\x8a\xeb\xc7\xeeV\xf0\xed\xfc$\xcb\xa6\xea\n+U\xd9v\xe1j\x94\xa7\x90<\xe2\r\xda\x05\xab>\xee\xbf\xc4\xc4\x108\x15Z\x00'

In [190]: bob_ed25519.sign(message=message.encode(),
   .....:     private_key=base58.b58decode(bob.private_key))
   .....: 
Out[190]: b'\x99\xbbZ\xa6\xa2\xfc\x08\x18Ig\xbb\xc04\x9c\x8d\xda\x02o\xb5\xc1\xa55\xe2\xad=1\x06\xf4\xa7\x02[\xed\x93B\xa1\xa7\xd4U\xb7\x01\x9a\xfc\x94\xe8\x94L\xeeA\x88=\xc0"\xd6\xbaOY\xf2\xd3\xc1.8?*\x07'

In [191]: threshold_sha256.add_subfulfillment(alice_ed25519)

In [192]: threshold_sha256.add_subfulfillment(bob_ed25519)

In [193]: fulfillment_uri = threshold_sha256.serialize_uri()

In [194]: handcrafted_car_transfer_tx['inputs'][0]['fulfillment'] = fulfillment_uri

Generate the id, by hashing the encoded json formatted string representation of the transaction:

In [195]: json_str_tx = json.dumps(
   .....:     handcrafted_car_transfer_tx,
   .....:     sort_keys=True,
   .....:     separators=(',', ':'),
   .....:     ensure_ascii=False,
   .....: )
   .....: 

In [196]: car_transfer_txid = sha3_256(json_str_tx.encode()).hexdigest()

In [197]: handcrafted_car_transfer_tx['id'] = car_transfer_txid

Let’s make sure our txid is the same as the one provided by the driver:

In [198]: handcrafted_car_transfer_tx['id'] == signed_car_transfer_tx['id']
Out[198]: False

Compare our TRANSFER transaction with the driver’s:

In [199]: (json.dumps(handcrafted_car_transfer_tx, sort_keys=True) ==
   .....:  json.dumps(signed_car_transfer_tx, sort_keys=True))
   .....: 
Out[199]: False

In a nutshell

Handcrafting the 'CREATE' transaction
import json

import base58
from sha3 import sha3_256
from cryptoconditions import Ed25519Sha256, ThresholdSha256

from bigchaindb_driver.crypto import generate_keypair

version = '2.0'

car_asset = {
    'data': {
        'car': {
            'vin': '5YJRE11B781000196',
        },
    },
}

alice, bob = generate_keypair(), generate_keypair()

# CRYPTO-CONDITIONS: instantiate an Ed25519 crypto-condition for alice
alice_ed25519 = Ed25519Sha256(public_key=base58.b58decode(alice.public_key))

# CRYPTO-CONDITIONS: instantiate an Ed25519 crypto-condition for bob
bob_ed25519 = Ed25519Sha256(public_key=base58.b58decode(bob.public_key))

# CRYPTO-CONDITIONS: instantiate a threshold SHA 256 crypto-condition
threshold_sha256 = ThresholdSha256(threshold=2)

# CRYPTO-CONDITIONS: add alice ed25519 to the threshold SHA 256 condition
threshold_sha256.add_subfulfillment(alice_ed25519)

# CRYPTO-CONDITIONS: add bob ed25519 to the threshold SHA 256 condition
threshold_sha256.add_subfulfillment(bob_ed25519)

# CRYPTO-CONDITIONS: generate the condition uri
condition_uri = threshold_sha256.condition.serialize_uri()

# CRYPTO-CONDITIONS: get the unsigned fulfillment dictionary (details)
condition_details = {
    'subconditions': [
        {'type': s['body'].TYPE_NAME,
         'public_key': base58.b58encode(s['body'].public_key).decode()}
        for s in threshold_sha256.subconditions
        if (s['type'] == 'fulfillment' and
            s['body'].TYPE_NAME == 'ed25519-sha-256')
    ],
    'threshold': threshold_sha256.threshold,
    'type': threshold_sha256.TYPE_NAME,
}

output = {
    'amount': '1',
    'condition': {
        'details': condition_details,
        'uri': condition_uri,
    },
    'public_keys': (alice.public_key, bob.public_key),
}

# The yet to be fulfilled input:
input_ = {
    'fulfillment': None,
    'fulfills': None,
    'owners_before': (alice.public_key,),
}

# Craft the payload:
handcrafted_car_creation_tx = {
    'operation': 'CREATE',
    'asset': car_asset,
    'metadata': None,
    'outputs': (output,),
    'inputs': (input_,),
    'version': version,
    'id': None,
}

# JSON: serialize the transaction-without-id to a json formatted string
message = json.dumps(
    handcrafted_car_creation_tx,
    sort_keys=True,
    separators=(',', ':'),
    ensure_ascii=False,
)
message = sha3_256(message.encode())

# CRYPTO-CONDITIONS: sign the serialized transaction-without-id
alice_ed25519.sign(message.digest(), base58.b58decode(alice.private_key))

# CRYPTO-CONDITIONS: generate the fulfillment uri
fulfillment_uri = alice_ed25519.serialize_uri()

# add the fulfillment uri (signature)
handcrafted_car_creation_tx['inputs'][0]['fulfillment'] = fulfillment_uri

# JSON: serialize the id-less transaction to a json formatted string
# Generate the id, by hashing the encoded json formatted string representation of
# the transaction:
json_str_tx = json.dumps(
    handcrafted_car_creation_tx,
    sort_keys=True,
    separators=(',', ':'),
    ensure_ascii=False,
)

# SHA3: hash the serialized id-less transaction to generate the id
car_creation_txid = sha3_256(json_str_tx.encode()).hexdigest()

# add the id
handcrafted_car_creation_tx['id'] = car_creation_txid

To send it over to BigchainDB we have different options. You can chose from three different methods to change the broadcasting API used in Tendermint. By choosing a mode, a new transaction can be pushed with a different mode. The recommended mode for basic usages is commit, which will wait until the transaction is committed to a block or a timeout is reached. The sync mode will return after the transaction is validated, while async will return right away.

Warning

The method .send will be deprecated in the next release of the driver, please use .send_commit, .send_sync, or .send_async instead.

from bigchaindb_driver import BigchainDB

bdb = BigchainDB('http://bdb-server:9984')
returned_car_creation_tx = bdb.transactions.send_async(handcrafted_car_creation_tx)
Handcrafting the 'TRANSFER' transaction
carol = generate_keypair()

alice_ed25519 = Ed25519Sha256(public_key=base58.b58decode(alice.public_key))

bob_ed25519 = Ed25519Sha256(public_key=base58.b58decode(bob.public_key))

carol_ed25519 = Ed25519Sha256(public_key=base58.b58decode(carol.public_key))

unsigned_fulfillments_dict = {
    'type': carol_ed25519.TYPE_NAME,
    'public_key': base58.b58encode(carol_ed25519.public_key).decode(),
}

condition_uri = carol_ed25519.condition.serialize_uri()

output = {
    'amount': '1',
    'condition': {
        'details': unsigned_fulfillments_dict,
        'uri': condition_uri,
    },
    'public_keys': (carol.public_key,),
}

# The yet to be fulfilled input:
input_ = {
    'fulfillment': None,
    'fulfills': {
        'transaction_id': handcrafted_car_creation_tx['id'],
        'output_index': 0,
    },
    'owners_before': (alice.public_key, bob.public_key),
}

# Craft the payload:
handcrafted_car_transfer_tx = {
    'operation': 'TRANSFER',
    'asset': {'id': handcrafted_car_creation_tx['id']},
    'metadata': None,
    'outputs': (output,),
    'inputs': (input_,),
    'version': version,
    'id': None,
}

# Sign the transaction:
message = json.dumps(
    handcrafted_car_transfer_tx,
    sort_keys=True,
    separators=(',', ':'),
    ensure_ascii=False,
)

message = sha3_256(message.encode())

message.update('{}{}'.format(
    handcrafted_car_transfer_tx['inputs'][0]['fulfills']['transaction_id'],
    handcrafted_car_transfer_tx['inputs'][0]['fulfills']['output_index']).encode()
)

threshold_sha256 = ThresholdSha256(threshold=2)

alice_ed25519.sign(message=message.digest(),
                   private_key=base58.b58decode(alice.private_key))
bob_ed25519.sign(message=message.digest(),
                 private_key=base58.b58decode(bob.private_key))

threshold_sha256.add_subfulfillment(alice_ed25519)

threshold_sha256.add_subfulfillment(bob_ed25519)

fulfillment_uri = threshold_sha256.serialize_uri()

handcrafted_car_transfer_tx['inputs'][0]['fulfillment'] = fulfillment_uri

# Generate the id, by hashing the encoded json formatted string
# representation of the transaction:
json_str_tx = json.dumps(
    handcrafted_car_transfer_tx,
    sort_keys=True,
    separators=(',', ':'),
    ensure_ascii=False,
)

car_transfer_txid = sha3_256(json_str_tx.encode()).hexdigest()

handcrafted_car_transfer_tx['id'] = car_transfer_txid

To send it over to BigchainDB we have different options. You can chose from three different methods to change the broadcasting API used in Tendermint. By choosing a mode, a new transaction can be pushed with a different mode. The recommended mode for basic usages is commit, which will wait until the transaction is committed to a block or a timeout is reached. The sync mode will return after the transaction is validated, while async will return right away.

Warning

The method .send will be deprecated in the next release of the driver, please use .send_commit, .send_sync, or .send_async instead.

bdb = BigchainDB('http://bdb-server:9984')
returned_car_transfer_tx = bdb.transactions.send_async(handcrafted_car_transfer_tx)

Multiple Owners with m-of-n Signatures

In this example, alice and bob co-own a car asset such that only one of them is required to sign the transfer transaction. The example is very similar to the one where both owners are required to sign, but with minor differences that are very important, in order to make the fulfillment URI valid.

We only show the “nutshell” version for now. The example is self-contained.

In a nutshell

Handcrafting the 'CREATE' transaction
import json

import base58
import sha3
from cryptoconditions import Ed25519Sha256, ThresholdSha256

from bigchaindb_driver.crypto import generate_keypair


version = '2.0'

car_asset = {
    'data': {
        'car': {
            'vin': '5YJRE11B781000196',
        },
    },
}

alice, bob = generate_keypair(), generate_keypair()

# CRYPTO-CONDITIONS: instantiate an Ed25519 crypto-condition for alice
alice_ed25519 = Ed25519Sha256(public_key=base58.b58decode(alice.public_key))

# CRYPTO-CONDITIONS: instantiate an Ed25519 crypto-condition for bob
bob_ed25519 = Ed25519Sha256(public_key=base58.b58decode(bob.public_key))

# CRYPTO-CONDITIONS: instantiate a threshold SHA 256 crypto-condition
# NOTICE that the threshold is set to 1, not 2
threshold_sha256 = ThresholdSha256(threshold=1)

# CRYPTO-CONDITIONS: add alice ed25519 to the threshold SHA 256 condition
threshold_sha256.add_subfulfillment(alice_ed25519)

# CRYPTO-CONDITIONS: add bob ed25519 to the threshold SHA 256 condition
threshold_sha256.add_subfulfillment(bob_ed25519)

# CRYPTO-CONDITIONS: generate the condition uri
condition_uri = threshold_sha256.condition.serialize_uri()

# CRYPTO-CONDITIONS: get the unsigned fulfillment dictionary (details)
condition_details = {
    'subconditions': [
        {'type': s['body'].TYPE_NAME,
         'public_key': base58.b58encode(s['body'].public_key).decode()}
        for s in threshold_sha256.subconditions
        if (s['type'] == 'fulfillment' and
            s['body'].TYPE_NAME == 'ed25519-sha-256')
    ],
    'threshold': threshold_sha256.threshold,
    'type': threshold_sha256.TYPE_NAME,
}

output = {
    'amount': '1',
    'condition': {
        'details': condition_details,
        'uri': condition_uri,
    },
    'public_keys': (alice.public_key, bob.public_key),
}

# The yet to be fulfilled input:
input_ = {
    'fulfillment': None,
    'fulfills': None,
    'owners_before': (alice.public_key,),
}

# Craft the payload:
handcrafted_car_creation_tx = {
    'operation': 'CREATE',
    'asset': car_asset,
    'metadata': None,
    'outputs': (output,),
    'inputs': (input_,),
    'version': version,
    'id': None,
}

# JSON: serialize the transaction-without-id to a json formatted string
message = json.dumps(
    handcrafted_car_creation_tx,
    sort_keys=True,
    separators=(',', ':'),
    ensure_ascii=False,
)

message = sha3.sha3_256(message.encode())

# CRYPTO-CONDITIONS: sign the serialized transaction-without-id
alice_ed25519.sign(message.digest(), base58.b58decode(alice.private_key))

# CRYPTO-CONDITIONS: generate the fulfillment uri
fulfillment_uri = alice_ed25519.serialize_uri()

# add the fulfillment uri (signature)
handcrafted_car_creation_tx['inputs'][0]['fulfillment'] = fulfillment_uri

# JSON: serialize the id-less transaction to a json formatted string
# Generate the id, by hashing the encoded json formatted string representation of
# the transaction:
json_str_tx = json.dumps(
    handcrafted_car_creation_tx,
    sort_keys=True,
    separators=(',', ':'),
    ensure_ascii=False,
)

# SHA3: hash the serialized id-less transaction to generate the id
car_creation_txid = sha3.sha3_256(json_str_tx.encode()).hexdigest()

# add the id
handcrafted_car_creation_tx['id'] = car_creation_txid

To send it over to BigchainDB we have different options. You can chose from three different methods to change the broadcasting API used in Tendermint. By choosing a mode, a new transaction can be pushed with a different mode. The recommended mode for basic usages is commit, which will wait until the transaction is committed to a block or a timeout is reached. The sync mode will return after the transaction is validated, while async will return right away.

Warning

The method .send will be deprecated in the next release of the driver, please use .send_commit, .send_sync, or .send_async instead.

from bigchaindb_driver import BigchainDB

bdb = BigchainDB('http://bdb-server:9984')
returned_car_creation_tx = bdb.transactions.send_async(handcrafted_car_creation_tx)
Handcrafting the 'TRANSFER' transaction
version = '2.0'

carol = generate_keypair()

alice_ed25519 = Ed25519Sha256(public_key=base58.b58decode(alice.public_key))

bob_ed25519 = Ed25519Sha256(public_key=base58.b58decode(bob.public_key))

carol_ed25519 = Ed25519Sha256(public_key=base58.b58decode(carol.public_key))

condition_uri = carol_ed25519.condition.serialize_uri()

output = {
    'amount': '1',
    'condition': {
        'details': {
            'type': carol_ed25519.TYPE_NAME,
            'public_key': base58.b58encode(carol_ed25519.public_key).decode(),
        },
        'uri': condition_uri,
    },
    'public_keys': (carol.public_key,),
}

# The yet to be fulfilled input:
input_ = {
    'fulfillment': None,
    'fulfills': {
        'transaction_id': handcrafted_car_creation_tx['id'],
        'output_index': 0,
    },
    'owners_before': (alice.public_key, bob.public_key),
}

# Craft the payload:
handcrafted_car_transfer_tx = {
    'operation': 'TRANSFER',
    'asset': {'id': handcrafted_car_creation_tx['id']},
    'metadata': None,
    'outputs': (output,),
    'inputs': (input_,),
    'version': version,
    'id': None,
}

# Sign the transaction:
message = json.dumps(
    handcrafted_car_transfer_tx,
    sort_keys=True,
    separators=(',', ':'),
    ensure_ascii=False,
)

message = sha3.sha3_256(message.encode())

message.update('{}{}'.format(
    handcrafted_car_transfer_tx['inputs'][0]['fulfills']['transaction_id'],
    handcrafted_car_transfer_tx['inputs'][0]['fulfills']['output_index']).encode())

threshold_sha256 = ThresholdSha256(threshold=1)

alice_ed25519.sign(message.digest(),
                   private_key=base58.b58decode(alice.private_key))

threshold_sha256.add_subfulfillment(alice_ed25519)

threshold_sha256.add_subcondition(bob_ed25519.condition)

fulfillment_uri = threshold_sha256.serialize_uri()

handcrafted_car_transfer_tx['inputs'][0]['fulfillment'] = fulfillment_uri

# Generate the id, by hashing the encoded json formatted string
# representation of the transaction:
json_str_tx = json.dumps(
    handcrafted_car_transfer_tx,
    sort_keys=True,
    separators=(',', ':'),
    ensure_ascii=False,
)

car_transfer_txid = sha3.sha3_256(json_str_tx.encode()).hexdigest()

handcrafted_car_transfer_tx['id'] = car_transfer_txid

To send it over to BigchainDB we have different options. You can chose from three different methods to change the broadcasting API used in Tendermint. By choosing a mode, a new transaction can be pushed with a different mode. The recommended mode for basic usages is commit, which will wait until the transaction is committed to a block or a timeout is reached. The sync mode will return after the transaction is validated, while async will return right away.

Warning

The method .send will be deprecated in the next release of the driver, please use .send_commit, .send_sync, or .send_async instead.

bdb = BigchainDB('http://bdb-server:9984')
returned_car_transfer_tx = bdb.transactions.send_async(handcrafted_car_transfer_tx)

Advanced Installation Options

Installing from the Source Code

The source code for the BigchainDB Python Driver can be downloaded from the Github repo. You can either clone the public repository:

git clone git://github.com/bigchaindb/bigchaindb-driver

Or download the tarball:

curl  -OL https://github.com/bigchaindb/bigchaindb-driver/tarball/master

Once you have a copy of the source code, you can install it by going to the directory containing setup.py and doing:

python setup.py install

Installing latest master with pip

To work with the latest BigchainDB (server) master branch:

$ pip install --process-dependency-links git+https://github.com/bigchaindb/bigchaindb-driver.git

Then connect to some BigchainDB node which is running BigchainDB server master.

Upgrading

Upgrading Using pip

If you installed the BigchainDB Python Driver using pip install bigchaindb_driver, then you can upgrade it to the latest version using:

pip install --upgrade bigchaindb_driver

Library Reference

driver

class bigchaindb_driver.BigchainDB(*nodes, transport_class=<class 'bigchaindb_driver.transport.Transport'>, headers=None, timeout=20)[source]

A BigchainDB driver is able to create, sign, and submit transactions to one or more nodes in a Federation.

If initialized with >1 nodes, the driver will send successive requests to different nodes in a round-robin fashion (this will be customizable in the future).

__init__(*nodes, transport_class=<class 'bigchaindb_driver.transport.Transport'>, headers=None, timeout=20)[source]

Initialize a BigchainDB driver instance.

Parameters:
  • *nodes (list of (str or dict)) – BigchainDB nodes to connect to. Currently, the full URL must be given. In the absence of any node, the default('http://localhost:9984') will be used. If node is passed as a dict, endpoint is a required key; headers is an optional dict of headers.
  • transport_class – Optional transport class to use. Defaults to Transport.
  • headers (dict) – Optional headers that will be passed with each request. To pass headers only on a per-request basis, you can pass the headers to the method of choice (e.g. BigchainDB().transactions.send_commit()).
  • timeout (int) – Optional timeout in seconds that will be passed to each request.
api_info(headers=None)[source]

Retrieves information provided by the API root endpoint '/api/v1'.

Parameters:headers (dict) – Optional headers to pass to the request.
Returns:Details of the HTTP API provided by the BigchainDB server.
Return type:dict
assets

AssetsEndpoint – Exposes functionalities of the '/assets' endpoint.

blocks

BlocksEndpoint – Exposes functionalities of the '/blocks' endpoint.

info(headers=None)[source]

Retrieves information of the node being connected to via the root endpoint '/'.

Parameters:headers (dict) – Optional headers to pass to the request.
Returns:Details of the node that this instance is connected to. Some information that may be interesting:
  • the server version and
  • an overview of all the endpoints
Return type:dict

Note

Currently limited to one node, and will be expanded to return information for each node that this instance is connected to.

metadata

MetadataEndpoint – Exposes functionalities of the '/metadata' endpoint.

nodes

tuple of str – URLs of connected nodes.

outputs

OutputsEndpoint – Exposes functionalities of the '/outputs' endpoint.

transactions

TransactionsEndpoint – Exposes functionalities of the '/transactions' endpoint.

transport

Transport – Object responsible for forwarding requests to a Connection instance (node).

class bigchaindb_driver.driver.TransactionsEndpoint(driver)[source]

Exposes functionality of the '/transactions/' endpoint.

path

str – The path of the endpoint.

static fulfill(transaction, private_keys)[source]

Fulfills the given transaction.

Parameters:
  • transaction (dict) – The transaction to be fulfilled.
  • private_keys (str | list | tuple) – One or more private keys to be used for fulfilling the transaction.
Returns:

The fulfilled transaction payload, ready to be sent to a BigchainDB federation.

Return type:

dict

Raises:

MissingPrivateKeyError – If a private key is missing.

get(*, asset_id, operation=None, headers=None)[source]

Given an asset id, get its list of transactions (and optionally filter for only 'CREATE' or 'TRANSFER' transactions).

Parameters:
  • asset_id (str) – Id of the asset.
  • operation (str) – The type of operation the transaction should be. Either 'CREATE' or 'TRANSFER'. Defaults to None.
  • headers (dict) – Optional headers to pass to the request.

Note

Please note that the id of an asset in BigchainDB is actually the id of the transaction which created the asset. In other words, when querying for an asset id with the operation set to 'CREATE', only one transaction should be expected. This transaction will be the transaction in which the asset was created, and the transaction id will be equal to the given asset id. Hence, the following calls to retrieve() and get() should return the same transaction.

>>> bdb = BigchainDB()
>>> bdb.transactions.retrieve('foo')
>>> bdb.transactions.get(asset_id='foo', operation='CREATE')

Since get() returns a list of transactions, it may be more efficient to use retrieve() instead, if one is only interested in the 'CREATE' operation.

Returns:List of transactions.
Return type:list
static prepare(*, operation='CREATE', signers=None, recipients=None, asset=None, metadata=None, inputs=None)[source]

Prepares a transaction payload, ready to be fulfilled.

Parameters:
  • operation (str) – The operation to perform. Must be 'CREATE' or 'TRANSFER'. Case insensitive. Defaults to 'CREATE'.
  • signers (list | tuple | str, optional) – One or more public keys representing the issuer(s) of the asset being created. Only applies for 'CREATE' operations. Defaults to None.
  • recipients (list | tuple | str, optional) – One or more public keys representing the new recipients(s) of the asset being created or transferred. Defaults to None.
  • asset (dict, optional) – The asset to be created or transferred. MUST be supplied for 'TRANSFER' operations. Defaults to None.
  • metadata (dict, optional) – Metadata associated with the transaction. Defaults to None.
  • inputs (dict | list | tuple, optional) – One or more inputs holding the condition(s) that this transaction intends to fulfill. Each input is expected to be a dict. Only applies to, and MUST be supplied for, 'TRANSFER' operations.
Returns:

The prepared transaction.

Return type:

dict

Raises:

BigchaindbException – If operation is not 'CREATE' or 'TRANSFER'.

Important

CREATE operations

  • signers MUST be set.

  • recipients, asset, and metadata MAY be set.

  • If asset is set, it MUST be in the form of:

    {
        'data': {
            ...
        }
    }
    
  • The argument inputs is ignored.

  • If recipients is not given, or evaluates to False, it will be set equal to signers:

    if not recipients:
        recipients = signers
    

TRANSFER operations

  • recipients, asset, and inputs MUST be set.

  • asset MUST be in the form of:

    {
        'id': '<Asset ID (i.e. TX ID of its CREATE transaction)>'
    }
    
  • metadata MAY be set.

  • The argument signers is ignored.

retrieve(txid, headers=None)[source]

Retrieves the transaction with the given id.

Parameters:
  • txid (str) – Id of the transaction to retrieve.
  • headers (dict) – Optional headers to pass to the request.
Returns:

The transaction with the given id.

Return type:

dict

send_async(transaction, headers=None)[source]

Submit a transaction to the Federation with the mode async.

Parameters:
  • transaction (dict) – the transaction to be sent to the Federation node(s).
  • headers (dict) – Optional headers to pass to the request.
Returns:

The transaction sent to the Federation node(s).

Return type:

dict

send_commit(transaction, headers=None)[source]

Submit a transaction to the Federation with the mode commit.

Parameters:
  • transaction (dict) – the transaction to be sent to the Federation node(s).
  • headers (dict) – Optional headers to pass to the request.
Returns:

The transaction sent to the Federation node(s).

Return type:

dict

send_sync(transaction, headers=None)[source]

Submit a transaction to the Federation with the mode sync.

Parameters:
  • transaction (dict) – the transaction to be sent to the Federation node(s).
  • headers (dict) – Optional headers to pass to the request.
Returns:

The transaction sent to the Federation node(s).

Return type:

dict

class bigchaindb_driver.driver.OutputsEndpoint(driver)[source]

Exposes functionality of the '/outputs' endpoint.

path

str – The path of the endpoint.

get(public_key, spent=None, headers=None)[source]

Get transaction outputs by public key. The public_key parameter must be a base58 encoded ed25519 public key associated with transaction output ownership.

Parameters:
  • public_key (str) – Public key for which unfulfilled conditions are sought.
  • spent (bool) – Indicate if the result set should include only spent or only unspent outputs. If not specified (None) the result includes all the outputs (both spent and unspent) associated with the public key.
  • headers (dict) – Optional headers to pass to the request.
Returns:

List of unfulfilled conditions.

Return type:

list of str

Example

Given a transaction with id da1b64a907ba54 having an ed25519 condition (at index 0) with alice’s public key:

>>> bdb = BigchainDB()
>>> bdb.outputs.get(alice_pubkey)
... ['../transactions/da1b64a907ba54/conditions/0']
class bigchaindb_driver.driver.AssetsEndpoint(driver)[source]

Exposes functionality of the '/assets' endpoint.

path

str – The path of the endpoint.

get(*, search, limit=0, headers=None)[source]

Retrieves the assets that match a given text search string.

Parameters:
  • search (str) – Text search string.
  • limit (int) – Limit the number of returned documents. Defaults to zero meaning that it returns all the matching assets.
  • headers (dict) – Optional headers to pass to the request.
Returns:

List of assets that match the query.

Return type:

list of dict

class bigchaindb_driver.driver.NamespacedDriver(driver)[source]

Base class for creating endpoints (namespaced objects) that can be added under the BigchainDB driver.

__init__(driver)[source]

Initializes an instance of NamespacedDriver with the given driver instance.

Parameters:driver (BigchainDB) – Instance of BigchainDB.

offchain

Module for operations that can be performed “offchain”, meaning without a connection to one or more BigchainDB federation nodes.

bigchaindb_driver.offchain.prepare_transaction(*, operation='CREATE', signers=None, recipients=None, asset=None, metadata=None, inputs=None)[source]

Prepares a transaction payload, ready to be fulfilled. Depending on the value of operation, simply dispatches to either prepare_create_transaction() or prepare_transfer_transaction().

Parameters:
  • operation (str) – The operation to perform. Must be 'CREATE' or 'TRANSFER'. Case insensitive. Defaults to 'CREATE'.
  • signers (list | tuple | str, optional) – One or more public keys representing the issuer(s) of the asset being created. Only applies for 'CREATE' operations. Defaults to None.
  • recipients (list | tuple | str, optional) – One or more public keys representing the new recipients(s) of the asset being created or transferred. Defaults to None.
  • asset (dict, optional) – The asset to be created or transferred. MUST be supplied for 'TRANSFER' operations. Defaults to None.
  • metadata (dict, optional) – Metadata associated with the transaction. Defaults to None.
  • inputs (dict | list | tuple, optional) – One or more inputs holding the condition(s) that this transaction intends to fulfill. Each input is expected to be a dict. Only applies to, and MUST be supplied for, 'TRANSFER' operations.
Returns:

The prepared transaction.

Return type:

dict

Raises:

BigchaindbException – If operation is not 'CREATE' or 'TRANSFER'.

Important

CREATE operations

  • signers MUST be set.

  • recipients, asset, and metadata MAY be set.

  • If asset is set, it MUST be in the form of:

    {
        'data': {
            ...
        }
    }
    
  • The argument inputs is ignored.

  • If recipients is not given, or evaluates to False, it will be set equal to signers:

    if not recipients:
        recipients = signers
    

TRANSFER operations

  • recipients, asset, and inputs MUST be set.

  • asset MUST be in the form of:

    {
        'id': '<Asset ID (i.e. TX ID of its CREATE transaction)>'
    }
    
  • metadata MAY be set.

  • The argument signers is ignored.

bigchaindb_driver.offchain.prepare_create_transaction(*, signers, recipients=None, asset=None, metadata=None)[source]

Prepares a "CREATE" transaction payload, ready to be fulfilled.

Parameters:
  • signers (list | tuple | str) – One or more public keys representing the issuer(s) of the asset being created.
  • recipients (list | tuple | str, optional) – One or more public keys representing the new recipients(s) of the asset being created. Defaults to None.
  • asset (dict, optional) – The asset to be created. Defaults to None.
  • metadata (dict, optional) – Metadata associated with the transaction. Defaults to None.
Returns:

The prepared "CREATE" transaction.

Return type:

dict

Important

  • If asset is set, it MUST be in the form of:

    {
        'data': {
            ...
        }
    }
    
  • If recipients is not given, or evaluates to False, it will be set equal to signers:

    if not recipients:
        recipients = signers
    
bigchaindb_driver.offchain.prepare_transfer_transaction(*, inputs, recipients, asset, metadata=None)[source]

Prepares a "TRANSFER" transaction payload, ready to be fulfilled.

Parameters:
  • inputs (dict | list | tuple) – One or more inputs holding the condition(s) that this transaction intends to fulfill. Each input is expected to be a dict.
  • recipients (str | list | tuple) – One or more public keys representing the new recipients(s) of the asset being transferred.
  • asset (dict) – A single-key dictionary holding the id of the asset being transferred with this transaction.
  • metadata (dict) – Metadata associated with the transaction. Defaults to None.
Returns:

The prepared "TRANSFER" transaction.

Return type:

dict

Important

  • asset MUST be in the form of:

    {
        'id': '<Asset ID (i.e. TX ID of its CREATE transaction)>'
    }
    

Example

Todo

Replace this section with docs.

In case it may not be clear what an input should look like, say Alice (public key: '3Cxh1eKZk3Wp9KGBWFS7iVde465UvqUKnEqTg2MW4wNf') wishes to transfer an asset over to Bob (public key: 'EcRawy3Y22eAUSS94vLF8BVJi62wbqbD9iSUSUNU9wAA'). Let the asset creation transaction payload be denoted by tx:

# noqa E501
>>> tx
    {'asset': {'data': {'msg': 'Hello BigchainDB!'}},
     'id': '9650055df2539223586d33d273cb8fd05bd6d485b1fef1caf7c8901a49464c87',
     'inputs': [{'fulfillment': {'public_key': '3Cxh1eKZk3Wp9KGBWFS7iVde465UvqUKnEqTg2MW4wNf',
                                 'type': 'ed25519-sha-256'},
                 'fulfills': None,
                 'owners_before': ['3Cxh1eKZk3Wp9KGBWFS7iVde465UvqUKnEqTg2MW4wNf']}],
     'metadata': None,
     'operation': 'CREATE',
     'outputs': [{'amount': '1',
                  'condition': {'details': {'public_key': '3Cxh1eKZk3Wp9KGBWFS7iVde465UvqUKnEqTg2MW4wNf',
                                            'type': 'ed25519-sha-256'},
                                'uri': 'ni:///sha-256;7ApQLsLLQgj5WOUipJg1txojmge68pctwFxvc3iOl54?fpt=ed25519-sha-256&cost=131072'},
                  'public_keys': ['3Cxh1eKZk3Wp9KGBWFS7iVde465UvqUKnEqTg2MW4wNf']}],
     'version': '2.0'}

Then, the input may be constructed in this way:

output_index
output = tx['transaction']['outputs'][output_index]
input_ = {
    'fulfillment': output['condition']['details'],
    'input': {
        'output_index': output_index,
        'transaction_id': tx['id'],
    },
    'owners_before': output['owners_after'],
}

Displaying the input on the prompt would look like:

>>> input_
{'fulfillment': {
  'public_key': '3Cxh1eKZk3Wp9KGBWFS7iVde465UvqUKnEqTg2MW4wNf',
  'type': 'ed25519-sha-256'},
 'input': {'output_index': 0,
  'transaction_id': '9650055df2539223586d33d273cb8fd05bd6d485b1fef1caf7c8901a49464c87'},
 'owners_before': ['3Cxh1eKZk3Wp9KGBWFS7iVde465UvqUKnEqTg2MW4wNf']}

To prepare the transfer:

>>> prepare_transfer_transaction(
...     inputs=input_,
...     recipients='EcRawy3Y22eAUSS94vLF8BVJi62wbqbD9iSUSUNU9wAA',
...     asset=tx['transaction']['asset'],
... )
bigchaindb_driver.offchain.fulfill_transaction(transaction, *, private_keys)[source]

Fulfills the given transaction.

Parameters:
  • transaction (dict) – The transaction to be fulfilled.
  • private_keys (str | list | tuple) – One or more private keys to be used for fulfilling the transaction.
Returns:

The fulfilled transaction payload, ready to be sent to a BigchainDB federation.

Return type:

dict

Raises:

MissingPrivateKeyError – If a private key is missing.

transport

class bigchaindb_driver.transport.Transport(*nodes, timeout=None)[source]

Transport class.

__init__(*nodes, timeout=None)[source]

Initializes an instance of Transport.

Parameters:
  • nodes – each node is a dictionary with the keys endpoint and headers
  • timeout (int) – Optional timeout in seconds.
forward_request(method, path=None, json=None, params=None, headers=None)[source]

Makes HTTP requests to the configured nodes.

Retries connection errors (e.g. DNS failures, refused connection, etc). A user may choose to retry other errors by catching the corresponding exceptions and retrying forward_request.

Exponential backoff is implemented individually for each node. Backoff delays are expressed as timestamps stored on the object and they are not reset in between multiple function calls.

Times out when self.timeout is expired, if not None.

Parameters:
  • method (str) – HTTP method name (e.g.: 'GET').
  • path (str) – Path to be appended to the base url of a node. E.g.: '/transactions').
  • json (dict) – Payload to be sent with the HTTP request.
  • params (dict)) – Dictionary of URL (query) parameters.
  • headers (dict) – Optional headers to pass to the request.
Returns:

Result of requests.models.Response.json()

Return type:

dict

pool

class bigchaindb_driver.pool.Pool(connections, picker_class=<class 'bigchaindb_driver.pool.RoundRobinPicker'>)[source]

Pool of connections.

__init__(connections, picker_class=<class 'bigchaindb_driver.pool.RoundRobinPicker'>)[source]

Initializes a Pool instance.

Parameters:connections (list) – List of Connection instances.
get_connection()[source]

Gets a Connection instance from the pool.

Returns:A Connection instance.
class bigchaindb_driver.pool.RoundRobinPicker[source]

Picks a Connection instance from a list of connections.

__init__()

Initialize self. See help(type(self)) for accurate signature.

pick(connections)[source]

Picks a connection with the earliest backoff time.

As a result, the first connection is picked for as long as it has no backoff time. Otherwise, the connections are tried in a round robin fashion.
Parameters:( (connections) – obj:list): List of Connection instances.
class bigchaindb_driver.pool.AbstractPicker[source]

Abstract class for picker classes that pick connections from a pool.

pick(connections)[source]

Picks a Connection instance from the given list of Connection instances.

Parameters:connections (List) – List of Connection instances.

connection

class bigchaindb_driver.connection.Connection(*, node_url, headers=None)[source]

A Connection object to make HTTP requests to a particular node.

__init__(*, node_url, headers=None)[source]

Initializes a Connection instance.

Parameters:
  • node_url (str) – Url of the node to connect to.
  • headers (dict) – Optional headers to send with each request.
request(method, *, path=None, json=None, params=None, headers=None, timeout=None, backoff_cap=None, **kwargs)[source]

Performs an HTTP request with the given parameters.

Implements exponential backoff.

If ConnectionError occurs, a timestamp equal to now + the default delay (BACKOFF_DELAY) is assigned to the object. The timestamp is in UTC. Next time the function is called, it either waits till the timestamp is passed or raises TimeoutError.

If ConnectionError occurs two or more times in a row, the retry count is incremented and the new timestamp is calculated as now + the default delay multiplied by two to the power of the number of retries.

If a request is successful, the backoff timestamp is removed, the retry count is back to zero.

Parameters:
  • method (str) – HTTP method (e.g.: 'GET').
  • path (str) – API endpoint path (e.g.: '/transactions').
  • json (dict) – JSON data to send along with the request.
  • params (dict) – Dictionary of URL (query) parameters.
  • headers (dict) – Optional headers to pass to the request.
  • timeout (int) – Optional timeout in seconds.
  • backoff_cap (int) – The maximal allowed backoff delay in seconds to be assigned to a node.
  • kwargs – Optional keyword arguments.

crypto

class bigchaindb_driver.crypto.CryptoKeypair(private_key, public_key)
private_key

Alias for field number 0

public_key

Alias for field number 1

bigchaindb_driver.crypto.generate_keypair(seed=None)[source]

Generates a cryptographic key pair.

Parameters:seed (bytes) – 32-byte seed for deterministic generation. Defaults to None.
Returns:A collections.namedtuple with named fields private_key and public_key.
Return type:CryptoKeypair

exceptions

Exceptions used by bigchaindb_driver.

exception bigchaindb_driver.exceptions.BigchaindbException[source]

Base exception for all Bigchaindb exceptions.

exception bigchaindb_driver.exceptions.TransportError[source]

Base exception for transport related errors.

This is mainly for cases where the status code denotes an HTTP error, and for cases in which there was a connection error.

exception bigchaindb_driver.exceptions.ConnectionError[source]

Exception for errors occurring when connecting, and/or making a request to Bigchaindb.

exception bigchaindb_driver.exceptions.NotFoundError[source]

Exception for HTTP 404 errors.

exception bigchaindb_driver.exceptions.KeypairNotFoundException[source]

Raised if an operation cannot proceed because the keypair was not given.

exception bigchaindb_driver.exceptions.InvalidPrivateKey[source]

Raised if a private key is invalid. E.g.: None.

exception bigchaindb_driver.exceptions.InvalidPublicKey[source]

Raised if a public key is invalid. E.g.: None.

exception bigchaindb_driver.exceptions.MissingPrivateKeyError[source]

Raised if a private key is missing.

utils

Set of utilities to support various functionalities of the driver.

bigchaindb_driver.utils.ops_map

dict – Mapping between operation strings and classes. E.g.: The string 'CREATE' is mapped to CreateOperation.

class bigchaindb_driver.utils.CreateOperation[source]

Class representing the 'CREATE' transaction operation.

class bigchaindb_driver.utils.TransferOperation[source]

Class representing the 'TRANSFER' transaction operation.

bigchaindb_driver.utils._normalize_operation(operation)[source]

Normalizes the given operation string. For now, this simply means converting the given string to uppercase, looking it up in ops_map, and returning the corresponding class if present.

Parameters:operation (str) – The operation string to convert.
Returns:The class corresponding to the given string, CreateOperation or TransferOperation.

Important

If the str.upper() step, or the ops_map lookup fails, the given operation argument is returned.

bigchaindb_driver.utils.normalize_node(node, headers=None)[source]

Normalizes given node as str or dict with headers

bigchaindb_driver.utils.normalize_nodes(*nodes, headers=None)[source]

Normalizes given dict or array of driver nodes

bigchaindb_driver.utils.normalize_url(node)[source]

Normalizes the given node url

About this Documentation

This section contains instructions to build and view the documentation locally, using the docker-compose.yml file of the bigchaindb-driver repository: https://github.com/bigchaindb/bigchaindb-driver.

If you do not have a clone of the repo, you need to get one.

Building the documentation

To build the docs, simply run

$ docker-compose up -d bdocs

Or if you prefer, start a bash session,

$ docker-compose run --rm bdocs bash

and build the docs:

root@a651959a1f2d:/usr/src/app# make -C docs html

Viewing the documentation

The docs will be hosted on port 55555, and can be accessed over [localhost](http:/localhost:33333), [127.0.0.1](http:/127.0.0.1:33333) OR http:/HOST_IP:33333.

Note

If you are using docker-machine you need to replace localhost with the ip of the machine (e.g.: docker-machine ip tm if your machine is named tm).

Making changes

The necessary source code is mounted, which allows you to make modifications, and view the changes by simply re-building the docs, and refreshing the browser.

Contributing

Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given. If you want to read about our guidelines for contributing, we are using C4 and you can’t find it here: C4

You can contribute in many ways:

Types of Contributions

Report Bugs

Report bugs at https://github.com/bigchaindb/bigchaindb-driver/issues.

If you are reporting a bug, please include:

  • Your operating system name and version.
  • Any details about your local setup that might be helpful in troubleshooting.
  • Detailed steps to reproduce the bug.

Fix Bugs

Look through the GitHub issues for bugs. Anything tagged with “bug” and “help wanted” is open to whoever wants to implement it.

Implement Features

Look through the GitHub issues for features. Anything tagged with “enhancement” and “help wanted” is open to whoever wants to implement it.

Make a Feature Request or Proposal

To make a feature request or proposal, write a BigchaindB Enhancement Proposal (BEP):

We use COSS to handle BEPs, you can read about it here: COSS

Write Documentation

bigchaindb-driver could always use more documentation, whether as part of the official bigchaindb-driver docs, in docstrings, or even on the web in blog posts, articles, and such.

Submit Feedback

The best way to send feedback is to file an issue at https://github.com/bigchaindb/bigchaindb-driver/issues.

If you are proposing a feature:

  • Explain in detail how it would work.
  • Keep the scope as narrow as possible, to make it easier to implement.
  • Remember that this is a volunteer-driven project, and that contributions are welcome :)

Get Started!

Ready to contribute? See the Installation Guide for Developers page.

Pull Request Guidelines

Before you submit a pull request, check that it meets these guidelines:

  1. The pull request should include tests.
  2. If the pull request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring, and add the feature to the list in README.rst.
  3. The pull request should work for Python 3.5, and pass the flake8 check. Check https://travis-ci.org/bigchaindb/bigchaindb-driver/pull_requests and make sure that the tests pass for all supported Python versions.
  4. Follow the pull request template while creating new PRs, the template will be visible to you when you create a new pull request.

Tips

Development Environment with Docker

Depending on what you are doing, you may need to run at least one BigchainDB node. You can use the docker-compose.yml file to run a node, and perform other tasks that depend on the running node. To run a BigchainDB node, (for development), you start a MongoDB and Tendermint node, followed by the linked BigchainDB node:

# Implicitly creates a MongoDB and Tendermint instance
$ docker-compose up -d bigchaindb

You can monitor the logs:

$ docker-compose logs -f

Additionally, we have a nice Makefile to make things easier for everyone. Some helpful commands are

>>> make
install          Install the package to the active Python's site-packages
start            Run BigchainDB driver from source and daemonize it (stop with make stop)
stop             Stop BigchainDB driver
reset            Stop and REMOVE all containers. WARNING: you will LOSE all data stored in BigchainDB server.
test             Run all tests once or specify a file/test with TEST=tests/file.py::Class::test
test-watch       Run all, or only one with TEST=tests/file.py::Class::test, tests and wait. Every time you change code, test/s will be run again.
docs             Generate Sphinx HTML documentation, including API docs
lint             Check style with flake8
cov              Check code coverage and open the result in the browser
clean            Remove all build, test, coverage and Python artifacts
release          package and upload a release
dist             builds source (and not for now, wheel package)
clean-build      Remove build artifacts
clean-pyc        Remove Python file artifacts
clean-test       Remove test and coverage artifacts

Tests

To run a subset of tests:

$ docker-compose run --rm bigchaindb-driver pytest -v tests/test_driver.py

Important

When running tests, unless you are targeting a test that does not require a connection with the BigchainDB server, you need to run the BigchainDB, MongoDB and Tendermint servers:

$ docker-compose up -d bigchaindb

Dependency on Bigchaindb

By default, the development requirements, BigchainDB server Dockerfile, and .travis.yml are set to depend from BigchainDB’s master branch to more easily track changes against BigchainDB’s API.

Credits

Development Lead

Contributors

None yet. Why not be the first?

Changelog

0.6.2 (2018-11-03)

Changed

  • In setup.py, changed python-rapidjson==0.6.0 to ~=0.6.0, and changed requests>=2.11.0 to >=2.20.0

0.6.1 (2018-10-21)

Fixed

  • Fixed the problem with a docs page (Handcrafting Transactions) that wouldn’t build.

0.6.0 (2018-10-20)

Changed

  • Added support for deterministic keypair generation from a 32-byte seed. See pull request #487 by external contributor @excerebrose
  • Pinned cryptoconditions==0.8.0 in setup.py

Removed

  • The send() function was removed. See pull request #483.

Known issues

  • Builds of the Handcrafting Transactions page started failing again, in Travis CI and on ReadTheDocs.

0.5.3 (2018-09-12)

Changed

  • Fixed a failing unit test
  • Pinned cryptoconditions==0.7.2 in setup.py
  • Fixed the Handcrafting Transactions page in the docs

0.5.2 (2018-08-31)

Added

  • Cap exponential backoff depending on timeout value for reasonable waiting time in event of network recovery. #470 <https://github.com/bigchaindb/bigchaindb-driver/pull/470>
  • Update cryptoconditions dependency because of security vulnerability CVE-2018-10903. #472 <https://github.com/bigchaindb/bigchaindb-driver/pull/472>

0.5.1 (2018-08-23)

Added

  • Support for BigchainDB server v2.0.0.b5.
  • added round-robin strategy to connect to nodes of the BigchainDB network BEP 14

0.5.0 (2018-06-14)

Added

  • Added three new methods to send/post a transaction as discussed here:

    • send_commit
    • send_async
    • send_sync

Deprecated

  • send() under TransactionEndpoint, and available via BigchainDB.transactions. Replaced by the above three methods: send_commit(), send_async(), and send_sync().

0.5.0a2 (2018-04-18)

0.5.0a1 (2018-04-03)

There were many changes between BigchainDB 1.3 and BigchainDB 2.0 Alpha, too many to list here. We wrote a series of blog posts to summarize most changes, especially those that affect end users and application developers:

0.4.1 (2017-08-02)

Fixed

0.4.0 (2017-07-05)

Added

  • Support for BigchainDB server (HTTP API) 1.0.0.

0.3.0 (2017-06-23)

Added

  • Support for BigchainDB server (HTTP API) 1.0.0rc1.
  • Support for crypto-conditions RFC draft version 02.
  • Added support for text search endpoint /assets?search=

0.2.0 (2017-02-06)

Added

  • Support for BigchainDB server 0.9.
  • Methods for GET / and GET /api/v1

Changed

  • Node URLs, passed to BigchainDB() MUST not include the api prefix '/api/v1', e.g.:

    • BEFORE: http://localhost:9984/api/v1
    • NOW: http://localhost:9984

0.1.0 (2016-11-29)

Added

  • Support for BigchainDB server 0.8.0.
  • Support for divisible assets.

Removed

  • create() and transfer() under TransactionEndpoint, and available via BigchainDB.transactions. Replaced by the three “canonical” transaction operations: prepare(), fulfill(), and send().
  • Support for client side timestamps.

0.0.3 (2016-11-25)

Added

  • Support for “canonical” transaction operations:

    • prepare
    • fulfill
    • send

Deprecated

  • create() and transfer() under TransactionEndpoint, and available via BigchainDB.transactions. Replaced by the above three “canonical” transaction operations: prepare(), fulfill(), and send().

Fixed

  • BigchainDB() default node setting on its transport class. See commit 0a80206

0.0.2 (2016-10-28)

Added

  • Support for BigchainDB server 0.7.0

0.0.1dev1 (2016-08-25)

  • Development (pre-alpha) release on PyPI.

Added

  • Minimal support for POST (via create() and transfer()), and GET operations on the /transactions endpoint.

0.0.1a1 (2016-08-12)

  • Planning release on PyPI.

Indices and tables