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': 'HFF1j6zLtmpY69djfTey6eTVrCZXgVvWMNDraj4tE51x',
    'type': 'ed25519-sha-256'},
   'fulfills': None,
   'owners_before': ['HFF1j6zLtmpY69djfTey6eTVrCZXgVvWMNDraj4tE51x']}],
 'metadata': {'planet': 'earth'},
 'operation': 'CREATE',
 'outputs': [{'amount': '1',
   'condition': {'details': {'public_key': 'HFF1j6zLtmpY69djfTey6eTVrCZXgVvWMNDraj4tE51x',
     'type': 'ed25519-sha-256'},
    'uri': 'ni:///sha-256;AyLVPCzVRtZXTsvRocbeb_Qy4HL0BPNxx4R6roBj5Lc?fpt=ed25519-sha-256&cost=131072'},
   'public_keys': ['HFF1j6zLtmpY69djfTey6eTVrCZXgVvWMNDraj4tE51x']}],
 'version': '2.0'}

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

In [8]: alice.public_key
Out[8]: 'HFF1j6zLtmpY69djfTey6eTVrCZXgVvWMNDraj4tE51x'

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': 'HFF1j6zLtmpY69djfTey6eTVrCZXgVvWMNDraj4tE51x',
   'type': 'ed25519-sha-256'},
  'uri': 'ni:///sha-256;AyLVPCzVRtZXTsvRocbeb_Qy4HL0BPNxx4R6roBj5Lc?fpt=ed25519-sha-256&cost=131072'},
 'public_keys': ['HFF1j6zLtmpY69djfTey6eTVrCZXgVvWMNDraj4tE51x']}

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]: 'HFF1j6zLtmpY69djfTey6eTVrCZXgVvWMNDraj4tE51x'

Becomes:

In [17]: base58.b58decode(alice.public_key)
Out[17]: b'\xf1`\xf7U\xa9}\x00)\xdd\xa5v=\x1d)\x8cb\x87\x91\x1977\x9a\xc1\xebA&\xe1\xceb2\xd1\xdf'
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;AyLVPCzVRtZXTsvRocbeb_Qy4HL0BPNxx4R6roBj5Lc?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;AyLVPCzVRtZXTsvRocbeb_Qy4HL0BPNxx4R6roBj5Lc?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': 'HFF1j6zLtmpY69djfTey6eTVrCZXgVvWMNDraj4tE51x',
  'type': 'ed25519-sha-256'},
 'fulfills': None,
 'owners_before': ['HFF1j6zLtmpY69djfTey6eTVrCZXgVvWMNDraj4tE51x']}

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

In [31]: prepared_creation_tx['inputs'][0]['fulfillment']
Out[31]: 
{'public_key': 'HFF1j6zLtmpY69djfTey6eTVrCZXgVvWMNDraj4tE51x',
 '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': ('HFF1j6zLtmpY69djfTey6eTVrCZXgVvWMNDraj4tE51x',)},),
 'metadata': {'planet': 'earth'},
 'operation': 'CREATE',
 'outputs': ({'amount': '1',
   'condition': {'details': {'public_key': 'HFF1j6zLtmpY69djfTey6eTVrCZXgVvWMNDraj4tE51x',
     'type': 'ed25519-sha-256'},
    'uri': 'ni:///sha-256;AyLVPCzVRtZXTsvRocbeb_Qy4HL0BPNxx4R6roBj5Lc?fpt=ed25519-sha-256&cost=131072'},
   'public_keys': ('HFF1j6zLtmpY69djfTey6eTVrCZXgVvWMNDraj4tE51x',)},),
 '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'HFF1j6zLtmpY69djfTey6eTVrCZXgVvWMNDraj4tE51x',
 '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'D\xfe\x9beT\xb6G\x0b\xda^\x16\xd0\xd6F\xcc\x0b@\n\x84]:.\xfd\xeca\x98\x9e\xd0\xdfmf\'\xf8\xf1j\xfc\xe8<\x92+\x95:\xe21\xd7\xcc\xbe\xbf\xb0K\xab*yC\x90\x94$\xef`\xb7\xe8"G\x01'

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': 'd11016b9eea20d051811c68c624adf4f6713a2754de6c996b0695a7f158e9ade',
 'inputs': [{'fulfillment': 'pGSAIPFg91WpfQAp3aV2PR0pjGKHkRk3N5rB60Em4c5iMtHfgUBE_ptlVLZHC9peFtDWRswLQAqEXTou_exhmJ7Q321mJ_jxavzoPJIrlTriMdfMvr-wS6sqeUOQlCTvYLfoIkcB',
   'fulfills': None,
   'owners_before': ['HFF1j6zLtmpY69djfTey6eTVrCZXgVvWMNDraj4tE51x']}],
 'metadata': {'planet': 'earth'},
 'operation': 'CREATE',
 'outputs': [{'amount': '1',
   'condition': {'details': {'public_key': 'HFF1j6zLtmpY69djfTey6eTVrCZXgVvWMNDraj4tE51x',
     'type': 'ed25519-sha-256'},
    'uri': 'ni:///sha-256;AyLVPCzVRtZXTsvRocbeb_Qy4HL0BPNxx4R6roBj5Lc?fpt=ed25519-sha-256&cost=131072'},
   'public_keys': ['HFF1j6zLtmpY69djfTey6eTVrCZXgVvWMNDraj4tE51x']}],
 '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': '408939845418b339abec2a5d49bbcd691a828281adce3aadcb9afc83b9fd77c1'},
 'id': 'dfd2d15c8ad64a501f47891d9f8a297555c4c4043f0c9dca92438417eb77fe1b',
 'inputs': [{'fulfillment': 'pGSAIOkSZNMyjA0LOiNjhb0dXdI_5uNiz4ARvudqp2yIx0-VgUCIEwwhsG5jEIZK6Qc0m4Jq7G0Q56y_vswkvXhy9SS1cH7xVRu8A2epfN8uIkLYDpX4_j_50p9Ie27U9UtgROYA',
   'fulfills': {'output_index': 0,
    'transaction_id': '408939845418b339abec2a5d49bbcd691a828281adce3aadcb9afc83b9fd77c1'},
   'owners_before': ['GgpG9ePfwj9U3v5m13QAvtsMkhUoNUj8ASadhkAGxFb2']}],
 'metadata': None,
 'operation': 'TRANSFER',
 'outputs': [{'amount': '1',
   'condition': {'details': {'public_key': '7vBkMMTjKDrS3TwxhknkfKQd6Zo88diNx6ecY5yPTSq4',
     'type': 'ed25519-sha-256'},
    'uri': 'ni:///sha-256;J2ROPTga7OXhjmvrL-gRg48_0WzheR38Y-KKM7EfMfg?fpt=ed25519-sha-256&cost=131072'},
   'public_keys': ['7vBkMMTjKDrS3TwxhknkfKQd6Zo88diNx6ecY5yPTSq4']}],
 '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': '408939845418b339abec2a5d49bbcd691a828281adce3aadcb9afc83b9fd77c1'},
 'id': None,
 'inputs': ({'fulfillment': None,
   'fulfills': {'output_index': 0,
    'transaction_id': '408939845418b339abec2a5d49bbcd691a828281adce3aadcb9afc83b9fd77c1'},
   'owners_before': ('GgpG9ePfwj9U3v5m13QAvtsMkhUoNUj8ASadhkAGxFb2',)},),
 'metadata': None,
 'operation': 'TRANSFER',
 'outputs': ({'amount': '1',
   'condition': {'details': {'public_key': '7vBkMMTjKDrS3TwxhknkfKQd6Zo88diNx6ecY5yPTSq4',
     'type': 'ed25519-sha-256'},
    'uri': 'ni:///sha-256;J2ROPTga7OXhjmvrL-gRg48_0WzheR38Y-KKM7EfMfg?fpt=ed25519-sha-256&cost=131072'},
   'public_keys': ('7vBkMMTjKDrS3TwxhknkfKQd6Zo88diNx6ecY5yPTSq4',)},),
 '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'7vBkMMTjKDrS3TwxhknkfKQd6Zo88diNx6ecY5yPTSq4',
 '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'\x88\x13\x0c!\xb0nc\x10\x86J\xe9\x074\x9b\x82j\xecm\x10\xe7\xac\xbf\xbe\xcc$\xbdxr\xf5$\xb5p~\xf1U\x1b\xbc\x03g\xa9|\xdf."B\xd8\x0e\x95\xf8\xfe?\xf9\xd2\x9fH{n\xd4\xf5K`D\xe6\x00'

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': 'd63dc03d4bf199407510e6e04d55dc46aa9eb1ad56d08d6ae644b1290bde937b',
 'inputs': [{'fulfillment': 'pGSAIAzJkqxwJOOubWZjKKEQVhXZAdDQ30I1DEPoGYCHNsMNgUD-v4tvOBYEDfRbon4mWvej8GfXuwfr5oHx6-t2wxoC08sTLSW1lYyGnXT3EmxMeqtd9Pqls-TgHc88nTjImFUM',
   'fulfills': None,
   'owners_before': ['rvAYQAuB5BTKffLQwT79wpYHRZDwypftAQJ7esMxo24']}],
 'metadata': None,
 'operation': 'CREATE',
 'outputs': [{'amount': '1',
   'condition': {'details': {'subconditions': [{'public_key': 'rvAYQAuB5BTKffLQwT79wpYHRZDwypftAQJ7esMxo24',
       'type': 'ed25519-sha-256'},
      {'public_key': '7m8yRq4e3yEMy5yVoNmA1DcWqmKYnNyacK4MQE2C5o6n',
       'type': 'ed25519-sha-256'}],
     'threshold': 2,
     'type': 'threshold-sha-256'},
    'uri': 'ni:///sha-256;7vDhE_co9Z1zkOjPwvPf-pZJuCf2rIywnoDPMU-GzV4?fpt=threshold-sha-256&cost=264192&subtypes=ed25519-sha-256'},
   'public_keys': ['rvAYQAuB5BTKffLQwT79wpYHRZDwypftAQJ7esMxo24',
    '7m8yRq4e3yEMy5yVoNmA1DcWqmKYnNyacK4MQE2C5o6n']}],
 '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': 'd63dc03d4bf199407510e6e04d55dc46aa9eb1ad56d08d6ae644b1290bde937b'},
 'id': '13f260fdbc344473c405e47ba7ddbb58667a082df76d4bb55cbe824d7d97ece3',
 'inputs': [{'fulfillment': 'ooHRoIHMpGSAIAzJkqxwJOOubWZjKKEQVhXZAdDQ30I1DEPoGYCHNsMNgUB5MczoKI_MbpIIR-Vh64ip_Tdzi5ibZ-n1Y9dQkCdE97hMUK9xzcR3xBtXaxZHotD8uZG_3BmAYtwcz5AVDzIFpGSAIGR0sTA1bP0BC_tZHXNqcYg4CT1eR17hgzFJwny8qQB3gUAxH3aC0htqKWihR1H2206mjRNTSck4iA171IdBGTwiRP3SilUHswjYyTeeUKlzLhGYz2hNVe1kYCgW5oX2jXcHoQA',
   'fulfills': {'output_index': 0,
    'transaction_id': 'd63dc03d4bf199407510e6e04d55dc46aa9eb1ad56d08d6ae644b1290bde937b'},
   'owners_before': ['rvAYQAuB5BTKffLQwT79wpYHRZDwypftAQJ7esMxo24',
    '7m8yRq4e3yEMy5yVoNmA1DcWqmKYnNyacK4MQE2C5o6n']}],
 'metadata': None,
 'operation': 'TRANSFER',
 'outputs': [{'amount': '1',
   'condition': {'details': {'public_key': '7y6Yu8awD4sUC336KaRcdM4afd8VZM4edPcVa8QChdL3',
     'type': 'ed25519-sha-256'},
    'uri': 'ni:///sha-256;LpxGj384cZ9zxIl7TxXseTqS7GMIr4WI8QVyhI_04Ww?fpt=ed25519-sha-256&cost=131072'},
   'public_keys': ['7y6Yu8awD4sUC336KaRcdM4afd8VZM4edPcVa8QChdL3']}],
 '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'\xb8\xc6\xf1\x16\x05\x13\xd5\x93\xd6<\xfd\xa9\xb4\xd5\x00\x89\x19d\x9af2\xaa\xee\xe4\x9a\xbb\xe7\x1a\x18\x87\x0c\xdf\xb0F\x15\x13S\xdb\x7f\x18\xd2\x9f\n\r7\xdf\n\xbd\x0b\xa1q\xb5I\x1e\x99\x96\xef\\\x8c\x14\x98\x0c\xd7\x03'

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'\x88\x18\xde\xa9\xafV\rlJr\x16\x18\xd1\xda\xd2t\x9e\xec\xe8\xc2\t\xdaN\xcb\x19^\xff\x86q5\xbfD\xb9\xc5Oz\xf7h\x93\x0c\xabws% \x01o\xa37\xcb\x89\xf9`\x95\xcfU\xc1\x03\xc1\xcd\xadx_\x01'

In [190]: bob_ed25519.sign(message=message.encode(),
   .....:     private_key=base58.b58decode(bob.private_key))
   .....: 
Out[190]: b'\x8cF\x91\xf1\xaa\xeb\t\xacv\xec\xc3wSy\xd6\xc8\xb9Cs\xed\x85\xad\xfa\xc6\xcd`0>\x03\xbe,\xcdS\x81\x13\xfel@\xba\xe7\xdcg\x150\x05\xe7\x800i"\xbd\xc9\xd8PO\x08\xc5?7\xc1\xba\xe7\xe4\x03'

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)