diff --git a/docs/source/python-server-api-examples.md b/docs/source/python-server-api-examples.md index 2b2bfd86..85a3809e 100644 --- a/docs/source/python-server-api-examples.md +++ b/docs/source/python-server-api-examples.md @@ -69,17 +69,52 @@ After a couple of seconds, we can check if the transactions was included in the # retrieve a transaction from the bigchain tx_retrieved = b.get_transaction(tx_signed['id']) - 'id': '6539dded9479c47b3c83385ae569ecaa90bcf387240d1ee2ea3ae0f7986aeddd', - 'transaction': { 'current_owner': 'pvGtcm5dvwWMzCqagki1N6CDKYs2J1cCwTNw8CqJic3Q', - 'data': { 'hash': '872fa6e6f46246cd44afdb2ee9cfae0e72885fb0910e2bcf9a5a2a4eadb417b8', - 'payload': {'msg': 'Hello BigchainDB!'}}, - 'input': None, - 'new_owner': 'ssQnnjketNYmbU3hwgFMEQsc4JVYAmZyWHnHCtFS8aeA', - 'operation': 'CREATE', - 'timestamp': '1455108421.753908'}} +{ + "id": "cdb6331f26ecec0ee7e67e4d5dcd63734e7f75bbd1ebe40699fc6d2960ae4cb2", + "transaction": { + "conditions": [ + { + "cid": 0, + "condition": { + "details": { + "bitmask": 32, + "public_key": "DTJCqP3sNkZcpoSA8bCtGwZ4ASfRLsMFXZDCmMHzCoeJ", + "signature": null, + "type": "fulfillment", + "type_id": 4 + }, + "uri": "cc:1:20:uQjL_E_uT1yUsJpVi1X7x2G7B15urzIlKN5fUufehTM:98" + }, + "new_owners": [ + "DTJCqP3sNkZcpoSA8bCtGwZ4ASfRLsMFXZDCmMHzCoeJ" + ] + } + ], + "data": { + "hash": "872fa6e6f46246cd44afdb2ee9cfae0e72885fb0910e2bcf9a5a2a4eadb417b8", + "payload": { + "msg": "Hello BigchainDB!" + } + }, + "fulfillments": [ + { + "current_owners": [ + "3LQ5dTiddXymDhNzETB1rEkp4mA7fEV1Qeiu5ghHiJm9" + ], + "fid": 0, + "fulfillment": "cf:1:4:ICKvgXHM8K2jNlKRfkwz3cCvH0OiF5A_-riWsQWXffOMQCyqbFgSDfKTaKRQHypHr5z5jsXzCQ4dKgYkmUo55CMxYs3TT2OxGiV0bZ7Tzn1lcLhpyutGZWm8xIyJKJmmSQQ", + "input": null + } + ], + "operation": "CREATE", + "timestamp": "1460450439.267737" + }, + "version": 1 +} + ``` -The new owner of the digital asset is now `ssQnnjketNYmbU3hwgFMEQsc4JVYAmZyWHnHCtFS8aeA`, which is the public key of `testuser1`. +The new owner of the digital asset is now `ACJyBLfeLNpCPrGWYoPYvnQ2MAC8BFukBko4hxtW9YoH`, which is the public key of `testuser1`. ## Transfer the Digital Asset @@ -87,10 +122,16 @@ Now that `testuser1` has a digital asset assigned to him, he can transfer it to ```python # create a second testuser -testuser2_priv, testuser2_pub = b.generate_keys() +testuser2_priv, testuser2_pub = crypto.generate_key_pair() + +# retrieve the transaction with condition id +tx_retrieved_id = b.get_owned_ids(testuser1_pub).pop() + {'cid': 0, + 'txid': 'cdb6331f26ecec0ee7e67e4d5dcd63734e7f75bbd1ebe40699fc6d2960ae4cb2'} + # create a transfer transaction -tx_transfer = b.create_transaction(testuser1_pub, testuser2_pub, tx_retrieved['id'], 'TRANSFER') +tx_transfer = b.create_transaction(testuser1_pub, testuser2_pub, tx_retrieved_id, 'TRANSFER') # sign the transaction tx_transfer_signed = b.sign_transaction(tx_transfer, testuser1_priv) @@ -101,14 +142,47 @@ b.write_transaction(tx_transfer_signed) # check if the transaction is already in the bigchain tx_transfer_retrieved = b.get_transaction(tx_transfer_signed['id']) -{ 'id': '1b78c313257540189f27da480152ed8c0b758569cdadd123d9810c057da408c3', - 'signature': '3045022056166de447001db8ef024cfa1eecdba4306f92688920ac24325729d5a5068d47022100fbd495077cb1040c48bd7dc050b2515b296ca215cb5ce3369f094928e31955f6', - 'transaction': { 'current_owner': 'ssQnnjketNYmbU3hwgFMEQsc4JVYAmZyWHnHCtFS8aeA', - 'data': None, - 'input': '6539dded9479c47b3c83385ae569ecaa90bcf387240d1ee2ea3ae0f7986aeddd', - 'new_owner': 'zVzophT73m4Wvf3f8gFYokddkYe3b9PbaMzobiUK7fmP', - 'operation': 'TRANSFER', - 'timestamp': '1455109497.480323'}} +{ + "id": "86ce10d653c69acf422a6d017a4ccd27168cdcdac99a49e4a38fb5e0d280c579", + "transaction": { + "conditions": [ + { + "cid": 0, + "condition": { + "details": { + "bitmask": 32, + "public_key": "7MUjLUFEu12Hk5jb8BZEFgM5JWgSya47SVbqzDqF6ZFQ", + "signature": null, + "type": "fulfillment", + "type_id": 4 + }, + "uri": "cc:1:20:XmUXkarmpe3n17ITJpi-EFy40qvGZ1C9aWphiiRfjOs:98" + }, + "new_owners": [ + "7MUjLUFEu12Hk5jb8BZEFgM5JWgSya47SVbqzDqF6ZFQ" + ] + } + ], + "data": null, + "fulfillments": [ + { + "current_owners": [ + "DTJCqP3sNkZcpoSA8bCtGwZ4ASfRLsMFXZDCmMHzCoeJ" + ], + "fid": 0, + "fulfillment": "cf:1:4:ILkIy_xP7k9clLCaVYtV-8dhuwdebq8yJSjeX1Ln3oUzQPKxMGutQV0EIRYxg81_Z6gdUHQYHkEyTKxwN7zRFjHNAnIdyU1NxqqohhFQSR-qYho-L-uqPRJcAed-SI7xwAI", + "input": { + "cid": 0, + "txid": "cdb6331f26ecec0ee7e67e4d5dcd63734e7f75bbd1ebe40699fc6d2960ae4cb2" + } + } + ], + "operation": "TRANSFER", + "timestamp": "1460450449.289641" + }, + "version": 1 +} + ``` ## Double Spends @@ -119,12 +193,199 @@ If we try to create another transaction with the same input as before, the trans ```python # create another transfer transaction with the same input -tx_transfer2 = b.create_transaction(testuser1_pub, testuser2_pub, tx_retrieved['id'], 'TRANSFER') +tx_transfer2 = b.create_transaction(testuser1_pub, testuser2_pub, tx_retrieved_id, 'TRANSFER') # sign the transaction tx_transfer_signed2 = b.sign_transaction(tx_transfer2, testuser1_priv) # check if the transaction is valid b.validate_transaction(tx_transfer_signed2) -Exception: input `6539dded9479c47b3c83385ae569ecaa90bcf387240d1ee2ea3ae0f7986aeddd` was already spent +DoubleSpend: input `cdb6331f26ecec0ee7e67e4d5dcd63734e7f75bbd1ebe40699fc6d2960ae4cb2` was already spent ``` + +## Crypto Conditions + +### Introduction + + +### Threshold Signatures + +```python +import copy +import json + +from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment +from bigchaindb import util, crypto + +# create some new testusers +thresholduser1_priv, thresholduser1_pub = crypto.generate_key_pair() +thresholduser2_priv, thresholduser2_pub = crypto.generate_key_pair() + +# retrieve the last transaction of testuser2 +tx_retrieved_id = b.get_owned_ids(testuser2_pub).pop() + +# create a base template for a 1-input/2-output transaction +threshold_tx = b.create_transaction(testuser2_pub, [thresholduser1_pub, thresholduser2_pub], tx_retrieved_id, 'TRANSFER') + +# create a Threshold Cryptocondition +threshold_condition = ThresholdSha256Fulfillment(threshold=2) +threshold_condition.add_subfulfillment(Ed25519Fulfillment(public_key=thresholduser1_pub)) +threshold_condition.add_subfulfillment(Ed25519Fulfillment(public_key=thresholduser2_pub)) + +# update the condition in the newly created transaction +threshold_tx['transaction']['conditions'][0]['condition'] = { + 'details': json.loads(threshold_condition.serialize_json()), + 'uri': threshold_condition.condition.serialize_uri() +} + +# conditions have been updated, so hash needs updating +threshold_tx_data = copy.deepcopy(threshold_tx) +for fulfillment in threshold_tx_data['transaction']['fulfillments']: + fulfillment['fulfillment'] = None + +threshold_tx['id'] = crypto.hash_data(util.serialize(threshold_tx_data['transaction'])) + +# sign the transaction +threshold_tx_signed = b.sign_transaction(threshold_tx, testuser2_priv) + +# write the transaction +b.write_transaction(threshold_tx_signed) + +# check if the transaction is already in the bigchain +tx_threshold_retrieved = b.get_transaction(threshold_tx_signed['id']) + +{ + "id": "f0ea4a96afb3b8cafd6336aa3c4b44d1bb0f2b801f61fcb6a44eea4b870ff2e2", + "transaction": { + "conditions": [ + { + "cid": 0, + "condition": { + "details": { + "bitmask": 41, + "subfulfillments": [ + { + "bitmask": 32, + "public_key": "3tuSZ4FitNVWRgK7bGe6pEia7ERmxHmhCxFfFEVbD7g4", + "signature": null, + "type": "fulfillment", + "type_id": 4, + "weight": 1 + }, + { + "bitmask": 32, + "public_key": "8CvrriTsPZULEXTZW2Hnmg7najZsvXzgTi9NKpJaUdHS", + "signature": null, + "type": "fulfillment", + "type_id": 4, + "weight": 1 + } + ], + "threshold": 2, + "type": "fulfillment", + "type_id": 2 + }, + "uri": "cc:1:29:kiQHpdEiRe24L62KzwQgLu7dxCHaLBfEFLr_xzlswT4:208" + }, + "new_owners": [ + "3tuSZ4FitNVWRgK7bGe6pEia7ERmxHmhCxFfFEVbD7g4", + "8CvrriTsPZULEXTZW2Hnmg7najZsvXzgTi9NKpJaUdHS" + ] + } + ], + "data": null, + "fulfillments": [ + { + "current_owners": [ + "7MUjLUFEu12Hk5jb8BZEFgM5JWgSya47SVbqzDqF6ZFQ" + ], + "fid": 0, + "fulfillment": "cf:1:4:IF5lF5Gq5qXt59eyEyaYvhBcuNKrxmdQvWlqYYokX4zrQDSWz8yxBCFaYFKZOLai5ZCoVq28LVoiQ7TL5zkajG-I-BYH2NaKj7CfPBIZHWkMGWfd_UuQWkbhyx07MJ_1Jww", + "input": { + "cid": 0, + "txid": "86ce10d653c69acf422a6d017a4ccd27168cdcdac99a49e4a38fb5e0d280c579" + } + } + ], + "operation": "TRANSFER", + "timestamp": "1460450459.321600" + }, + "version": 1 +} + +``` + +```python +from cryptoconditions.fulfillment import Fulfillment + +thresholduser3_priv, thresholduser3_pub = crypto.generate_key_pair() + +# retrieve the last transaction of thresholduser1_pub +tx_retrieved_id = b.get_owned_ids(thresholduser1_pub).pop() + +# create a base template for a 2-input/1-output transaction +threshold_tx_transfer = b.create_transaction([thresholduser1_pub, thresholduser2_pub], thresholduser3_pub, tx_retrieved_id, 'TRANSFER') + +# parse the threshold cryptocondition +threshold_fulfillment = Fulfillment.from_json(threshold_tx['transaction']['conditions'][0]['condition']['details']) +subfulfillment1 = threshold_fulfillment.subconditions[0]['body'] +subfulfillment2 = threshold_fulfillment.subconditions[1]['body'] + +# get the fulfillment message to sign +threshold_tx_fulfillment_message = util.get_fulfillment_message(threshold_tx_transfer, + threshold_tx_transfer['transaction']['fulfillments'][0]) + +# sign the subconditions +subfulfillment1.sign(util.serialize(threshold_tx_fulfillment_message), crypto.SigningKey(thresholduser1_priv)) +subfulfillment2.sign(util.serialize(threshold_tx_fulfillment_message), crypto.SigningKey(thresholduser2_priv)) + +threshold_tx_transfer['transaction']['fulfillments'][0]['fulfillment'] = threshold_fulfillment.serialize_uri() + +b.write_transaction(threshold_tx_transfer) + +{ + "id": "27d1e780526e172fdafb6cfec24b43878b0f8a2c34e962546ba4932ef7662646", + "transaction": { + "conditions": [ + { + "cid": 0, + "condition": { + "details": { + "bitmask": 32, + "public_key": "4SwVNiYRykGw1ixgKH75k97ipCnmm5QpwNwzQdCKLCzH", + "signature": null, + "type": "fulfillment", + "type_id": 4 + }, + "uri": "cc:1:20:MzgxMS8Zt2XZrSA_dFk1d64nwUz16knOeKkxc5LyIv4:98" + }, + "new_owners": [ + "4SwVNiYRykGw1ixgKH75k97ipCnmm5QpwNwzQdCKLCzH" + ] + } + ], + "data": null, + "fulfillments": [ + { + "current_owners": [ + "3tuSZ4FitNVWRgK7bGe6pEia7ERmxHmhCxFfFEVbD7g4", + "8CvrriTsPZULEXTZW2Hnmg7najZsvXzgTi9NKpJaUdHS" + ], + "fid": 0, + "fulfillment": "cf:1:2:AgIBYwQgKwNKM5oJUhL3lUJ3Xj0dzePTH_1BOxcIry5trRxnNXFANabre0P23pzs3liGozZ-cua3zLZuZIc4UA-2Eb_3oi0zFZKHlL6_PrfxpZFp4Mafsl3Iz1yGVz8s-x5jcbahDwABYwQgaxAYvRMOihIk-M4AZYFB2mlf4XjEqhiOaWpqinOYiXFAuQm7AMeXDs4NCeFI4P6YeL3RqNZqyTr9OsNHZ9JgJLZ2ER1nFpwsLhOt4TJZ01Plon7r7xA2GFKFkw511bRWAQA", + "input": { + "cid": 0, + "txid": "f0ea4a96afb3b8cafd6336aa3c4b44d1bb0f2b801f61fcb6a44eea4b870ff2e2" + } + } + ], + "operation": "TRANSFER", + "timestamp": "1460450469.337543" + }, + "version": 1 +} + +``` + + +### Merkle Trees diff --git a/tests/doc/__init__.py b/tests/doc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/doc/run_doc_python_server_api_examples.py b/tests/doc/run_doc_python_server_api_examples.py new file mode 100644 index 00000000..77a7e272 --- /dev/null +++ b/tests/doc/run_doc_python_server_api_examples.py @@ -0,0 +1,133 @@ +import copy +import json +from time import sleep + +from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment +from cryptoconditions.fulfillment import Fulfillment + +from bigchaindb import Bigchain, util, crypto, exceptions + + +b = Bigchain() + +# create a test user +testuser1_priv, testuser1_pub = crypto.generate_key_pair() + +# define a digital asset data payload +digital_asset_payload = {'msg': 'Hello BigchainDB!'} + +# a create transaction uses the operation `CREATE` and has no inputs +tx = b.create_transaction(b.me, testuser1_pub, None, 'CREATE', payload=digital_asset_payload) + +# all transactions need to be signed by the user creating the transaction +tx_signed = b.sign_transaction(tx, b.me_private) + +# write the transaction to the bigchain +# the transaction will be stored in a backlog where it will be validated, +# included in a block, and written to the bigchain +b.write_transaction(tx_signed) + +sleep(10) + +tx_retrieved = b.get_transaction(tx_signed['id']) + +# create a second testuser +testuser2_priv, testuser2_pub = crypto.generate_key_pair() + +# retrieve the transaction with condition id +tx_retrieved_id = b.get_owned_ids(testuser1_pub).pop() + +# create a transfer transaction +tx_transfer = b.create_transaction(testuser1_pub, testuser2_pub, tx_retrieved_id, 'TRANSFER') + +# sign the transaction +tx_transfer_signed = b.sign_transaction(tx_transfer, testuser1_priv) + +# write the transaction +b.write_transaction(tx_transfer_signed) + +sleep(10) + +# check if the transaction is already in the bigchain +tx_transfer_retrieved = b.get_transaction(tx_transfer_signed['id']) + +# create another transfer transaction with the same input +tx_transfer2 = b.create_transaction(testuser1_pub, testuser2_pub, tx_retrieved_id, 'TRANSFER') + +# sign the transaction +tx_transfer_signed2 = b.sign_transaction(tx_transfer2, testuser1_priv) + +# check if the transaction is valid +try: + b.validate_transaction(tx_transfer_signed2) +except exceptions.DoubleSpend as e: + print(e) + +# create some new testusers +thresholduser1_priv, thresholduser1_pub = crypto.generate_key_pair() +thresholduser2_priv, thresholduser2_pub = crypto.generate_key_pair() + +# retrieve the last transaction of testuser2 +tx_retrieved_id = b.get_owned_ids(testuser2_pub).pop() + +# create a base template for a 1-input/2-output transaction +threshold_tx = b.create_transaction(testuser2_pub, [thresholduser1_pub, thresholduser2_pub], tx_retrieved_id, 'TRANSFER') + +# create a Threshold Cryptocondition +threshold_condition = ThresholdSha256Fulfillment(threshold=2) +threshold_condition.add_subfulfillment(Ed25519Fulfillment(public_key=thresholduser1_pub)) +threshold_condition.add_subfulfillment(Ed25519Fulfillment(public_key=thresholduser2_pub)) + +# update the condition in the newly created transaction +threshold_tx['transaction']['conditions'][0]['condition'] = { + 'details': json.loads(threshold_condition.serialize_json()), + 'uri': threshold_condition.condition.serialize_uri() +} + +# conditions have been updated, so hash needs updating +threshold_tx_data = copy.deepcopy(threshold_tx) +for fulfillment in threshold_tx_data['transaction']['fulfillments']: + fulfillment['fulfillment'] = None + +threshold_tx['id'] = crypto.hash_data(util.serialize(threshold_tx_data['transaction'])) + +# sign the transaction +threshold_tx_signed = b.sign_transaction(threshold_tx, testuser2_priv) + +# write the transaction +b.write_transaction(threshold_tx_signed) + +sleep(10) + +# check if the transaction is already in the bigchain +tx_threshold_retrieved = b.get_transaction(threshold_tx_signed['id']) + +thresholduser3_priv, thresholduser3_pub = crypto.generate_key_pair() + +# retrieve the last transaction of thresholduser1_pub +tx_retrieved_id = b.get_owned_ids(thresholduser1_pub).pop() + +# create a base template for a 2-input/1-output transaction +threshold_tx_transfer = b.create_transaction([thresholduser1_pub, thresholduser2_pub], thresholduser3_pub, tx_retrieved_id, 'TRANSFER') + +# parse the threshold cryptocondition +threshold_fulfillment = Fulfillment.from_json(threshold_tx['transaction']['conditions'][0]['condition']['details']) +subfulfillment1 = threshold_fulfillment.subconditions[0]['body'] +subfulfillment2 = threshold_fulfillment.subconditions[1]['body'] + +# get the fulfillment message to sign +threshold_tx_fulfillment_message = util.get_fulfillment_message(threshold_tx_transfer, + threshold_tx_transfer['transaction']['fulfillments'][0]) + +# sign the subconditions +subfulfillment1.sign(util.serialize(threshold_tx_fulfillment_message), crypto.SigningKey(thresholduser1_priv)) +subfulfillment2.sign(util.serialize(threshold_tx_fulfillment_message), crypto.SigningKey(thresholduser2_priv)) + +threshold_tx_transfer['transaction']['fulfillments'][0]['fulfillment'] = threshold_fulfillment.serialize_uri() + +b.verify_signature(threshold_tx_transfer) + +b.validate_transaction(threshold_tx_transfer) + +b.write_transaction(threshold_tx_transfer) +