Merge branch 'master' into core/193/invalid-block-backlog

This commit is contained in:
ryan 2016-05-03 15:37:56 +02:00
commit 41328b3255
10 changed files with 430 additions and 13 deletions

View File

@ -14,6 +14,29 @@ For reference, the possible headings are:
* **External Contributors** to list contributors outside of ascribe GmbH.
## [0.3.0] - 2016-05-03
Tag name: v0.3.0
= commit:
committed:
### Added
- Crypto-conditions specs according to the Interledger protocol: [Pull Request #174](https://github.com/bigchaindb/bigchaindb/pull/174)
- Added support for anonymous hashlocked conditions and fulfillments: [Pull Request #211](https://github.com/bigchaindb/bigchaindb/pull/211)
### Changed
- Several improvements to the aws deployment scripts: [Pull Request #227](https://github.com/bigchaindb/bigchaindb/pull/227)
### Fixed
- Bug related to block validation: [Pull Request #233](https://github.com/bigchaindb/bigchaindb/pull/233)
### Notes
This release completely refactored the structure of the transactions and broke compatibility with older versions
of BigchainDB. The refactor of the transactions was made in order to add support for multiple inputs/outputs and
the crypto-conditions specs from the Interledger protocol.
We also updated the rethinkdb python drivers so you need to upgrade to rethinkdb v2.3+
## [0.2.0] - 2016-04-26
Tag name: v0.2.0
= commit: 0c4a2b380aabdcf50fa2d7fb351c290aaedc3db7

View File

@ -43,6 +43,16 @@ my_string = 'This is a very long string, so long that it will not fit into just
It seems the preference is for slashes, but using parentheses is okay too. (There are good arguments either way. Arguing about it seems like a waste of time.)
### How to Format Long import Statements
If you need to `import` lots of names from a module or package, and they won't all fit in one line (without making the line too long), then use parentheses to spread the names across multiple lines, like so:
```python
from Tkinter import (Tk, Frame, Button, Entry, Canvas, Text,
LEFT, DISABLED, NORMAL, RIDGE, END)
```
For the rationale, see [PEP 328](https://www.python.org/dev/peps/pep-0328/#rationale-for-parentheses).
### Using the % operator or `format()` to Format Strings
Given the choice:

View File

@ -2,7 +2,7 @@
[![PyPI](https://img.shields.io/pypi/v/bigchaindb.svg)](https://pypi.python.org/pypi/BigchainDB)
[![Travis branch](https://img.shields.io/travis/bigchaindb/bigchaindb/master.svg)](https://travis-ci.org/bigchaindb/bigchaindb)
[![Codecov branch](https://img.shields.io/codecov/c/github/bigchaindb/bigchaindb/master.svg)](https://codecov.io/github/bigchaindb/bigchaindb?branch=master)
[![Documentation Status](https://readthedocs.org/projects/bigchaindb/badge/?version=stable)](https://bigchaindb.readthedocs.org/en/stable/)
[![Documentation Status](https://readthedocs.org/projects/bigchaindb/badge/?version=latest)](https://bigchaindb.readthedocs.org/en/latest/)
# BigchainDB

View File

@ -176,7 +176,6 @@ class BaseConsensusRules(AbstractConsensusRules):
return transaction
# TODO: Unsure if a bigchain parameter is really necessary here?
@staticmethod
def validate_block(bigchain, block):
"""Validate a block.
@ -198,6 +197,15 @@ class BaseConsensusRules(AbstractConsensusRules):
if calculated_hash != block['id']:
raise exceptions.InvalidHash()
# Check if the block was created by a federation node
if block['block']['node_pubkey'] not in (bigchain.federation_nodes + [bigchain.me]):
raise exceptions.OperationError('Only federation nodes can create blocks')
# Check if block signature is valid
verifying_key = crypto.VerifyingKey(block['block']['node_pubkey'])
if not verifying_key.verify(util.serialize(block['block']), block['signature']):
raise exceptions.InvalidSignature('Invalid block signature')
return block
@staticmethod

View File

@ -203,11 +203,20 @@ def create_tx(current_owners, new_owners, inputs, operation, payload=None):
},
}
"""
# validate arguments (owners and inputs should be lists)
# validate arguments (owners and inputs should be lists or None)
# The None case appears on fulfilling a hashlock
if current_owners is None:
current_owners = []
if not isinstance(current_owners, list):
current_owners = [current_owners]
# The None case appears on assigning a hashlock
if new_owners is None:
new_owners = []
if not isinstance(new_owners, list):
new_owners = [new_owners]
if not isinstance(inputs, list):
inputs = [inputs]
@ -247,20 +256,30 @@ def create_tx(current_owners, new_owners, inputs, operation, payload=None):
# handle outputs
conditions = []
for fulfillment in fulfillments:
# threshold condition
if len(new_owners) > 1:
condition = cc.ThresholdSha256Fulfillment(threshold=len(new_owners))
for new_owner in new_owners:
condition.add_subfulfillment(cc.Ed25519Fulfillment(public_key=new_owner))
# simple signature condition
elif len(new_owners) == 1:
condition = cc.Ed25519Fulfillment(public_key=new_owners[0])
conditions.append({
'new_owners': new_owners,
'condition': {
'details': json.loads(condition.serialize_json()),
'uri': condition.condition.serialize_uri()
},
'cid': fulfillment['fid']
})
# to be added later (hashlock conditions)
else:
condition = None
if condition:
conditions.append({
'new_owners': new_owners,
'condition': {
'details': json.loads(condition.serialize_json()),
'uri': condition.condition.serialize_uri()
},
'cid': fulfillment['fid']
})
tx = {
'fulfillments': fulfillments,

View File

@ -1,2 +1,2 @@
__version__ = '0.2.0'
__short_version__ = '0.2'
__version__ = '0.3.0'
__short_version__ = '0.3'

View File

@ -26,6 +26,7 @@ coverage:
- "deploy-cluster-aws/*"
- "docs/*"
- "tests/*"
- "bigchaindb/version.py"
comment:
# @stevepeak (from codecov.io) suggested we change 'suggestions' to 'uncovered'

View File

@ -746,3 +746,142 @@ threshold_tx_transfer
"version":1
}
```
### Hash-locked Conditions
By creating a hash of a difficult-to-guess 256-bit random or pseudo-random integer it is possible to create a condition which the creator can trivially fulfill by publishing the random value. However, for anyone else, the condition is cryptographically hard to fulfill, because they would have to find a preimage for the given condition hash.
One possible usecase might be to redeem a digital voucher when given a secret (voucher code).
```python
# Create a hash-locked asset without any new_owners
hashlock_tx = b.create_transaction(b.me, None, None, 'CREATE')
# Define a secret that will be hashed - fulfillments need to guess the secret
secret = b'much secret! wow!'
first_tx_condition = cc.PreimageSha256Fulfillment(preimage=secret)
# The conditions list is empty, so we need to append a new condition
hashlock_tx['transaction']['conditions'].append({
'condition': {
'uri': first_tx_condition.condition.serialize_uri()
},
'cid': 0,
'new_owners': None
})
# Conditions have been updated, so hash needs updating
hashlock_tx['id'] = util.get_hash_data(hashlock_tx)
# The asset needs to be signed by the current_owner
hashlock_tx_signed = b.sign_transaction(hashlock_tx, b.me_private)
# Some validations
assert b.validate_transaction(hashlock_tx_signed) == hashlock_tx_signed
assert b.is_valid_transaction(hashlock_tx_signed) == hashlock_tx_signed
b.write_transaction(hashlock_tx_signed)
hashlock_tx_signed
```
```python
{
"assignee":"FmLm6MxCABc8TsiZKdeYaZKo5yZWMM6Vty7Q1B6EgcP2",
"id":"604c520244b7ff63604527baf269e0cbfb887122f503703120fd347d6b99a237",
"transaction":{
"conditions":[
{
"cid":0,
"condition":{
"uri":"cc:0:3:nsW2IiYgk9EUtsg4uBe3pBnOgRoAEX2IIsPgjqZz47U:17"
},
"new_owners":None
}
],
"data":None,
"fulfillments":[
{
"current_owners":[
"FmLm6MxCABc8TsiZKdeYaZKo5yZWMM6Vty7Q1B6EgcP2"
],
"fid":0,
"fulfillment":"cf:4:21-D-LfNhIQhvY5914ArFTUGpgPKc7EVC1ZtJqqOTHGx1p9FuRr9tRfkbdqtX2MZWh7sRVUmMnwp7I1-xZbCnCkeADf69IwDHbZvNS6aTr1CpekREsV9ZG8m_wjlZiUN",
"input":None
}
],
"operation":"CREATE",
"timestamp":"1461250387.910102"
},
"version":1
}
```
In order to redeem the asset, one needs to create a fulfillment the correct secret as a preimage:
```python
hashlockuser_priv, hashlockuser_pub = crypto.generate_key_pair()
# create hashlock fulfillment tx
hashlock_fulfill_tx = b.create_transaction(None, hashlockuser_pub, {'txid': hashlock_tx['id'], 'cid': 0}, 'TRANSFER')
# provide a wrong secret
hashlock_fulfill_tx_fulfillment = cc.PreimageSha256Fulfillment(preimage=b'')
hashlock_fulfill_tx['transaction']['fulfillments'][0]['fulfillment'] = \
hashlock_fulfill_tx_fulfillment.serialize_uri()
assert b.is_valid_transaction(hashlock_fulfill_tx) == False
# provide the right secret
hashlock_fulfill_tx_fulfillment = cc.PreimageSha256Fulfillment(preimage=secret)
hashlock_fulfill_tx['transaction']['fulfillments'][0]['fulfillment'] = \
hashlock_fulfill_tx_fulfillment.serialize_uri()
assert b.validate_transaction(hashlock_fulfill_tx) == hashlock_fulfill_tx
assert b.is_valid_transaction(hashlock_fulfill_tx) == hashlock_fulfill_tx
b.write_transaction(hashlock_fulfill_tx)
hashlock_fulfill_tx
```
```python
{
"assignee":"FmLm6MxCABc8TsiZKdeYaZKo5yZWMM6Vty7Q1B6EgcP2",
"id":"fe6871bf3ca62eb61c52c5555cec2e07af51df817723f0cb76e5cf6248f449d2",
"transaction":{
"conditions":[
{
"cid":0,
"condition":{
"details":{
"bitmask":32,
"public_key":"EiqCKxnBCmmNb83qyGch48tULK9RLaEt4xFA43UVCVDb",
"signature":None,
"type":"fulfillment",
"type_id":4
},
"uri":"cc:4:20:y9884Md2YI_wdnGSTJGhwvFaNsKLe8sqwimqk-2JLSI:96"
},
"new_owners":[
"EiqCKxnBCmmNb83qyGch48tULK9RLaEt4xFA43UVCVDb"
]
}
],
"data":None,
"fulfillments":[
{
"current_owners":[],
"fid":0,
"fulfillment":"cf:0:bXVjaCBzZWNyZXQhIHdvdyE",
"input":{
"cid":0,
"txid":"604c520244b7ff63604527baf269e0cbfb887122f503703120fd347d6b99a237"
}
}
],
"operation":"TRANSFER",
"timestamp":"1461250397.944510"
},
"version":1
}
```

View File

@ -433,6 +433,37 @@ class TestBlockValidation(object):
assert block == b.validate_block(block)
assert b.is_valid_block(block)
def test_invalid_signature(self, b):
# create a valid block
block = b.create_block([])
# replace the block signature with an invalid one
block['signature'] = crypto.SigningKey(b.me_private).sign(b'wrongdata')
# check that validate_block raises an InvalidSignature exception
with pytest.raises(exceptions.InvalidSignature):
b.validate_block(block)
def test_invalid_node_pubkey(self, b):
# blocks can only be created by a federation node
# create a valid block
block = b.create_block([])
# create some temp keys
tmp_sk, tmp_vk = crypto.generate_key_pair()
# change the block node_pubkey
block['block']['node_pubkey'] = tmp_vk
# just to make sure lets re-hash the block and create a valid signature
# from a non federation node
block['id'] = crypto.hash_data(util.serialize(block['block']))
block['signature'] = crypto.SigningKey(tmp_sk).sign(util.serialize(block['block']))
# check that validate_block raises an OperationError
with pytest.raises(exceptions.OperationError):
b.validate_block(block)
class TestBigchainVoter(object):
def test_valid_block_voting(self, b):
@ -1544,6 +1575,136 @@ class TestCryptoconditions(object):
assert b.verify_signature(tx_transfer_signed) is True
def test_create_asset_with_hashlock_condition(self, b):
hashlock_tx = b.create_transaction(b.me, None, None, 'CREATE')
secret = b'much secret! wow!'
first_tx_condition = cc.PreimageSha256Fulfillment(preimage=secret)
hashlock_tx['transaction']['conditions'].append({
'condition': {
'details': json.loads(first_tx_condition.serialize_json()),
'uri': first_tx_condition.condition.serialize_uri()
},
'cid': 0,
'new_owners': None
})
# conditions have been updated, so hash needs updating
hashlock_tx['id'] = util.get_hash_data(hashlock_tx)
hashlock_tx_signed = b.sign_transaction(hashlock_tx, b.me_private)
assert b.validate_transaction(hashlock_tx_signed) == hashlock_tx_signed
assert b.is_valid_transaction(hashlock_tx_signed) == hashlock_tx_signed
b.write_transaction(hashlock_tx_signed)
# create and write block to bigchain
block = b.create_block([hashlock_tx_signed])
b.write_block(block, durability='hard')
@pytest.mark.usefixtures('inputs')
def test_transfer_asset_with_hashlock_condition(self, b, user_vk, user_sk):
first_input_tx = b.get_owned_ids(user_vk).pop()
hashlock_tx = b.create_transaction(user_vk, None, first_input_tx, 'TRANSFER')
secret = b'much secret! wow!'
first_tx_condition = cc.PreimageSha256Fulfillment(preimage=secret)
hashlock_tx['transaction']['conditions'].append({
'condition': {
'details': json.loads(first_tx_condition.serialize_json()),
'uri': first_tx_condition.condition.serialize_uri()
},
'cid': 0,
'new_owners': None
})
# conditions have been updated, so hash needs updating
hashlock_tx['id'] = util.get_hash_data(hashlock_tx)
hashlock_tx_signed = b.sign_transaction(hashlock_tx, user_sk)
assert b.validate_transaction(hashlock_tx_signed) == hashlock_tx_signed
assert b.is_valid_transaction(hashlock_tx_signed) == hashlock_tx_signed
assert len(b.get_owned_ids(user_vk)) == 1
b.write_transaction(hashlock_tx_signed)
# create and write block to bigchain
block = b.create_block([hashlock_tx_signed])
b.write_block(block, durability='hard')
assert len(b.get_owned_ids(user_vk)) == 0
def test_create_and_fulfill_asset_with_hashlock_condition(self, b, user_vk):
hashlock_tx = b.create_transaction(b.me, None, None, 'CREATE')
secret = b'much secret! wow!'
first_tx_condition = cc.PreimageSha256Fulfillment(preimage=secret)
hashlock_tx['transaction']['conditions'].append({
'condition': {
'details': json.loads(first_tx_condition.serialize_json()),
'uri': first_tx_condition.condition.serialize_uri()
},
'cid': 0,
'new_owners': None
})
# conditions have been updated, so hash needs updating
hashlock_tx['id'] = util.get_hash_data(hashlock_tx)
hashlock_tx_signed = b.sign_transaction(hashlock_tx, b.me_private)
assert b.validate_transaction(hashlock_tx_signed) == hashlock_tx_signed
assert b.is_valid_transaction(hashlock_tx_signed) == hashlock_tx_signed
b.write_transaction(hashlock_tx_signed)
# create and write block to bigchain
block = b.create_block([hashlock_tx_signed])
b.write_block(block, durability='hard')
assert len(b.get_owned_ids(b.me)) == 0
# create hashlock fulfillment tx
hashlock_fulfill_tx = b.create_transaction(None, user_vk, {'txid': hashlock_tx['id'], 'cid': 0}, 'TRANSFER')
hashlock_fulfill_tx_fulfillment = cc.PreimageSha256Fulfillment(preimage=b'')
hashlock_fulfill_tx['transaction']['fulfillments'][0]['fulfillment'] = \
hashlock_fulfill_tx_fulfillment.serialize_uri()
with pytest.raises(exceptions.InvalidSignature):
b.validate_transaction(hashlock_fulfill_tx)
assert b.is_valid_transaction(hashlock_fulfill_tx) == False
hashlock_fulfill_tx_fulfillment = cc.PreimageSha256Fulfillment(preimage=secret)
hashlock_fulfill_tx['transaction']['fulfillments'][0]['fulfillment'] = \
hashlock_fulfill_tx_fulfillment.serialize_uri()
assert b.validate_transaction(hashlock_fulfill_tx) == hashlock_fulfill_tx
assert b.is_valid_transaction(hashlock_fulfill_tx) == hashlock_fulfill_tx
b.write_transaction(hashlock_fulfill_tx)
# create and write block to bigchain
block = b.create_block([hashlock_fulfill_tx])
b.write_block(block, durability='hard')
assert len(b.get_owned_ids(b.me)) == 0
assert len(b.get_owned_ids(user_vk)) == 1
# try doublespending
user2_sk, user2_vk = crypto.generate_key_pair()
hashlock_doublespend_tx = b.create_transaction(None, user2_vk, {'txid': hashlock_tx['id'], 'cid': 0}, 'TRANSFER')
hashlock_doublespend_tx_fulfillment = cc.PreimageSha256Fulfillment(preimage=secret)
hashlock_doublespend_tx['transaction']['fulfillments'][0]['fulfillment'] = \
hashlock_doublespend_tx_fulfillment.serialize_uri()
with pytest.raises(exceptions.DoubleSpend):
b.validate_transaction(hashlock_doublespend_tx)
def test_get_subcondition_from_vk(self, b, user_sk, user_vk):
user2_sk, user2_vk = crypto.generate_key_pair()
user3_sk, user3_vk = crypto.generate_key_pair()

View File

@ -233,4 +233,60 @@ b.write_transaction(threshold_tx_transfer)
print(json.dumps(threshold_tx_transfer, sort_keys=True, indent=4, separators=(',', ':')))
"""
Hashlocked Conditions
"""
# Create a hash-locked asset without any new_owners
hashlock_tx = b.create_transaction(b.me, None, None, 'CREATE')
# Define a secret that will be hashed - fulfillments need to guess the secret
secret = b'much secret! wow!'
first_tx_condition = cc.PreimageSha256Fulfillment(preimage=secret)
# The conditions list is empty, so we need to append a new condition
hashlock_tx['transaction']['conditions'].append({
'condition': {
'uri': first_tx_condition.condition.serialize_uri()
},
'cid': 0,
'new_owners': None
})
# Conditions have been updated, so hash needs updating
hashlock_tx['id'] = util.get_hash_data(hashlock_tx)
# The asset needs to be signed by the current_owner
hashlock_tx_signed = b.sign_transaction(hashlock_tx, b.me_private)
# Some validations
assert b.validate_transaction(hashlock_tx_signed) == hashlock_tx_signed
assert b.is_valid_transaction(hashlock_tx_signed) == hashlock_tx_signed
b.write_transaction(hashlock_tx_signed)
print(json.dumps(hashlock_tx_signed, sort_keys=True, indent=4, separators=(',', ':')))
sleep(10)
hashlockuser_priv, hashlockuser_pub = crypto.generate_key_pair()
# create hashlock fulfillment tx
hashlock_fulfill_tx = b.create_transaction(None, hashlockuser_priv, {'txid': hashlock_tx['id'], 'cid': 0}, 'TRANSFER')
# try a wrong secret
hashlock_fulfill_tx_fulfillment = cc.PreimageSha256Fulfillment(preimage=b'')
hashlock_fulfill_tx['transaction']['fulfillments'][0]['fulfillment'] = \
hashlock_fulfill_tx_fulfillment.serialize_uri()
assert b.is_valid_transaction(hashlock_fulfill_tx) == False
# provide the right secret
hashlock_fulfill_tx_fulfillment = cc.PreimageSha256Fulfillment(preimage=secret)
hashlock_fulfill_tx['transaction']['fulfillments'][0]['fulfillment'] = \
hashlock_fulfill_tx_fulfillment.serialize_uri()
assert b.validate_transaction(hashlock_fulfill_tx) == hashlock_fulfill_tx
assert b.is_valid_transaction(hashlock_fulfill_tx) == hashlock_fulfill_tx
b.write_transaction(hashlock_fulfill_tx)
print(json.dumps(hashlock_fulfill_tx, sort_keys=True, indent=4, separators=(',', ':')))