Merge pull request #211 from bigchaindb/feat/127/crypto-conditions-ilp-bigchain-integration-hashlock

Feat/127/crypto conditions ilp bigchain integration hashlock
This commit is contained in:
Dimitri De Jonghe 2016-05-03 11:23:46 +02:00
commit bbdc7c86fc
4 changed files with 353 additions and 9 deletions

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

@ -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

@ -1544,6 +1544,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=(',', ':')))