diff --git a/docs/source/python-server-api-examples.md b/docs/source/python-server-api-examples.md index 72dc4329..6fd74537 100644 --- a/docs/source/python-server-api-examples.md +++ b/docs/source/python-server-api-examples.md @@ -70,51 +70,51 @@ After a couple of seconds, we can check if the transactions was included in the tx_retrieved = b.get_transaction(tx_signed['id']) { - "id": "cdb6331f26ecec0ee7e67e4d5dcd63734e7f75bbd1ebe40699fc6d2960ae4cb2", - "transaction": { - "conditions": [ + "id":"933cd83a419d2735822a2154c84176a2f419cbd449a74b94e592ab807af23861", + "transaction":{ + "conditions":[ { - "cid": 0, - "condition": { - "details": { - "bitmask": 32, - "public_key": "DTJCqP3sNkZcpoSA8bCtGwZ4ASfRLsMFXZDCmMHzCoeJ", - "signature": null, - "type": "fulfillment", - "type_id": 4 + "cid":0, + "condition":{ + "details":{ + "bitmask":32, + "public_key":"BwuhqQX8FPsmqYiRV2CSZYWWsSWgSSQQFHjqxKEuqkPs", + "signature":null, + "type":"fulfillment", + "type_id":4 }, - "uri": "cc:1:20:uQjL_E_uT1yUsJpVi1X7x2G7B15urzIlKN5fUufehTM:98" + "uri":"cc:4:20:oqXTWvR3afHHX8OaOO84kZxS6nH4GEBXD4Vw8Mc5iBo:96" }, - "new_owners": [ - "DTJCqP3sNkZcpoSA8bCtGwZ4ASfRLsMFXZDCmMHzCoeJ" + "new_owners":[ + "BwuhqQX8FPsmqYiRV2CSZYWWsSWgSSQQFHjqxKEuqkPs" ] } ], - "data": { - "hash": "872fa6e6f46246cd44afdb2ee9cfae0e72885fb0910e2bcf9a5a2a4eadb417b8", - "payload": { - "msg": "Hello BigchainDB!" + "data":{ + "hash":"872fa6e6f46246cd44afdb2ee9cfae0e72885fb0910e2bcf9a5a2a4eadb417b8", + "payload":{ + "msg":"Hello BigchainDB!" } }, - "fulfillments": [ + "fulfillments":[ { - "current_owners": [ + "current_owners":[ "3LQ5dTiddXymDhNzETB1rEkp4mA7fEV1Qeiu5ghHiJm9" ], - "fid": 0, - "fulfillment": "cf:1:4:ICKvgXHM8K2jNlKRfkwz3cCvH0OiF5A_-riWsQWXffOMQCyqbFgSDfKTaKRQHypHr5z5jsXzCQ4dKgYkmUo55CMxYs3TT2OxGiV0bZ7Tzn1lcLhpyutGZWm8xIyJKJmmSQQ", - "input": null + "fid":0, + "fulfillment":"cf:4:Iq-BcczwraM2UpF-TDPdwK8fQ6IXkD_6uJaxBZd984yxCGX7Csx-S2FBVe8LVyW2sAtmjsOSV0oiw9-s_9qSJB0dDUl_x8YQk5yxNdQyNVWVM1mWSGQL68gMngdmFG8O", + "input":null } ], - "operation": "CREATE", - "timestamp": "1460450439.267737" + "operation":"CREATE", + "timestamp":"1460981667.449279" }, - "version": 1 + "version":1 } ``` -The new owner of the digital asset is now `DTJCqP3sNkZcpoSA8bCtGwZ4ASfRLsMFXZDCmMHzCoeJ`, which is the public key of `testuser1`. +The new owner of the digital asset is now `BwuhqQX8FPsmqYiRV2CSZYWWsSWgSSQQFHjqxKEuqkPs`, which is the public key of `testuser1`. Note that the current owner with public key `3LQ5dTiddXymDhNzETB1rEkp4mA7fEV1Qeiu5ghHiJm9` refers to one of the federation nodes that actually created the asset and assigned it to `testuser1`. @@ -130,9 +130,11 @@ 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'} +{ + "cid":0, + "txid":"933cd83a419d2735822a2154c84176a2f419cbd449a74b94e592ab807af23861" +} # create a transfer transaction tx_transfer = b.create_transaction(testuser1_pub, testuser2_pub, tx_retrieved_id, 'TRANSFER') @@ -147,44 +149,44 @@ b.write_transaction(tx_transfer_signed) tx_transfer_retrieved = b.get_transaction(tx_transfer_signed['id']) { - "id": "86ce10d653c69acf422a6d017a4ccd27168cdcdac99a49e4a38fb5e0d280c579", - "transaction": { - "conditions": [ + "id":"aa11365317cb89bfdae2375bae76d6b8232008f8672507080e3766ca06976dcd", + "transaction":{ + "conditions":[ { - "cid": 0, - "condition": { - "details": { - "bitmask": 32, - "public_key": "7MUjLUFEu12Hk5jb8BZEFgM5JWgSya47SVbqzDqF6ZFQ", - "signature": null, - "type": "fulfillment", - "type_id": 4 + "cid":0, + "condition":{ + "details":{ + "bitmask":32, + "public_key":"qv8DvdNG5nZHWCP5aPSqgqxAvaPJpQj19abRvFCntor", + "signature":null, + "type":"fulfillment", + "type_id":4 }, - "uri": "cc:1:20:XmUXkarmpe3n17ITJpi-EFy40qvGZ1C9aWphiiRfjOs:98" + "uri":"cc:4:20:DIfyalZvV_9ukoO01mxmK3nxsfAWSKYYF33XDYkbY4E:96" }, - "new_owners": [ - "7MUjLUFEu12Hk5jb8BZEFgM5JWgSya47SVbqzDqF6ZFQ" + "new_owners":[ + "qv8DvdNG5nZHWCP5aPSqgqxAvaPJpQj19abRvFCntor" ] } ], - "data": null, - "fulfillments": [ + "data":null, + "fulfillments":[ { - "current_owners": [ - "DTJCqP3sNkZcpoSA8bCtGwZ4ASfRLsMFXZDCmMHzCoeJ" + "current_owners":[ + "BwuhqQX8FPsmqYiRV2CSZYWWsSWgSSQQFHjqxKEuqkPs" ], - "fid": 0, - "fulfillment": "cf:1:4:ILkIy_xP7k9clLCaVYtV-8dhuwdebq8yJSjeX1Ln3oUzQPKxMGutQV0EIRYxg81_Z6gdUHQYHkEyTKxwN7zRFjHNAnIdyU1NxqqohhFQSR-qYho-L-uqPRJcAed-SI7xwAI", - "input": { - "cid": 0, - "txid": "cdb6331f26ecec0ee7e67e4d5dcd63734e7f75bbd1ebe40699fc6d2960ae4cb2" + "fid":0, + "fulfillment":"cf:4:oqXTWvR3afHHX8OaOO84kZxS6nH4GEBXD4Vw8Mc5iBqzkVR6cFJhRvMGKa-Lc81sdYWVu0ZSMPGht-P7s6FZLkRXDqrwwInabLhjx14eABY34oHb6IyWcB-dyQnlVNEI", + "input":{ + "cid":0, + "txid":"933cd83a419d2735822a2154c84176a2f419cbd449a74b94e592ab807af23861" } } ], - "operation": "TRANSFER", - "timestamp": "1460450449.289641" + "operation":"TRANSFER", + "timestamp":"1460981677.472037" }, - "version": 1 + "version":1 } ``` @@ -204,7 +206,148 @@ tx_transfer_signed2 = b.sign_transaction(tx_transfer2, testuser1_priv) # check if the transaction is valid b.validate_transaction(tx_transfer_signed2) -DoubleSpend: input `cdb6331f26ecec0ee7e67e4d5dcd63734e7f75bbd1ebe40699fc6d2960ae4cb2` was already spent +DoubleSpend: input `{'cid': 0, 'txid': '933cd83a419d2735822a2154c84176a2f419cbd449a74b94e592ab807af23861'}` was already spent +``` + +## Multiple Owners + +When creating a transaction to a group of people, one can simply provide an array of `new_owners`: + +```python +# Create a new asset and assign it to multiple owners +tx_multisig = b.create_transaction(b.me, [testuser1_pub, testuser2_pub], None, 'CREATE') + +# Have the federation sign the transaction +tx_multisig_signed = b.sign_transaction(tx_multisig, b.me_private) +b.write_transaction(tx_multisig_signed) + +# wait a few seconds for the asset to appear on the blockchain + +# retrieve the transaction +tx_multisig_retrieved = b.get_transaction(tx_multisig_signed['id']) + +{ + "id":"a9a6e5c74ea02b8885c83125f1b74a2ba8ca42236ec5e1c358aa1053ec721ccb", + "transaction":{ + "conditions":[ + { + "cid":0, + "condition":{ + "details":{ + "bitmask":41, + "subfulfillments":[ + { + "bitmask":32, + "public_key":"BwuhqQX8FPsmqYiRV2CSZYWWsSWgSSQQFHjqxKEuqkPs", + "signature":null, + "type":"fulfillment", + "type_id":4, + "weight":1 + }, + { + "bitmask":32, + "public_key":"qv8DvdNG5nZHWCP5aPSqgqxAvaPJpQj19abRvFCntor", + "signature":null, + "type":"fulfillment", + "type_id":4, + "weight":1 + } + ], + "threshold":2, + "type":"fulfillment", + "type_id":2 + }, + "uri":"cc:2:29:DpflJzUSlnTUBx8lD8QUolOA-M9nQnrGwvWSk7f3REc:206" + }, + "new_owners":[ + "BwuhqQX8FPsmqYiRV2CSZYWWsSWgSSQQFHjqxKEuqkPs", + "qv8DvdNG5nZHWCP5aPSqgqxAvaPJpQj19abRvFCntor" + ] + } + ], + "data":null, + "fulfillments":[ + { + "current_owners":[ + "3LQ5dTiddXymDhNzETB1rEkp4mA7fEV1Qeiu5ghHiJm9" + ], + "fid":0, + "fulfillment":"cf:4:Iq-BcczwraM2UpF-TDPdwK8fQ6IXkD_6uJaxBZd984z5qdHRz9Jag68dkOyZS5_YoTR_0WpwiUnBGoNgwjwEuIn5JNm7Kksi0nUnHsWssyXISkmqRnHH-30HQhKjznIH", + "input":null + } + ], + "operation":"CREATE", + "timestamp":"1460981687.501433" + }, + "version":1 +} +``` + +The asset can be transfered as soon as each of the `new_owners` sign the transaction. + +To do so, simply provide an array of the necessary private keys to the signing routine: + +```python +# create a third testuser +testuser3_priv, testuser3_pub = crypto.generate_key_pair() + +# retrieve the multisig transaction +tx_multisig_retrieved_id = b.get_owned_ids(testuser2_pub).pop() + +# transfer the asset from the 2 owners to the third testuser +tx_multisig_transfer = b.create_transaction([testuser1_pub, testuser2_pub], testuser3_pub, tx_multisig_retrieved_id, 'TRANSFER') + +# sign with both private keys +tx_multisig_transfer_signed = b.sign_transaction(tx_multisig_transfer, [testuser1_priv, testuser2_priv]) + +# write to bigchain +b.write_transaction(tx_multisig_transfer_signed) + +# check if the transaction exists in the bigchain +tx_multisig_retrieved = b.get_transaction(tx_multisig_transfer_signed['id']) + +{ + "assignee":"3LQ5dTiddXymDhNzETB1rEkp4mA7fEV1Qeiu5ghHiJm9", + "id":"e689e23f774e7c562eeb310c7c712b34fb6210bea5deb9175e48b68810029150", + "transaction":{ + "conditions":[ + { + "cid":0, + "condition":{ + "details":{ + "bitmask":32, + "public_key":"8YN9fALMj9CkeCcmTiM2kxwurpkMzHg9RkwSLJKMasvG", + "signature":null, + "type":"fulfillment", + "type_id":4 + }, + "uri":"cc:4:20:cAq6JQJXtwlxURqrksiyqLThB9zh08ZxSPLTDSaReYE:96" + }, + "new_owners":[ + "8YN9fALMj9CkeCcmTiM2kxwurpkMzHg9RkwSLJKMasvG" + ] + } + ], + "data":null, + "fulfillments":[ + { + "current_owners":[ + "BwuhqQX8FPsmqYiRV2CSZYWWsSWgSSQQFHjqxKEuqkPs", + "qv8DvdNG5nZHWCP5aPSqgqxAvaPJpQj19abRvFCntor" + ], + "fid":0, + "fulfillment":"cf:4:oqXTWvR3afHHX8OaOO84kZxS6nH4GEBXD4Vw8Mc5iBrcuiGDNVgpH9SwiuNeYZ-nugSTbxykH8W1eH5UJiunmnBSlKnJb8_QYOQsMAXl3MyLq2pWAyI45ZSG1rr2CksI", + "input":{ + "cid":0, + "txid":"aa11365317cb89bfdae2375bae76d6b8232008f8672507080e3766ca06976dcd" + } + } + ], + "operation":"TRANSFER", + "timestamp":"1460981697.526878" + }, + "version":1 +} ``` ## Crypto-Conditions @@ -238,29 +381,31 @@ The basic workflow for creating a more complex cryptocondition is the following: 2. Set up the threshold condition using the [cryptocondition library](https://github.com/bigchaindb/cryptoconditions) 3. Update the condition and hash in the transaction template -We'll illustrate this by an example: +We'll illustrate this by a threshold condition where 2 out of 3 `new_owners` need to sign the transaction: ```python import copy import json -from cryptoconditions import Ed25519Fulfillment, ThresholdSha256Fulfillment +import cryptoconditions as cc 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() +thresholduser3_priv, thresholduser3_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') +threshold_tx = b.create_transaction(testuser2_pub, [thresholduser1_pub, thresholduser2_pub, thresholduser3_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)) +threshold_condition = cc.ThresholdSha256Fulfillment(threshold=2) +threshold_condition.add_subfulfillment(cc.Ed25519Fulfillment(public_key=thresholduser1_pub)) +threshold_condition.add_subfulfillment(cc.Ed25519Fulfillment(public_key=thresholduser2_pub)) +threshold_condition.add_subfulfillment(cc.Ed25519Fulfillment(public_key=thresholduser3_pub)) # update the condition in the newly created transaction threshold_tx['transaction']['conditions'][0]['condition'] = { @@ -281,64 +426,72 @@ b.write_transaction(threshold_tx_signed) tx_threshold_retrieved = b.get_transaction(threshold_tx_signed['id']) { - "id": "f0ea4a96afb3b8cafd6336aa3c4b44d1bb0f2b801f61fcb6a44eea4b870ff2e2", - "transaction": { - "conditions": [ + "id":"0057d29ff735d91505decf5e7195ea8da675b01676165abf23ea774bbb469383", + "transaction":{ + "conditions":[ { - "cid": 0, - "condition": { - "details": { - "bitmask": 41, - "subfulfillments": [ + "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":"8NaGq26YMcEvj8Sc5MnqspKzFTQd1eZBAuuPDw4ERHpz", + "signature":null, + "type":"fulfillment", + "type_id":4, + "weight":1 }, { - "bitmask": 32, - "public_key": "8CvrriTsPZULEXTZW2Hnmg7najZsvXzgTi9NKpJaUdHS", - "signature": null, - "type": "fulfillment", - "type_id": 4, - "weight": 1 + "bitmask":32, + "public_key":"ALE9Agojob28D1fHWCxFXJwpqrYPkcsUs26YksBVj27z", + "signature":null, + "type":"fulfillment", + "type_id":4, + "weight":1 + }, + { + "bitmask":32, + "public_key":"Cx4jWSGci7fw6z5QyeApCijbwnMpyuhp4C1kzuFc3XrM", + "signature":null, + "type":"fulfillment", + "type_id":4, + "weight":1 } ], - "threshold": 2, - "type": "fulfillment", - "type_id": 2 + "threshold":2, + "type":"fulfillment", + "type_id":2 }, - "uri": "cc:1:29:kiQHpdEiRe24L62KzwQgLu7dxCHaLBfEFLr_xzlswT4:208" + "uri":"cc:2:29:FoElId4TE5TU2loonT7sayXhxwcmaJVoCeIduh56Dxw:246" }, - "new_owners": [ - "3tuSZ4FitNVWRgK7bGe6pEia7ERmxHmhCxFfFEVbD7g4", - "8CvrriTsPZULEXTZW2Hnmg7najZsvXzgTi9NKpJaUdHS" + "new_owners":[ + "8NaGq26YMcEvj8Sc5MnqspKzFTQd1eZBAuuPDw4ERHpz", + "ALE9Agojob28D1fHWCxFXJwpqrYPkcsUs26YksBVj27z", + "Cx4jWSGci7fw6z5QyeApCijbwnMpyuhp4C1kzuFc3XrM" ] } ], - "data": null, - "fulfillments": [ + "data":null, + "fulfillments":[ { - "current_owners": [ - "7MUjLUFEu12Hk5jb8BZEFgM5JWgSya47SVbqzDqF6ZFQ" + "current_owners":[ + "qv8DvdNG5nZHWCP5aPSqgqxAvaPJpQj19abRvFCntor" ], - "fid": 0, - "fulfillment": "cf:1:4:IF5lF5Gq5qXt59eyEyaYvhBcuNKrxmdQvWlqYYokX4zrQDSWz8yxBCFaYFKZOLai5ZCoVq28LVoiQ7TL5zkajG-I-BYH2NaKj7CfPBIZHWkMGWfd_UuQWkbhyx07MJ_1Jww", - "input": { - "cid": 0, - "txid": "86ce10d653c69acf422a6d017a4ccd27168cdcdac99a49e4a38fb5e0d280c579" + "fid":0, + "fulfillment":"cf:4:DIfyalZvV_9ukoO01mxmK3nxsfAWSKYYF33XDYkbY4EbD7-_neXJJEe_tVTDc1_EqldlP_ulysFMprcW3VG4gzLzCMMpxA8kCr_pvywSFIEVYJHnI1csMvPivvBGHvkD", + "input":{ + "cid":0, + "txid":"aa11365317cb89bfdae2375bae76d6b8232008f8672507080e3766ca06976dcd" } } ], - "operation": "TRANSFER", - "timestamp": "1460450459.321600" + "operation":"TRANSFER", + "timestamp":"1460981707.559401" }, - "version": 1 + "version":1 } - ``` The transaction can now be transfered by fulfilling the threshold condition. @@ -351,29 +504,39 @@ The fulfillment involves: ```python -from cryptoconditions.fulfillment import Fulfillment -thresholduser3_priv, thresholduser3_pub = crypto.generate_key_pair() +thresholduser4_priv, thresholduser4_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') +threshold_tx_transfer = b.create_transaction([thresholduser1_pub, thresholduser2_pub, thresholduser3_pub], thresholduser4_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'] +threshold_fulfillment = cc.Fulfillment.from_json(threshold_tx['transaction']['conditions'][0]['condition']['details']) + +subfulfillment1 = threshold_fulfillment.get_subcondition_from_vk(thresholduser1_pub)[0] +subfulfillment2 = threshold_fulfillment.get_subcondition_from_vk(thresholduser2_pub)[0] +subfulfillment3 = threshold_fulfillment.get_subcondition_from_vk(thresholduser3_pub)[0] + # get the fulfillment message to sign threshold_tx_fulfillment_message = util.get_fulfillment_message(threshold_tx_transfer, threshold_tx_transfer['transaction']['fulfillments'][0], serialized=True) -# sign the subconditions +# clear the subconditions of the threshold fulfillment, they will be added again after signing +threshold_fulfillment.subconditions = [] + +# sign and add the subconditions until threshold of 2 is reached subfulfillment1.sign(threshold_tx_fulfillment_message, crypto.SigningKey(thresholduser1_priv)) +threshold_fulfillment.add_subfulfillment(subfulfillment1) subfulfillment2.sign(threshold_tx_fulfillment_message, crypto.SigningKey(thresholduser2_priv)) +threshold_fulfillment.add_subfulfillment(subfulfillment2) + +# Add remaining (unfulfilled) fulfillment as a condition +threshold_fulfillment.add_subcondition(subfulfillment3.condition) # update the fulfillment threshold_tx_transfer['transaction']['fulfillments'][0]['fulfillment'] = threshold_fulfillment.serialize_uri() @@ -386,45 +549,48 @@ assert b.validate_transaction(threshold_tx_transfer) == True b.write_transaction(threshold_tx_transfer) { - "id": "27d1e780526e172fdafb6cfec24b43878b0f8a2c34e962546ba4932ef7662646", - "transaction": { - "conditions": [ + "assignee":"3LQ5dTiddXymDhNzETB1rEkp4mA7fEV1Qeiu5ghHiJm9", + "id":"a45b2340c59df7422a5788b3c462dee708a18cdf09d1a10bd26be3f31af4b8d7", + "transaction":{ + "conditions":[ { - "cid": 0, - "condition": { - "details": { - "bitmask": 32, - "public_key": "4SwVNiYRykGw1ixgKH75k97ipCnmm5QpwNwzQdCKLCzH", - "signature": null, - "type": "fulfillment", - "type_id": 4 + "cid":0, + "condition":{ + "details":{ + "bitmask":32, + "public_key":"ED2pyPfsbNRTHkdMnaFkAwCSpZWRmbaM1h8fYzgRRMmc", + "signature":null, + "type":"fulfillment", + "type_id":4 }, - "uri": "cc:1:20:MzgxMS8Zt2XZrSA_dFk1d64nwUz16knOeKkxc5LyIv4:98" + "uri":"cc:4:20:xDz3NhRG-3eVzIB9sgnd99LKjOyDF-KlxWuf1TgNT0s:96" }, - "new_owners": [ - "4SwVNiYRykGw1ixgKH75k97ipCnmm5QpwNwzQdCKLCzH" + "new_owners":[ + "ED2pyPfsbNRTHkdMnaFkAwCSpZWRmbaM1h8fYzgRRMmc" ] } ], - "data": null, - "fulfillments": [ + "data":null, + "fulfillments":[ { - "current_owners": [ - "3tuSZ4FitNVWRgK7bGe6pEia7ERmxHmhCxFfFEVbD7g4", - "8CvrriTsPZULEXTZW2Hnmg7najZsvXzgTi9NKpJaUdHS" + "current_owners":[ + "8NaGq26YMcEvj8Sc5MnqspKzFTQd1eZBAuuPDw4ERHpz", + "ALE9Agojob28D1fHWCxFXJwpqrYPkcsUs26YksBVj27z", + "Cx4jWSGci7fw6z5QyeApCijbwnMpyuhp4C1kzuFc3XrM" ], - "fid": 0, - "fulfillment": "cf:1:2:AgIBYwQgKwNKM5oJUhL3lUJ3Xj0dzePTH_1BOxcIry5trRxnNXFANabre0P23pzs3liGozZ-cua3zLZuZIc4UA-2Eb_3oi0zFZKHlL6_PrfxpZFp4Mafsl3Iz1yGVz8s-x5jcbahDwABYwQgaxAYvRMOihIk-M4AZYFB2mlf4XjEqhiOaWpqinOYiXFAuQm7AMeXDs4NCeFI4P6YeL3RqNZqyTr9OsNHZ9JgJLZ2ER1nFpwsLhOt4TJZ01Plon7r7xA2GFKFkw511bRWAQA", - "input": { - "cid": 0, - "txid": "f0ea4a96afb3b8cafd6336aa3c4b44d1bb0f2b801f61fcb6a44eea4b870ff2e2" + "fid":0, + "fulfillment":"cf:2:AQIBAwEBACcABAEgILGLuLLaNHo-KE59tkrpYmlVeucu16Eg9TcSuBqnMVwmAWABAWMABGBtiKCT8NBtSdnxJNdGYkyWqoRy2qOeNZ5UdUvpALcBD4vGRaohuVP9pQYNHpAA5GjTNNQT9CVMB67D8QL_DJsRU8ICSIVIG2P8pRqX6oia-304Xqq67wY-wLh_3IKlUg0AAQFjAARgiqYTeWkT6-jRMriCK4i8ceE2TwPys0JXgIrbw4kbwElVNnc7Aqw5c-Ts8-ymLp3d9_xTIb3-mPaV4JjhBqcobKuq2msJAjrxZOEeuYuAyC0tpduwTajOyp_Kmwzhdm8PAA", + "input":{ + "cid":0, + "txid":"0057d29ff735d91505decf5e7195ea8da675b01676165abf23ea774bbb469383" } } ], - "operation": "TRANSFER", - "timestamp": "1460450469.337543" + "operation":"TRANSFER", + "timestamp":"1460981717.579700" }, - "version": 1 + "version":1 } + ``` diff --git a/setup.py b/setup.py index 5b1cec03..a9387f56 100644 --- a/setup.py +++ b/setup.py @@ -71,7 +71,7 @@ setup( 'rethinkdb==2.2.0.post4', 'pysha3==0.3', 'pytz==2015.7', - 'cryptoconditions==0.2.0', + 'cryptoconditions==0.2.1', 'statsd==3.2.1', 'python-rapidjson==0.0.6', 'logstats==0.2.1', diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index 1e2c3a85..56b56d69 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -1310,7 +1310,7 @@ class TestCryptoconditions(object): first_tx_fulfillment.sign(first_tx_fulfillment_message, crypto.SigningKey(user_sk)) first_tx['transaction']['fulfillments'][0]['fulfillment'] = first_tx_fulfillment.serialize_uri() - assert b.validate_transaction(first_tx) + assert b.validate_transaction(first_tx) == first_tx assert b.is_valid_transaction(first_tx) == first_tx b.write_transaction(first_tx) @@ -1329,20 +1329,22 @@ class TestCryptoconditions(object): next_tx_fulfillment.sign(next_tx_fulfillment_message, crypto.SigningKey(other_sk)) next_tx['transaction']['fulfillments'][0]['fulfillment'] = next_tx_fulfillment.serialize_uri() - assert b.validate_transaction(next_tx) + assert b.validate_transaction(next_tx) == next_tx assert b.is_valid_transaction(next_tx) == next_tx @pytest.mark.usefixtures('inputs') def test_override_condition_and_fulfillment_transfer_threshold(self, b, user_vk, user_sk): other1_sk, other1_vk = crypto.generate_key_pair() other2_sk, other2_vk = crypto.generate_key_pair() + other3_sk, other3_vk = crypto.generate_key_pair() first_input_tx = b.get_owned_ids(user_vk).pop() - first_tx = b.create_transaction(user_vk, [other1_vk, other2_vk], first_input_tx, 'TRANSFER') + first_tx = b.create_transaction(user_vk, [other1_vk, other2_vk, other3_vk], first_input_tx, 'TRANSFER') first_tx_condition = cc.ThresholdSha256Fulfillment(threshold=2) first_tx_condition.add_subfulfillment(cc.Ed25519Fulfillment(public_key=other1_vk)) first_tx_condition.add_subfulfillment(cc.Ed25519Fulfillment(public_key=other2_vk)) + first_tx_condition.add_subfulfillment(cc.Ed25519Fulfillment(public_key=other3_vk)) first_tx['transaction']['conditions'][0]['condition'] = { 'details': json.loads(first_tx_condition.serialize_json()), @@ -1353,7 +1355,7 @@ class TestCryptoconditions(object): first_tx_signed = b.sign_transaction(first_tx, user_sk) - assert b.validate_transaction(first_tx_signed) + assert b.validate_transaction(first_tx_signed) == first_tx_signed assert b.is_valid_transaction(first_tx_signed) == first_tx_signed b.write_transaction(first_tx_signed) @@ -1364,7 +1366,7 @@ class TestCryptoconditions(object): next_input_tx = b.get_owned_ids(other1_vk).pop() # create another transaction with the same input - next_tx = b.create_transaction([other1_vk, other2_vk], user_vk, next_input_tx, 'TRANSFER') + next_tx = b.create_transaction([other1_vk, other2_vk, other3_vk], user_vk, next_input_tx, 'TRANSFER') next_tx_fulfillment = next_tx['transaction']['fulfillments'][0] next_tx_fulfillment_message = util.get_fulfillment_message(next_tx, next_tx_fulfillment, serialized=True) @@ -1375,9 +1377,70 @@ class TestCryptoconditions(object): next_tx_subfulfillment2 = cc.Ed25519Fulfillment(public_key=other2_vk) next_tx_subfulfillment2.sign(next_tx_fulfillment_message, crypto.SigningKey(other2_sk)) next_tx_fulfillment.add_subfulfillment(next_tx_subfulfillment2) + # need to add remaining (unsigned) fulfillment as a condition + next_tx_subfulfillment3 = cc.Ed25519Fulfillment(public_key=other3_vk) + next_tx_fulfillment.add_subcondition(next_tx_subfulfillment3.condition) next_tx['transaction']['fulfillments'][0]['fulfillment'] = next_tx_fulfillment.serialize_uri() - assert b.validate_transaction(next_tx) + assert b.validate_transaction(next_tx) == next_tx + assert b.is_valid_transaction(next_tx) == next_tx + + @pytest.mark.usefixtures('inputs') + def test_override_condition_and_fulfillment_transfer_threshold_from_json(self, b, user_vk, user_sk): + other1_sk, other1_vk = crypto.generate_key_pair() + other2_sk, other2_vk = crypto.generate_key_pair() + other3_sk, other3_vk = crypto.generate_key_pair() + + first_input_tx = b.get_owned_ids(user_vk).pop() + first_tx = b.create_transaction(user_vk, [other1_vk, other2_vk, other3_vk], first_input_tx, 'TRANSFER') + + first_tx_condition = cc.ThresholdSha256Fulfillment(threshold=2) + first_tx_condition.add_subfulfillment(cc.Ed25519Fulfillment(public_key=other1_vk)) + first_tx_condition.add_subfulfillment(cc.Ed25519Fulfillment(public_key=other2_vk)) + first_tx_condition.add_subfulfillment(cc.Ed25519Fulfillment(public_key=other3_vk)) + + first_tx['transaction']['conditions'][0]['condition'] = { + 'details': json.loads(first_tx_condition.serialize_json()), + 'uri': first_tx_condition.condition.serialize_uri() + } + # conditions have been updated, so hash needs updating + first_tx['id'] = util.get_hash_data(first_tx) + + first_tx_signed = b.sign_transaction(first_tx, user_sk) + + assert b.validate_transaction(first_tx_signed) == first_tx_signed + assert b.is_valid_transaction(first_tx_signed) == first_tx_signed + + b.write_transaction(first_tx_signed) + + # create and write block to bigchain + block = b.create_block([first_tx]) + b.write_block(block, durability='hard') + + next_input_tx = b.get_owned_ids(other1_vk).pop() + # create another transaction with the same input + next_tx = b.create_transaction([other1_vk, other2_vk, other3_vk], user_vk, next_input_tx, 'TRANSFER') + + next_tx_fulfillment = next_tx['transaction']['fulfillments'][0] + next_tx_fulfillment_message = util.get_fulfillment_message(next_tx, next_tx_fulfillment, serialized=True) + + # parse the threshold cryptocondition + next_tx_fulfillment = cc.Fulfillment.from_json(first_tx['transaction']['conditions'][0]['condition']['details']) + + subfulfillment1 = next_tx_fulfillment.get_subcondition_from_vk(other1_vk)[0] + subfulfillment2 = next_tx_fulfillment.get_subcondition_from_vk(other2_vk)[0] + subfulfillment3 = next_tx_fulfillment.get_subcondition_from_vk(other3_vk)[0] + + next_tx_fulfillment.subconditions = [] + # sign the subconditions until threshold of 2 is reached + subfulfillment1.sign(next_tx_fulfillment_message, crypto.SigningKey(other1_sk)) + next_tx_fulfillment.add_subfulfillment(subfulfillment1) + subfulfillment2.sign(next_tx_fulfillment_message, crypto.SigningKey(other2_sk)) + next_tx_fulfillment.add_subfulfillment(subfulfillment2) + next_tx_fulfillment.add_subcondition(subfulfillment3.condition) + + next_tx['transaction']['fulfillments'][0]['fulfillment'] = next_tx_fulfillment.serialize_uri() + assert b.validate_transaction(next_tx) == next_tx assert b.is_valid_transaction(next_tx) == next_tx @pytest.mark.usefixtures('inputs') @@ -1401,7 +1464,7 @@ class TestCryptoconditions(object): first_tx_signed = b.sign_transaction(first_tx, user_sk) - assert b.validate_transaction(first_tx_signed) + assert b.validate_transaction(first_tx_signed) == first_tx_signed assert b.is_valid_transaction(first_tx_signed) == first_tx_signed b.write_transaction(first_tx_signed) diff --git a/tests/doc/run_doc_python_server_api_examples.py b/tests/doc/run_doc_python_server_api_examples.py index 9d46e938..79552911 100644 --- a/tests/doc/run_doc_python_server_api_examples.py +++ b/tests/doc/run_doc_python_server_api_examples.py @@ -31,11 +31,18 @@ sleep(10) tx_retrieved = b.get_transaction(tx_signed['id']) +print(json.dumps(tx_retrieved, sort_keys=True, indent=4, separators=(',', ':'))) + +print(testuser1_pub) +print(b.me) + +print(tx_retrieved['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() +print(json.dumps(tx_retrieved_id, sort_keys=True, indent=4, separators=(',', ':'))) # create a transfer transaction tx_transfer = b.create_transaction(testuser1_pub, testuser2_pub, tx_retrieved_id, 'TRANSFER') @@ -50,6 +57,8 @@ sleep(10) # check if the transaction is already in the bigchain tx_transfer_retrieved = b.get_transaction(tx_transfer_signed['id']) +print(json.dumps(tx_transfer_retrieved, sort_keys=True, indent=4, separators=(',', ':'))) + # create another transfer transaction with the same input tx_transfer2 = b.create_transaction(testuser1_pub, testuser2_pub, tx_retrieved_id, 'TRANSFER') @@ -63,20 +72,54 @@ try: except exceptions.DoubleSpend as e: print(e) +# Create a new asset and assign it to multiple owners +tx_multisig = b.create_transaction(b.me, [testuser1_pub, testuser2_pub], None, 'CREATE') + +# Have the federation sign the transaction +tx_multisig_signed = b.sign_transaction(tx_multisig, b.me_private) +b.write_transaction(tx_multisig_signed) + +# wait a few seconds for the asset to appear on the blockchain +sleep(10) + +# retrieve the transaction +tx_multisig_retrieved = b.get_transaction(tx_multisig_signed['id']) + +print(json.dumps(tx_multisig_retrieved, sort_keys=True, indent=4, separators=(',', ':'))) + +testuser3_priv, testuser3_pub = crypto.generate_key_pair() + +tx_multisig_retrieved_id = b.get_owned_ids(testuser2_pub).pop() +tx_multisig_transfer = b.create_transaction([testuser1_pub, testuser2_pub], testuser3_pub, tx_multisig_retrieved_id, 'TRANSFER') +tx_multisig_transfer_signed = b.sign_transaction(tx_multisig_transfer, [testuser1_priv, testuser2_priv]) + +b.write_transaction(tx_multisig_transfer_signed) + +# wait a few seconds for the asset to appear on the blockchain +sleep(10) + +# retrieve the transaction +tx_multisig_retrieved = b.get_transaction(tx_multisig_transfer_signed['id']) + +print(json.dumps(tx_multisig_transfer_signed, sort_keys=True, indent=4, separators=(',', ':'))) + # create some new testusers thresholduser1_priv, thresholduser1_pub = crypto.generate_key_pair() thresholduser2_priv, thresholduser2_pub = crypto.generate_key_pair() +thresholduser3_priv, thresholduser3_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 base template for a 1-input/3-output transaction +threshold_tx = b.create_transaction(testuser2_pub, [thresholduser1_pub, thresholduser2_pub, thresholduser3_pub], + tx_retrieved_id, 'TRANSFER') -# create a Threshold Cryptocondition +# create a 2-out-of-3 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)) +threshold_condition.add_subfulfillment(Ed25519Fulfillment(public_key=thresholduser3_pub)) # update the condition in the newly created transaction threshold_tx['transaction']['conditions'][0]['condition'] = { @@ -97,28 +140,41 @@ sleep(10) # check if the transaction is already in the bigchain tx_threshold_retrieved = b.get_transaction(threshold_tx_signed['id']) +print(json.dumps(tx_threshold_retrieved, sort_keys=True, indent=4, separators=(',', ':'))) -thresholduser3_priv, thresholduser3_pub = crypto.generate_key_pair() +thresholduser4_priv, thresholduser4_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') +threshold_tx_transfer = b.create_transaction([thresholduser1_pub, thresholduser2_pub, thresholduser3_pub], + thresholduser4_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'] + +subfulfillment1 = threshold_fulfillment.get_subcondition_from_vk(thresholduser1_pub)[0] +subfulfillment2 = threshold_fulfillment.get_subcondition_from_vk(thresholduser2_pub)[0] +subfulfillment3 = threshold_fulfillment.get_subcondition_from_vk(thresholduser3_pub)[0] + # get the fulfillment message to sign threshold_tx_fulfillment_message = util.get_fulfillment_message(threshold_tx_transfer, threshold_tx_transfer['transaction']['fulfillments'][0], serialized=True) -# sign the subconditions +# clear the subconditions of the threshold fulfillment, they will be added again after signing +threshold_fulfillment.subconditions = [] + +# sign and add the subconditions until threshold of 2 is reached subfulfillment1.sign(threshold_tx_fulfillment_message, crypto.SigningKey(thresholduser1_priv)) +threshold_fulfillment.add_subfulfillment(subfulfillment1) subfulfillment2.sign(threshold_tx_fulfillment_message, crypto.SigningKey(thresholduser2_priv)) +threshold_fulfillment.add_subfulfillment(subfulfillment2) + +# Add remaining (unfulfilled) fulfillment as a condition +threshold_fulfillment.add_subcondition(subfulfillment3.condition) assert threshold_fulfillment.validate(threshold_tx_fulfillment_message) == True @@ -126,7 +182,10 @@ threshold_tx_transfer['transaction']['fulfillments'][0]['fulfillment'] = thresho assert b.verify_signature(threshold_tx_transfer) == True -assert b.validate_transaction(threshold_tx_transfer) == True +assert b.validate_transaction(threshold_tx_transfer) == threshold_tx_transfer b.write_transaction(threshold_tx_transfer) +print(json.dumps(threshold_tx_transfer, sort_keys=True, indent=4, separators=(',', ':'))) + +