Compare commits

...

22 Commits
main ... v0.9.7

Author SHA1 Message Date
Jürgen Eckel
7f80b0f306 fixed deployment issue for 0.9.6
Signed-off-by: Jürgen Eckel <juergen@riddleandcode.com>
2022-06-17 10:33:26 +02:00
Jürgen Eckel
d1bb726e9a increased version to 0.9.6
Signed-off-by: Jürgen Eckel <juergen@riddleandcode.com>
2022-06-16 21:50:01 +02:00
Jürgen Eckel
99e1edf880 increased cc usage to 0.9.9 readded daemon proceses
Signed-off-by: Jürgen Eckel <juergen@riddleandcode.com>
2022-06-15 17:30:31 +02:00
Jürgen Eckel
9f66450c7c using cryptoconditions without print message
Signed-off-by: Jürgen Eckel <juergen@riddleandcode.com>
2022-06-15 15:45:55 +02:00
Jürgen Eckel
a061e773c9 increased version number
Signed-off-by: Jürgen Eckel <juergen@riddleandcode.com>
2022-06-15 15:11:36 +02:00
Jürgen Eckel
6d0af34aa2 simplified zenroom unit test
Signed-off-by: Jürgen Eckel <juergen@riddleandcode.com>
2022-06-15 14:16:48 +02:00
Jürgen Eckel
ea05872927 fixed linting errors
Signed-off-by: Jürgen Eckel <juergen@riddleandcode.com>
2022-06-15 13:45:09 +02:00
Jürgen Eckel
a35f705865 Merge branch 'main' into final_zenroom 2022-06-15 11:22:14 +02:00
Jürgen Eckel
bab55e2803 Merge branch 'main' into final_zenroom
Signed-off-by: Jürgen Eckel <juergen@riddleandcode.com>
2022-06-15 11:05:07 +02:00
Jürgen Eckel
7aa3228844 adjusted zenroom integraiton tests
Signed-off-by: Jürgen Eckel <juergen@riddleandcode.com>
2022-06-15 10:31:29 +02:00
Jürgen Eckel
57f8529303 fixed acceptance tests
Signed-off-by: Jürgen Eckel <juergen@riddleandcode.com>
2022-06-15 09:42:49 +02:00
Jürgen Eckel
f3791cfd8f removed obsolte lines from the zenroom tests
Signed-off-by: Jürgen Eckel <juergen@riddleandcode.com>
2022-06-14 20:15:35 +02:00
Jürgen Eckel
28869d5a88 simplified zenroom unit tests
Signed-off-by: Jürgen Eckel <juergen@riddleandcode.com>
2022-06-14 17:27:06 +02:00
Jürgen Eckel
4fd071adeb zenroom unit tests are passing
Signed-off-by: Jürgen Eckel <juergen@riddleandcode.com>
2022-06-14 17:21:53 +02:00
Jürgen Eckel
b394831e39 the last mile before integration
Signed-off-by: Jürgen Eckel <juergen@riddleandcode.com>
2022-06-14 13:45:46 +02:00
Jürgen Eckel
ca4a9cf949 added zenroom fulfillment verification
Signed-off-by: Jürgen Eckel <juergen@riddleandcode.com>
2022-06-14 13:45:09 +02:00
Jürgen Eckel
3f28ddd990 added manual tx crafting
Signed-off-by: Jürgen Eckel <juergen@riddleandcode.com>
2022-06-13 23:50:53 +02:00
Jürgen Eckel
ebabd3de7d extended test to pass zenrooom validation, but to fail planetmint validation.
Signed-off-by: Jürgen Eckel <juergen@riddleandcode.com>
2022-06-10 13:09:48 +02:00
Jürgen Eckel
ecb828f1d6 added fialing zenroom tx signing test
Signed-off-by: Jürgen Eckel <juergen@riddleandcode.com>
2022-04-27 08:45:38 +02:00
Jürgen Eckel
7237df59ba increased version number and fixed a zenroom runtime bug
Signed-off-by: Jürgen Eckel <juergen@riddleandcode.com>
2022-04-26 14:13:14 +02:00
Jürgen Eckel
83cd391028 expl. define dthe aiohttp package
Signed-off-by: Jürgen Eckel <juergen@riddleandcode.com>
2022-04-26 14:10:14 +02:00
Jürgen Eckel
b455434aac zenroom fixes
Signed-off-by: Jürgen Eckel <juergen@riddleandcode.com>
2022-04-07 09:00:55 +02:00
16 changed files with 1128 additions and 779 deletions

View File

@ -25,6 +25,10 @@ For reference, the possible headings are:
* **Known Issues** * **Known Issues**
* **Notes** * **Notes**
## [0.9.7] - 2022-06-17
### Feature Update
Deep Zenroom integration
## [0.9.6] - 2022-06-08 ## [0.9.6] - 2022-06-08

View File

@ -9,56 +9,10 @@ RUN apt-get install -y vim zsh build-essential cmake
RUN mkdir -p /src RUN mkdir -p /src
RUN /usr/local/bin/python -m pip install --upgrade pip RUN /usr/local/bin/python -m pip install --upgrade pip
RUN pip install --upgrade meson ninja RUN pip install --upgrade meson ninja
RUN pip install zenroom==2.0.0.dev1644927841
RUN pip install --upgrade \ RUN pip install --upgrade \
pycco \ pycco \
websocket-client~=0.47.0 \ websocket-client~=0.47.0 \
pytest~=3.0 \ pytest~=3.0 \
#git+https://github.com/planetmint/cryptoconditions.git@gitzenroom \ planetmint-cryptoconditions>=0.9.9\
#git+https://github.com/planetmint/planetmint-driver.git@gitzenroom \ planetmint-driver>=0.9.2 \
planetmint-cryptoconditions>=0.9.4\
planetmint-driver>=0.9.0 \
blns blns
#FROM python:3.9
#
#RUN apt-get update && apt-get install -y vim zsh
#RUN apt-get update \
# && apt-get install -y git zsh\
# && pip install -U pip \
# && apt-get autoremove \
# && apt-get clean
#RUN apt install sudo
#RUN apt-get install -y python3 openssl ca-certificates git python3-dev
#RUN apt-get install zsh gcc
#RUN apt-get install libffi-dev
#RUN apt-get install build-essential cmake -y
#
#
#RUN mkdir -p /src
#RUN pip install --upgrade \
# pycco \
# websocket-client~=0.47.0 \
# pytest~=3.0 \
# planetmint-driver>=0.9.0 \
# blns \
# git+https://github.com/planetmint/cryptoconditions.git@gitzenroom >=0.9.0 \
# chardet==3.0.4 \
# aiohttp==3.7.4 \
# abci==0.8.3 \
# #planetmint-cryptoconditions>=0.9.0\
# flask-cors==3.0.10 \
# flask-restful==0.3.9 \
# flask==2.0.1 \
# gunicorn==20.1.0 \
# jsonschema==3.2.0 \
# logstats==0.3.0 \
# packaging>=20.9 \
# pymongo==3.11.4 \
# pyyaml==5.4.1 \
# requests==2.25.1 \
# setproctitle==1.2.2
#

View File

@ -5,37 +5,41 @@
import pytest import pytest
GENERATE_KEYPAIR = \ CONDITION_SCRIPT = """
"""Rule input encoding base58 Scenario 'ecdh': create the signature of an object
Rule output encoding base58 Given I have the 'keyring'
Scenario 'ecdh': Create the keypair Given that I have a 'string dictionary' named 'houses' inside 'asset'
Given that I am known as 'Pippo' When I create the signature of 'houses'
When I create the ecdh key Then print the 'signature'"""
When I create the testnet key
Then print data"""
# secret key to public key
SK_TO_PK = \
"""Rule input encoding base58
Rule output encoding base58
Scenario 'ecdh': Create the keypair
Given that I am known as '{}'
Given I have the 'keys'
When I create the ecdh public key
When I create the testnet address
Then print my 'ecdh public key'
Then print my 'testnet address'"""
FULFILL_SCRIPT = \ FULFILL_SCRIPT = \
"""Rule input encoding base58 """Scenario 'ecdh': Bob verifies the signature from Alice
Rule output encoding base58
Scenario 'ecdh': Bob verifies the signature from Alice
Given I have a 'ecdh public key' from 'Alice' Given I have a 'ecdh public key' from 'Alice'
Given that I have a 'string dictionary' named 'houses' inside 'asset' Given that I have a 'string dictionary' named 'houses' inside 'asset'
Given I have a 'signature' named 'data.signature' inside 'result' Given I have a 'signature' named 'signature' inside 'result'
When I verify the 'houses' has a signature in 'data.signature' by 'Alice' When I verify the 'houses' has a signature in 'signature' by 'Alice'
Then print the string 'ok'""" Then print the string 'ok'"""
SK_TO_PK = \
"""Scenario 'ecdh': Create the keypair
Given that I am known as '{}'
Given I have the 'keyring'
When I create the ecdh public key
When I create the bitcoin address
Then print my 'ecdh public key'
Then print my 'bitcoin address'"""
GENERATE_KEYPAIR = \
"""Scenario 'ecdh': Create the keypair
Given that I am known as 'Pippo'
When I create the ecdh key
When I create the bitcoin key
Then print data"""
ZENROOM_DATA = {
'also': 'more data'
}
HOUSE_ASSETS = { HOUSE_ASSETS = {
"data": { "data": {
"houses": [ "houses": [
@ -51,19 +55,11 @@ HOUSE_ASSETS = {
} }
} }
ZENROOM_DATA = { metadata = {
'also': 'more data' 'units': 300,
'type': 'KG'
} }
CONDITION_SCRIPT = """Rule input encoding base58
Rule output encoding base58
Scenario 'ecdh': create the signature of an object
Given I have the 'keys'
Given that I have a 'string dictionary' named 'houses' inside 'asset'
When I create the signature of 'houses'
When I rename the 'signature' to 'data.signature'
Then print the 'data.signature'"""
@pytest.fixture @pytest.fixture
def gen_key_zencode(): def gen_key_zencode():
return GENERATE_KEYPAIR return GENERATE_KEYPAIR

View File

@ -1,68 +1,73 @@
# GOAL: import os
# In this script I tried to implement the ECDSA signature using zenroom
# However, the scripts are customizable and so with the same procedure
# we can implement more complex smart contracts
# PUBLIC IDENTITY
# The public identity of the users in this script (Bob and Alice)
# is the pair (ECDH public key, Testnet address)
import json import json
import base58
from hashlib import sha3_256
from cryptoconditions.types.ed25519 import Ed25519Sha256
from cryptoconditions.types.zenroom import ZenroomSha256
from zenroom import zencode_exec
from planetmint_driver import Planetmint
from planetmint_driver.crypto import generate_keypair
import hashlib
from cryptoconditions import ZenroomSha256
from json.decoder import JSONDecodeError
def test_zenroom(gen_key_zencode, secret_key_to_private_key_zencode, fulfill_script_zencode,
condition_script_zencode, zenroom_data, zenroom_house_assets):
alice = json.loads(ZenroomSha256.run_zenroom(gen_key_zencode).output)['keys']
bob = json.loads(ZenroomSha256.run_zenroom(gen_key_zencode).output)['keys']
zen_public_keys = json.loads(ZenroomSha256.run_zenroom(secret_key_to_private_key_zencode.format('Alice'),
keys={'keys': alice}).output)
zen_public_keys.update(json.loads(ZenroomSha256.run_zenroom(secret_key_to_private_key_zencode.format('Bob'),
keys={'keys': bob}).output))
# CRYPTO-CONDITIONS: instantiate an Ed25519 crypto-condition for buyer
zenSha = ZenroomSha256(script=fulfill_script_zencode, keys=zen_public_keys, data=zenroom_data) def test_zenroom_signing(gen_key_zencode, secret_key_to_private_key_zencode,
fulfill_script_zencode, zenroom_data, zenroom_house_assets,
condition_script_zencode):
biolabs = generate_keypair()
version = '2.0'
alice = json.loads(zencode_exec(gen_key_zencode).output)['keyring']
bob = json.loads(zencode_exec(gen_key_zencode).output)['keyring']
zen_public_keys = json.loads(zencode_exec(secret_key_to_private_key_zencode.format('Alice'),
keys=json.dumps({'keyring': alice})).output)
zen_public_keys.update(json.loads(zencode_exec(secret_key_to_private_key_zencode.format('Bob'),
keys=json.dumps({'keyring': bob})).output))
zenroomscpt = ZenroomSha256(script=fulfill_script_zencode, data=zenroom_data, keys=zen_public_keys)
print(F'zenroom is: {zenroomscpt.script}')
# CRYPTO-CONDITIONS: generate the condition uri # CRYPTO-CONDITIONS: generate the condition uri
condition_uri = zenSha.condition.serialize_uri() condition_uri_zen = zenroomscpt.condition.serialize_uri()
print(F'\nzenroom condition URI: {condition_uri_zen}')
# CRYPTO-CONDITIONS: construct an unsigned fulfillment dictionary # CRYPTO-CONDITIONS: construct an unsigned fulfillment dictionary
unsigned_fulfillment_dict = { unsigned_fulfillment_dict_zen = {
'type': zenSha.TYPE_NAME, 'type': zenroomscpt.TYPE_NAME,
'script': fulfill_script_zencode, 'public_key': base58.b58encode(biolabs.public_key).decode(),
'keys': zen_public_keys,
} }
output = { output = {
'amount': '1000', 'amount': '10',
'condition': { 'condition': {
'details': unsigned_fulfillment_dict, 'details': unsigned_fulfillment_dict_zen,
'uri': condition_uri, 'uri': condition_uri_zen,
}, },
'data': zenroom_data, 'public_keys': [biolabs.public_key,],
'script': fulfill_script_zencode,
'conf': '',
'public_keys': (zen_public_keys['Alice']['ecdh_public_key'], ),
} }
input_ = { input_ = {
'fulfillment': None, 'fulfillment': None,
'fulfills': None, 'fulfills': None,
'owners_before': (zen_public_keys['Alice']['ecdh_public_key'], ), 'owners_before': [biolabs.public_key,]
}
metadata = {
"result": {
"output": ["ok"]
}
} }
token_creation_tx = { token_creation_tx = {
'operation': 'CREATE', 'operation': 'CREATE',
'asset': zenroom_house_assets, 'asset': zenroom_house_assets,
'metadata': None, 'metadata': metadata,
'outputs': (output,), 'outputs': [output,],
'inputs': (input_,), 'inputs': [input_,],
'version': '2.0', 'version': version,
'id': None, 'id': None,
} }
@ -74,12 +79,36 @@ condition_script_zencode, zenroom_data, zenroom_house_assets):
ensure_ascii=False, ensure_ascii=False,
) )
try: # major workflow:
assert(not zenSha.validate(message=message)) # we store the fulfill script in the transaction/message (zenroom-sha)
except JSONDecodeError: # the condition script is used to fulfill the transaction and create the signature
pass #
except ValueError: # the server should ick the fulfill script and recreate the zenroom-sha and verify the signature
pass
message = zenSha.sign(message, condition_script_zencode, alice)
assert(zenSha.validate(message=message))
message = zenroomscpt.sign(message, condition_script_zencode, alice)
assert(zenroomscpt.validate(message=message))
message = json.loads(message)
fulfillment_uri_zen = zenroomscpt.serialize_uri()
message['inputs'][0]['fulfillment'] = fulfillment_uri_zen
tx = message
tx['id'] = None
json_str_tx = json.dumps(
tx,
sort_keys=True,
skipkeys=False,
separators=(',', ':')
)
# SHA3: hash the serialized id-less transaction to generate the id
shared_creation_txid = sha3_256(json_str_tx.encode()).hexdigest()
message['id'] = shared_creation_txid
# `https://example.com:9984`
plntmnt = Planetmint(os.environ.get('PLANETMINT_ENDPOINT'))
sent_transfer_tx = plntmnt.transactions.send_commit(message)
print( f"\n\nstatus and result : + {sent_transfer_tx}")

View File

@ -13,10 +13,8 @@ RUN mkdir -p /src
RUN pip install --upgrade meson ninja RUN pip install --upgrade meson ninja
RUN pip install --upgrade \ RUN pip install --upgrade \
pytest~=6.2.5 \ pytest~=6.2.5 \
planetmint-driver~=0.9.0 \
pycco \ pycco \
websocket-client~=0.47.0 \ websocket-client~=0.47.0 \
#git+https://github.com/planetmint/cryptoconditions.git@gitzenroom \ planetmint-cryptoconditions>=0.9.9\
#git+https://github.com/planetmint/planetmint-driver.git@gitzenroom \ planetmint-driver>=0.9.2 \
blns blns

View File

@ -5,37 +5,41 @@
import pytest import pytest
GENERATE_KEYPAIR = \ CONDITION_SCRIPT = """
"""Rule input encoding base58 Scenario 'ecdh': create the signature of an object
Rule output encoding base58 Given I have the 'keyring'
Scenario 'ecdh': Create the keypair Given that I have a 'string dictionary' named 'houses' inside 'asset'
Given that I am known as 'Pippo' When I create the signature of 'houses'
When I create the ecdh key Then print the 'signature'"""
When I create the testnet key
Then print data"""
# secret key to public key
SK_TO_PK = \
"""Rule input encoding base58
Rule output encoding base58
Scenario 'ecdh': Create the keypair
Given that I am known as '{}'
Given I have the 'keys'
When I create the ecdh public key
When I create the testnet address
Then print my 'ecdh public key'
Then print my 'testnet address'"""
FULFILL_SCRIPT = \ FULFILL_SCRIPT = \
"""Rule input encoding base58 """Scenario 'ecdh': Bob verifies the signature from Alice
Rule output encoding base58
Scenario 'ecdh': Bob verifies the signature from Alice
Given I have a 'ecdh public key' from 'Alice' Given I have a 'ecdh public key' from 'Alice'
Given that I have a 'string dictionary' named 'houses' inside 'asset' Given that I have a 'string dictionary' named 'houses' inside 'asset'
Given I have a 'signature' named 'data.signature' inside 'result' Given I have a 'signature' named 'signature' inside 'result'
When I verify the 'houses' has a signature in 'data.signature' by 'Alice' When I verify the 'houses' has a signature in 'signature' by 'Alice'
Then print the string 'ok'""" Then print the string 'ok'"""
SK_TO_PK = \
"""Scenario 'ecdh': Create the keypair
Given that I am known as '{}'
Given I have the 'keyring'
When I create the ecdh public key
When I create the bitcoin address
Then print my 'ecdh public key'
Then print my 'bitcoin address'"""
GENERATE_KEYPAIR = \
"""Scenario 'ecdh': Create the keypair
Given that I am known as 'Pippo'
When I create the ecdh key
When I create the bitcoin key
Then print data"""
ZENROOM_DATA = {
'also': 'more data'
}
HOUSE_ASSETS = { HOUSE_ASSETS = {
"data": { "data": {
"houses": [ "houses": [
@ -51,45 +55,31 @@ HOUSE_ASSETS = {
} }
} }
ZENROOM_DATA = { metadata = {
'also': 'more data' 'units': 300,
'type': 'KG'
} }
CONDITION_SCRIPT = """Rule input encoding base58
Rule output encoding base58
Scenario 'ecdh': create the signature of an object
Given I have the 'keys'
Given that I have a 'string dictionary' named 'houses' inside 'asset'
When I create the signature of 'houses'
When I rename the 'signature' to 'data.signature'
Then print the 'data.signature'"""
@pytest.fixture @pytest.fixture
def gen_key_zencode(): def gen_key_zencode():
return GENERATE_KEYPAIR return GENERATE_KEYPAIR
@pytest.fixture @pytest.fixture
def secret_key_to_private_key_zencode(): def secret_key_to_private_key_zencode():
return SK_TO_PK return SK_TO_PK
@pytest.fixture @pytest.fixture
def fulfill_script_zencode(): def fulfill_script_zencode():
return FULFILL_SCRIPT return FULFILL_SCRIPT
@pytest.fixture @pytest.fixture
def condition_script_zencode(): def condition_script_zencode():
return CONDITION_SCRIPT return CONDITION_SCRIPT
@pytest.fixture @pytest.fixture
def zenroom_house_assets(): def zenroom_house_assets():
return HOUSE_ASSETS return HOUSE_ASSETS
@pytest.fixture @pytest.fixture
def zenroom_data(): def zenroom_data():
return ZENROOM_DATA return ZENROOM_DATA

View File

@ -1,84 +1,123 @@
# GOAL:
# In this script I tried to implement the ECDSA signature using zenroom
# However, the scripts are customizable and so with the same procedure
# we can implement more complex smart contracts
# PUBLIC IDENTITY
# The public identity of the users in this script (Bob and Alice)
# is the pair (ECDH public key, Testnet address)
import json import json
import base58
from cryptoconditions import ZenroomSha256 from hashlib import sha3_256
from json.decoder import JSONDecodeError from cryptoconditions.types.zenroom import ZenroomSha256
from planetmint_driver.crypto import generate_keypair
from .helper.hosts import Hosts
from zenroom import zencode_exec
import time
def test_zenroom(gen_key_zencode, secret_key_to_private_key_zencode, fulfill_script_zencode, def test_zenroom_signing(
condition_script_zencode, zenroom_data, zenroom_house_assets): gen_key_zencode,
alice = json.loads(ZenroomSha256.run_zenroom(gen_key_zencode).output)['keys'] secret_key_to_private_key_zencode,
bob = json.loads(ZenroomSha256.run_zenroom(gen_key_zencode).output)['keys'] fulfill_script_zencode,
zenroom_data,
zenroom_house_assets,
condition_script_zencode,
):
zen_public_keys = json.loads(ZenroomSha256.run_zenroom(secret_key_to_private_key_zencode.format('Alice'), biolabs = generate_keypair()
keys={'keys': alice}).output) version = "2.0"
zen_public_keys.update(json.loads(ZenroomSha256.run_zenroom(secret_key_to_private_key_zencode.format('Bob'),
keys={'keys': bob}).output))
# CRYPTO-CONDITIONS: instantiate an Ed25519 crypto-condition for buyer alice = json.loads(zencode_exec(gen_key_zencode).output)["keyring"]
zenSha = ZenroomSha256(script=fulfill_script_zencode, keys=zen_public_keys, data=zenroom_data) bob = json.loads(zencode_exec(gen_key_zencode).output)["keyring"]
zen_public_keys = json.loads(
ZenroomSha256.run_zenroom(
secret_key_to_private_key_zencode.format("Alice"),
keys=json.dumps({"keyring": alice}),
).output
)
zen_public_keys.update(
json.loads(
ZenroomSha256.run_zenroom(
secret_key_to_private_key_zencode.format("Bob"),
keys=json.dumps({"keyring": bob}),
).output
)
)
zenroomscpt = ZenroomSha256(
script=fulfill_script_zencode, data=zenroom_data, keys=zen_public_keys
)
print(f"zenroom is: {zenroomscpt.script}")
# CRYPTO-CONDITIONS: generate the condition uri # CRYPTO-CONDITIONS: generate the condition uri
condition_uri = zenSha.condition.serialize_uri() condition_uri_zen = zenroomscpt.condition.serialize_uri()
print(f"\nzenroom condition URI: {condition_uri_zen}")
# CRYPTO-CONDITIONS: construct an unsigned fulfillment dictionary # CRYPTO-CONDITIONS: construct an unsigned fulfillment dictionary
unsigned_fulfillment_dict = { unsigned_fulfillment_dict_zen = {
'type': zenSha.TYPE_NAME, "type": zenroomscpt.TYPE_NAME,
'script': fulfill_script_zencode, "public_key": base58.b58encode(biolabs.public_key).decode(),
'keys': zen_public_keys,
} }
output = { output = {
'amount': '1000', "amount": "10",
'condition': { "condition": {
'details': unsigned_fulfillment_dict, "details": unsigned_fulfillment_dict_zen,
'uri': condition_uri, "uri": condition_uri_zen,
}, },
'data': zenroom_data, "public_keys": [
'script': fulfill_script_zencode, biolabs.public_key,
'conf': '', ],
'public_keys': (zen_public_keys['Alice']['ecdh_public_key'], ),
} }
input_ = { input_ = {
'fulfillment': None, "fulfillment": None,
'fulfills': None, "fulfills": None,
'owners_before': (zen_public_keys['Alice']['ecdh_public_key'], ), "owners_before": [
biolabs.public_key,
],
} }
metadata = {"result": {"output": ["ok"]}}
token_creation_tx = { token_creation_tx = {
'operation': 'CREATE', "operation": "CREATE",
'asset': zenroom_house_assets, "asset": zenroom_house_assets,
'metadata': None, "metadata": metadata,
'outputs': (output,), "outputs": [
'inputs': (input_,), output,
'version': '2.0', ],
'id': None, "inputs": [
input_,
],
"version": version,
"id": None,
} }
# JSON: serialize the transaction-without-id to a json formatted string # JSON: serialize the transaction-without-id to a json formatted string
message = json.dumps( message = json.dumps(
token_creation_tx, token_creation_tx,
sort_keys=True, sort_keys=True,
separators=(',', ':'), separators=(",", ":"),
ensure_ascii=False, ensure_ascii=False,
) )
try: # major workflow:
assert(not zenSha.validate(message=message)) # we store the fulfill script in the transaction/message (zenroom-sha)
except JSONDecodeError: # the condition script is used to fulfill the transaction and create the signature
pass #
except ValueError: # the server should ick the fulfill script and recreate the zenroom-sha and verify the signature
pass
message = zenSha.sign(message, condition_script_zencode, alice) message = zenroomscpt.sign(message, condition_script_zencode, alice)
assert(zenSha.validate(message=message)) assert zenroomscpt.validate(message=message)
message = json.loads(message)
fulfillment_uri_zen = zenroomscpt.serialize_uri()
message["inputs"][0]["fulfillment"] = fulfillment_uri_zen
tx = message
tx["id"] = None
json_str_tx = json.dumps(tx, sort_keys=True, skipkeys=False, separators=(",", ":"))
# SHA3: hash the serialized id-less transaction to generate the id
shared_creation_txid = sha3_256(json_str_tx.encode()).hexdigest()
message["id"] = shared_creation_txid
hosts = Hosts("/shared/hostnames")
pm_alpha = hosts.get_connection()
sent_transfer_tx = pm_alpha.transactions.send_commit(message)
time.sleep(1)
# Assert that transaction is stored on both planetmint nodes
hosts.assert_transaction(shared_creation_txid)
print(f"\n\nstatus and result : + {sent_transfer_tx}")

View File

@ -36,26 +36,29 @@ BANNER = """
def start(args): def start(args):
# Exchange object for event stream api # Exchange object for event stream api
logger.info('Starting Planetmint') logger.info("Starting Planetmint")
exchange = Exchange() exchange = Exchange()
# start the web api # start the web api
app_server = server.create_server( app_server = server.create_server(
settings=planetmint.config['server'], settings=planetmint.config["server"],
log_config=planetmint.config['log'], log_config=planetmint.config["log"],
planetmint_factory=Planetmint) planetmint_factory=Planetmint,
p_webapi = Process(name='planetmint_webapi', target=app_server.run, daemon=True) )
p_webapi = Process(name="planetmint_webapi", target=app_server.run, daemon=True)
p_webapi.start() p_webapi.start()
logger.info(BANNER.format(planetmint.config['server']['bind'])) logger.info(BANNER.format(planetmint.config["server"]["bind"]))
# start websocket server # start websocket server
p_websocket_server = Process(name='planetmint_ws', p_websocket_server = Process(
name="planetmint_ws",
target=websocket_server.start, target=websocket_server.start,
daemon=True, daemon=True,
args=(exchange.get_subscriber_queue(EventTypes.BLOCK_VALID),)) args=(exchange.get_subscriber_queue(EventTypes.BLOCK_VALID),),
)
p_websocket_server.start() p_websocket_server.start()
p_exchange = Process(name='planetmint_exchange', target=exchange.run, daemon=True) p_exchange = Process(name="planetmint_exchange", target=exchange.run, daemon=True)
p_exchange.start() p_exchange.start()
# We need to import this after spawning the web server # We need to import this after spawning the web server
@ -63,7 +66,7 @@ def start(args):
# for gevent. # for gevent.
from abci.server import ABCIServer from abci.server import ABCIServer
setproctitle.setproctitle('planetmint') setproctitle.setproctitle("planetmint")
# Start the ABCIServer # Start the ABCIServer
# abci = ABCI(TmVersion(planetmint.config['tendermint']['version'])) # abci = ABCI(TmVersion(planetmint.config['tendermint']['version']))
@ -82,5 +85,5 @@ def start(args):
app.run() app.run()
if __name__ == '__main__': if __name__ == "__main__":
start() start()

View File

@ -6,7 +6,8 @@
from functools import reduce from functools import reduce
import base58 import base58
from cryptoconditions import Fulfillment, ThresholdSha256, Ed25519Sha256 from cryptoconditions import ThresholdSha256, Ed25519Sha256, ZenroomSha256
from cryptoconditions import Fulfillment
from planetmint.transactions.common.exceptions import AmountError from planetmint.transactions.common.exceptions import AmountError
from .utils import _fulfillment_to_details, _fulfillment_from_details from .utils import _fulfillment_to_details, _fulfillment_from_details
@ -24,7 +25,7 @@ class Output(object):
owners before a Transaction was confirmed. owners before a Transaction was confirmed.
""" """
MAX_AMOUNT = 9 * 10 ** 18 MAX_AMOUNT = 9 * 10**18
def __init__(self, fulfillment, public_keys=None, amount=1): def __init__(self, fulfillment, public_keys=None, amount=1):
"""Create an instance of a :class:`~.Output`. """Create an instance of a :class:`~.Output`.
@ -41,13 +42,13 @@ class Output(object):
TypeError: if `public_keys` is not instance of `list`. TypeError: if `public_keys` is not instance of `list`.
""" """
if not isinstance(public_keys, list) and public_keys is not None: if not isinstance(public_keys, list) and public_keys is not None:
raise TypeError('`public_keys` must be a list instance or None') raise TypeError("`public_keys` must be a list instance or None")
if not isinstance(amount, int): if not isinstance(amount, int):
raise TypeError('`amount` must be an int') raise TypeError("`amount` must be an int")
if amount < 1: if amount < 1:
raise AmountError('`amount` must be greater than 0') raise AmountError("`amount` must be greater than 0")
if amount > self.MAX_AMOUNT: if amount > self.MAX_AMOUNT:
raise AmountError('`amount` must be <= %s' % self.MAX_AMOUNT) raise AmountError("`amount` must be <= %s" % self.MAX_AMOUNT)
self.fulfillment = fulfillment self.fulfillment = fulfillment
self.amount = amount self.amount = amount
@ -71,19 +72,20 @@ class Output(object):
# and fulfillment! # and fulfillment!
condition = {} condition = {}
try: try:
condition['details'] = _fulfillment_to_details(self.fulfillment) # TODO verify if a script is returned in case of zenroom fulfillments
condition["details"] = _fulfillment_to_details(self.fulfillment)
except AttributeError: except AttributeError:
pass pass
try: try:
condition['uri'] = self.fulfillment.condition_uri condition["uri"] = self.fulfillment.condition_uri
except AttributeError: except AttributeError:
condition['uri'] = self.fulfillment condition["uri"] = self.fulfillment
output = { output = {
'public_keys': self.public_keys, "public_keys": self.public_keys,
'condition': condition, "condition": condition,
'amount': str(self.amount), "amount": str(self.amount),
} }
return output return output
@ -113,25 +115,24 @@ class Output(object):
""" """
threshold = len(public_keys) threshold = len(public_keys)
if not isinstance(amount, int): if not isinstance(amount, int):
raise TypeError('`amount` must be a int') raise TypeError("`amount` must be a int")
if amount < 1: if amount < 1:
raise AmountError('`amount` needs to be greater than zero') raise AmountError("`amount` needs to be greater than zero")
if not isinstance(public_keys, list): if not isinstance(public_keys, list):
raise TypeError('`public_keys` must be an instance of list') raise TypeError("`public_keys` must be an instance of list")
if len(public_keys) == 0: if len(public_keys) == 0:
raise ValueError('`public_keys` needs to contain at least one' raise ValueError("`public_keys` needs to contain at least one" "owner")
'owner')
elif len(public_keys) == 1 and not isinstance(public_keys[0], list): elif len(public_keys) == 1 and not isinstance(public_keys[0], list):
if isinstance(public_keys[0], Fulfillment): if isinstance(public_keys[0], Fulfillment):
ffill = public_keys[0] ffill = public_keys[0]
elif isinstance(public_keys[0], ZenroomSha256):
ffill = ZenroomSha256(public_key=base58.b58decode(public_keys[0]))
else: else:
ffill = Ed25519Sha256( ffill = Ed25519Sha256(public_key=base58.b58decode(public_keys[0]))
public_key=base58.b58decode(public_keys[0]))
return cls(ffill, public_keys, amount=amount) return cls(ffill, public_keys, amount=amount)
else: else:
initial_cond = ThresholdSha256(threshold=threshold) initial_cond = ThresholdSha256(threshold=threshold)
threshold_cond = reduce(cls._gen_condition, public_keys, threshold_cond = reduce(cls._gen_condition, public_keys, initial_cond)
initial_cond)
return cls(threshold_cond, public_keys, amount=amount) return cls(threshold_cond, public_keys, amount=amount)
@classmethod @classmethod
@ -161,7 +162,7 @@ class Output(object):
ffill = ThresholdSha256(threshold=threshold) ffill = ThresholdSha256(threshold=threshold)
reduce(cls._gen_condition, new_public_keys, ffill) reduce(cls._gen_condition, new_public_keys, ffill)
elif isinstance(new_public_keys, list) and len(new_public_keys) <= 1: elif isinstance(new_public_keys, list) and len(new_public_keys) <= 1:
raise ValueError('Sublist cannot contain single owner') raise ValueError("Sublist cannot contain single owner")
else: else:
try: try:
new_public_keys = new_public_keys.pop() new_public_keys = new_public_keys.pop()
@ -176,8 +177,7 @@ class Output(object):
if isinstance(new_public_keys, Fulfillment): if isinstance(new_public_keys, Fulfillment):
ffill = new_public_keys ffill = new_public_keys
else: else:
ffill = Ed25519Sha256( ffill = Ed25519Sha256(public_key=base58.b58decode(new_public_keys))
public_key=base58.b58decode(new_public_keys))
initial.add_subfulfillment(ffill) initial.add_subfulfillment(ffill)
return initial return initial
@ -198,12 +198,12 @@ class Output(object):
:class:`~planetmint.transactions.common.transaction.Output` :class:`~planetmint.transactions.common.transaction.Output`
""" """
try: try:
fulfillment = _fulfillment_from_details(data['condition']['details']) fulfillment = _fulfillment_from_details(data["condition"]["details"])
except KeyError: except KeyError:
# NOTE: Hashlock condition case # NOTE: Hashlock condition case
fulfillment = data['condition']['uri'] fulfillment = data["condition"]["uri"]
try: try:
amount = int(data['amount']) amount = int(data["amount"])
except ValueError: except ValueError:
raise AmountError('Invalid amount: %s' % data['amount']) raise AmountError("Invalid amount: %s" % data["amount"])
return cls(fulfillment, data['public_keys'], amount) return cls(fulfillment, data["public_keys"], amount)

View File

@ -100,8 +100,8 @@ definitions:
uri: uri:
type: string type: string
pattern: "^ni:///sha-256;([a-zA-Z0-9_-]{0,86})[?]\ pattern: "^ni:///sha-256;([a-zA-Z0-9_-]{0,86})[?]\
(fpt=(ed25519|threshold)-sha-256(&)?|cost=[0-9]+(&)?|\ (fpt=(ed25519|threshold|zenroom)-sha-256(&)?|cost=[0-9]+(&)?|\
subtypes=ed25519-sha-256(&)?){2,3}$" subtypes=(ed25519|zenroom)-sha-256(&)?){2,3}$"
public_keys: public_keys:
"$ref": "#/definitions/public_keys" "$ref": "#/definitions/public_keys"
input: input:
@ -147,7 +147,7 @@ definitions:
properties: properties:
type: type:
type: string type: string
pattern: "^ed25519-sha-256$" pattern: "^(ed25519|zenroom)-sha-256$"
public_key: public_key:
"$ref": "#/definitions/base58" "$ref": "#/definitions/base58"
- type: object - type: object

View File

@ -17,9 +17,9 @@ from functools import lru_cache
import rapidjson import rapidjson
import base58 import base58
from cryptoconditions import Fulfillment, ThresholdSha256, Ed25519Sha256 from cryptoconditions import Fulfillment, ThresholdSha256, Ed25519Sha256, ZenroomSha256
from cryptoconditions.exceptions import ( from cryptoconditions.exceptions import ParsingError, ASN1DecodeError, ASN1EncodeError
ParsingError, ASN1DecodeError, ASN1EncodeError)
try: try:
from hashlib import sha3_256 from hashlib import sha3_256
except ImportError: except ImportError:
@ -27,8 +27,14 @@ except ImportError:
from planetmint.transactions.common.crypto import PrivateKey, hash_data from planetmint.transactions.common.crypto import PrivateKey, hash_data
from planetmint.transactions.common.exceptions import ( from planetmint.transactions.common.exceptions import (
KeypairMismatchException, InputDoesNotExist, DoubleSpend, KeypairMismatchException,
InvalidHash, InvalidSignature, AmountError, AssetIdMismatch) InputDoesNotExist,
DoubleSpend,
InvalidHash,
InvalidSignature,
AmountError,
AssetIdMismatch,
)
from planetmint.transactions.common.utils import serialize from planetmint.transactions.common.utils import serialize
from .memoize import memoize_from_dict, memoize_to_dict from .memoize import memoize_from_dict, memoize_to_dict
from .input import Input from .input import Input
@ -36,15 +42,16 @@ from .output import Output
from .transaction_link import TransactionLink from .transaction_link import TransactionLink
UnspentOutput = namedtuple( UnspentOutput = namedtuple(
'UnspentOutput', ( "UnspentOutput",
(
# TODO 'utxo_hash': sha3_256(f'{txid}{output_index}'.encode()) # TODO 'utxo_hash': sha3_256(f'{txid}{output_index}'.encode())
# 'utxo_hash', # noqa # 'utxo_hash', # noqa
'transaction_id', "transaction_id",
'output_index', "output_index",
'amount', "amount",
'asset_id', "asset_id",
'condition_uri', "condition_uri",
) ),
) )
@ -71,13 +78,22 @@ class Transaction(object):
version (string): Defines the version number of a Transaction. version (string): Defines the version number of a Transaction.
""" """
CREATE = 'CREATE' CREATE = "CREATE"
TRANSFER = 'TRANSFER' TRANSFER = "TRANSFER"
ALLOWED_OPERATIONS = (CREATE, TRANSFER) ALLOWED_OPERATIONS = (CREATE, TRANSFER)
VERSION = '2.0' VERSION = "2.0"
def __init__(self, operation, asset, inputs=None, outputs=None, def __init__(
metadata=None, version=None, hash_id=None, tx_dict=None): self,
operation,
asset,
inputs=None,
outputs=None,
metadata=None,
version=None,
hash_id=None,
tx_dict=None,
):
"""The constructor allows to create a customizable Transaction. """The constructor allows to create a customizable Transaction.
Note: Note:
@ -98,30 +114,41 @@ class Transaction(object):
hash_id (string): Hash id of the transaction. hash_id (string): Hash id of the transaction.
""" """
if operation not in self.ALLOWED_OPERATIONS: if operation not in self.ALLOWED_OPERATIONS:
allowed_ops = ', '.join(self.__class__.ALLOWED_OPERATIONS) allowed_ops = ", ".join(self.__class__.ALLOWED_OPERATIONS)
raise ValueError('`operation` must be one of {}' raise ValueError("`operation` must be one of {}".format(allowed_ops))
.format(allowed_ops))
# Asset payloads for 'CREATE' operations must be None or # Asset payloads for 'CREATE' operations must be None or
# dicts holding a `data` property. Asset payloads for 'TRANSFER' # dicts holding a `data` property. Asset payloads for 'TRANSFER'
# operations must be dicts holding an `id` property. # operations must be dicts holding an `id` property.
if (operation == self.CREATE and if (
asset is not None and not (isinstance(asset, dict) and 'data' in asset)): operation == self.CREATE
raise TypeError(('`asset` must be None or a dict holding a `data` ' and asset is not None
" property instance for '{}' Transactions".format(operation))) and not (isinstance(asset, dict) and "data" in asset)
elif (operation == self.TRANSFER and ):
not (isinstance(asset, dict) and 'id' in asset)): raise TypeError(
raise TypeError(('`asset` must be a dict holding an `id` property ' (
'for \'TRANSFER\' Transactions')) "`asset` must be None or a dict holding a `data` "
" property instance for '{}' Transactions".format(operation)
)
)
elif operation == self.TRANSFER and not (
isinstance(asset, dict) and "id" in asset
):
raise TypeError(
(
"`asset` must be a dict holding an `id` property "
"for 'TRANSFER' Transactions"
)
)
if outputs and not isinstance(outputs, list): if outputs and not isinstance(outputs, list):
raise TypeError('`outputs` must be a list instance or None') raise TypeError("`outputs` must be a list instance or None")
if inputs and not isinstance(inputs, list): if inputs and not isinstance(inputs, list):
raise TypeError('`inputs` must be a list instance or None') raise TypeError("`inputs` must be a list instance or None")
if metadata is not None and not isinstance(metadata, dict): if metadata is not None and not isinstance(metadata, dict):
raise TypeError('`metadata` must be a dict or None') raise TypeError("`metadata` must be a dict or None")
self.version = version if version is not None else self.VERSION self.version = version if version is not None else self.VERSION
self.operation = operation self.operation = operation
@ -141,14 +168,17 @@ class Transaction(object):
if self.operation == self.CREATE: if self.operation == self.CREATE:
self._asset_id = self._id self._asset_id = self._id
elif self.operation == self.TRANSFER: elif self.operation == self.TRANSFER:
self._asset_id = self.asset['id'] self._asset_id = self.asset["id"]
return (UnspentOutput( return (
UnspentOutput(
transaction_id=self._id, transaction_id=self._id,
output_index=output_index, output_index=output_index,
amount=output.amount, amount=output.amount,
asset_id=self._asset_id, asset_id=self._asset_id,
condition_uri=output.fulfillment.condition_uri, condition_uri=output.fulfillment.condition_uri,
) for output_index, output in enumerate(self.outputs)) )
for output_index, output in enumerate(self.outputs)
)
@property @property
def spent_outputs(self): def spent_outputs(self):
@ -156,10 +186,7 @@ class Transaction(object):
is represented as a dictionary containing a transaction id and is represented as a dictionary containing a transaction id and
output index. output index.
""" """
return ( return (input_.fulfills.to_dict() for input_ in self.inputs if input_.fulfills)
input_.fulfills.to_dict()
for input_ in self.inputs if input_.fulfills
)
@property @property
def serialized(self): def serialized(self):
@ -199,9 +226,11 @@ class Transaction(object):
# as inputs. # as inputs.
indices = indices or range(len(self.outputs)) indices = indices or range(len(self.outputs))
return [ return [
Input(self.outputs[idx].fulfillment, Input(
self.outputs[idx].fulfillment,
self.outputs[idx].public_keys, self.outputs[idx].public_keys,
TransactionLink(self.id, idx)) TransactionLink(self.id, idx),
)
for idx in indices for idx in indices
] ]
@ -213,7 +242,7 @@ class Transaction(object):
Input`): An Input to be added to the Transaction. Input`): An Input to be added to the Transaction.
""" """
if not isinstance(input_, Input): if not isinstance(input_, Input):
raise TypeError('`input_` must be a Input instance') raise TypeError("`input_` must be a Input instance")
self.inputs.append(input_) self.inputs.append(input_)
def add_output(self, output): def add_output(self, output):
@ -225,7 +254,7 @@ class Transaction(object):
Transaction. Transaction.
""" """
if not isinstance(output, Output): if not isinstance(output, Output):
raise TypeError('`output` must be an Output instance or None') raise TypeError("`output` must be an Output instance or None")
self.outputs.append(output) self.outputs.append(output)
def sign(self, private_keys): def sign(self, private_keys):
@ -236,6 +265,7 @@ class Transaction(object):
currently: currently:
- Ed25519Fulfillment - Ed25519Fulfillment
- ThresholdSha256 - ThresholdSha256
- ZenroomSha256
Furthermore, note that all keys required to fully sign the Furthermore, note that all keys required to fully sign the
Transaction have to be passed to this method. A subset of all Transaction have to be passed to this method. A subset of all
will cause this method to fail. will cause this method to fail.
@ -251,7 +281,7 @@ class Transaction(object):
# TODO: Singing should be possible with at least one of all private # TODO: Singing should be possible with at least one of all private
# keys supplied to this method. # keys supplied to this method.
if private_keys is None or not isinstance(private_keys, list): if private_keys is None or not isinstance(private_keys, list):
raise TypeError('`private_keys` must be a list instance') raise TypeError("`private_keys` must be a list instance")
# NOTE: Generate public keys from private keys and match them in a # NOTE: Generate public keys from private keys and match them in a
# dictionary: # dictionary:
@ -268,8 +298,10 @@ class Transaction(object):
# to decode to convert the bytestring into a python str # to decode to convert the bytestring into a python str
return public_key.decode() return public_key.decode()
key_pairs = {gen_public_key(PrivateKey(private_key)): key_pairs = {
PrivateKey(private_key) for private_key in private_keys} gen_public_key(PrivateKey(private_key)): PrivateKey(private_key)
for private_key in private_keys
}
tx_dict = self.to_dict() tx_dict = self.to_dict()
tx_dict = Transaction._remove_signatures(tx_dict) tx_dict = Transaction._remove_signatures(tx_dict)
@ -290,7 +322,7 @@ class Transaction(object):
currently: currently:
- Ed25519Fulfillment - Ed25519Fulfillment
- ThresholdSha256. - ThresholdSha256.
- ZenroomSha256
Args: Args:
input_ (:class:`~planetmint.transactions.common.transaction. input_ (:class:`~planetmint.transactions.common.transaction.
Input`) The Input to be signed. Input`) The Input to be signed.
@ -298,15 +330,51 @@ class Transaction(object):
key_pairs (dict): The keys to sign the Transaction with. key_pairs (dict): The keys to sign the Transaction with.
""" """
if isinstance(input_.fulfillment, Ed25519Sha256): if isinstance(input_.fulfillment, Ed25519Sha256):
return cls._sign_simple_signature_fulfillment(input_, message, return cls._sign_simple_signature_fulfillment(input_, message, key_pairs)
key_pairs)
elif isinstance(input_.fulfillment, ThresholdSha256): elif isinstance(input_.fulfillment, ThresholdSha256):
return cls._sign_threshold_signature_fulfillment(input_, message, return cls._sign_threshold_signature_fulfillment(input_, message, key_pairs)
key_pairs) elif isinstance(input_.fulfillment, ZenroomSha256):
return cls._sign_threshold_signature_fulfillment(input_, message, key_pairs)
else: else:
raise ValueError( raise ValueError(
'Fulfillment couldn\'t be matched to ' "Fulfillment couldn't be matched to "
'Cryptocondition fulfillment type.') "Cryptocondition fulfillment type."
)
@classmethod
def _sign_zenroom_fulfillment(cls, input_, message, key_pairs):
"""Signs a Zenroomful.
Args:
input_ (:class:`~planetmint.transactions.common.transaction.
Input`) The input to be signed.
message (str): The message to be signed
key_pairs (dict): The keys to sign the Transaction with.
"""
# NOTE: To eliminate the dangers of accidentally signing a condition by
# reference, we remove the reference of input_ here
# intentionally. If the user of this class knows how to use it,
# this should never happen, but then again, never say never.
input_ = deepcopy(input_)
public_key = input_.owners_before[0]
message = sha3_256(message.encode())
if input_.fulfills:
message.update(
"{}{}".format(input_.fulfills.txid, input_.fulfills.output).encode()
)
try:
# cryptoconditions makes no assumptions of the encoding of the
# message to sign or verify. It only accepts bytestrings
input_.fulfillment.sign(
message.digest(), base58.b58decode(key_pairs[public_key].encode())
)
except KeyError:
raise KeypairMismatchException(
"Public key {} is not a pair to "
"any of the private keys".format(public_key)
)
return input_
@classmethod @classmethod
def _sign_simple_signature_fulfillment(cls, input_, message, key_pairs): def _sign_simple_signature_fulfillment(cls, input_, message, key_pairs):
@ -326,18 +394,21 @@ class Transaction(object):
public_key = input_.owners_before[0] public_key = input_.owners_before[0]
message = sha3_256(message.encode()) message = sha3_256(message.encode())
if input_.fulfills: if input_.fulfills:
message.update('{}{}'.format( message.update(
input_.fulfills.txid, input_.fulfills.output).encode()) "{}{}".format(input_.fulfills.txid, input_.fulfills.output).encode()
)
try: try:
# cryptoconditions makes no assumptions of the encoding of the # cryptoconditions makes no assumptions of the encoding of the
# message to sign or verify. It only accepts bytestrings # message to sign or verify. It only accepts bytestrings
input_.fulfillment.sign( input_.fulfillment.sign(
message.digest(), base58.b58decode(key_pairs[public_key].encode())) message.digest(), base58.b58decode(key_pairs[public_key].encode())
)
except KeyError: except KeyError:
raise KeypairMismatchException('Public key {} is not a pair to ' raise KeypairMismatchException(
'any of the private keys' "Public key {} is not a pair to "
.format(public_key)) "any of the private keys".format(public_key)
)
return input_ return input_
@classmethod @classmethod
@ -353,8 +424,9 @@ class Transaction(object):
input_ = deepcopy(input_) input_ = deepcopy(input_)
message = sha3_256(message.encode()) message = sha3_256(message.encode())
if input_.fulfills: if input_.fulfills:
message.update('{}{}'.format( message.update(
input_.fulfills.txid, input_.fulfills.output).encode()) "{}{}".format(input_.fulfills.txid, input_.fulfills.output).encode()
)
for owner_before in set(input_.owners_before): for owner_before in set(input_.owners_before):
# TODO: CC should throw a KeypairMismatchException, instead of # TODO: CC should throw a KeypairMismatchException, instead of
@ -367,24 +439,24 @@ class Transaction(object):
# TODO FOR CC: `get_subcondition` is singular. One would not # TODO FOR CC: `get_subcondition` is singular. One would not
# expect to get a list back. # expect to get a list back.
ccffill = input_.fulfillment ccffill = input_.fulfillment
subffills = ccffill.get_subcondition_from_vk( subffills = ccffill.get_subcondition_from_vk(base58.b58decode(owner_before))
base58.b58decode(owner_before))
if not subffills: if not subffills:
raise KeypairMismatchException('Public key {} cannot be found ' raise KeypairMismatchException(
'in the fulfillment' "Public key {} cannot be found "
.format(owner_before)) "in the fulfillment".format(owner_before)
)
try: try:
private_key = key_pairs[owner_before] private_key = key_pairs[owner_before]
except KeyError: except KeyError:
raise KeypairMismatchException('Public key {} is not a pair ' raise KeypairMismatchException(
'to any of the private keys' "Public key {} is not a pair "
.format(owner_before)) "to any of the private keys".format(owner_before)
)
# cryptoconditions makes no assumptions of the encoding of the # cryptoconditions makes no assumptions of the encoding of the
# message to sign or verify. It only accepts bytestrings # message to sign or verify. It only accepts bytestrings
for subffill in subffills: for subffill in subffills:
subffill.sign( subffill.sign(message.digest(), base58.b58decode(private_key.encode()))
message.digest(), base58.b58decode(private_key.encode()))
return input_ return input_
def inputs_valid(self, outputs=None): def inputs_valid(self, outputs=None):
@ -409,15 +481,14 @@ class Transaction(object):
# to check for outputs, we're just submitting dummy # to check for outputs, we're just submitting dummy
# values to the actual method. This simplifies it's logic # values to the actual method. This simplifies it's logic
# greatly, as we do not have to check against `None` values. # greatly, as we do not have to check against `None` values.
return self._inputs_valid(['dummyvalue' return self._inputs_valid(["dummyvalue" for _ in self.inputs])
for _ in self.inputs])
elif self.operation == self.TRANSFER: elif self.operation == self.TRANSFER:
return self._inputs_valid([output.fulfillment.condition_uri return self._inputs_valid(
for output in outputs]) [output.fulfillment.condition_uri for output in outputs]
)
else: else:
allowed_ops = ', '.join(self.__class__.ALLOWED_OPERATIONS) allowed_ops = ", ".join(self.__class__.ALLOWED_OPERATIONS)
raise TypeError('`operation` must be one of {}' raise TypeError("`operation` must be one of {}".format(allowed_ops))
.format(allowed_ops))
def _inputs_valid(self, output_condition_uris): def _inputs_valid(self, output_condition_uris):
"""Validates an Input against a given set of Outputs. """Validates an Input against a given set of Outputs.
@ -435,21 +506,22 @@ class Transaction(object):
""" """
if len(self.inputs) != len(output_condition_uris): if len(self.inputs) != len(output_condition_uris):
raise ValueError('Inputs and ' raise ValueError(
'output_condition_uris must have the same count') "Inputs and " "output_condition_uris must have the same count"
)
tx_dict = self.tx_dict if self.tx_dict else self.to_dict() tx_dict = self.tx_dict if self.tx_dict else self.to_dict()
tx_dict = Transaction._remove_signatures(tx_dict) tx_dict = Transaction._remove_signatures(tx_dict)
tx_dict['id'] = None tx_dict["id"] = None
tx_serialized = Transaction._to_str(tx_dict) tx_serialized = Transaction._to_str(tx_dict)
def validate(i, output_condition_uri=None): def validate(i, output_condition_uri=None):
"""Validate input against output condition URI""" """Validate input against output condition URI"""
return self._input_valid(self.inputs[i], self.operation, return self._input_valid(
tx_serialized, output_condition_uri) self.inputs[i], self.operation, tx_serialized, output_condition_uri
)
return all(validate(i, cond) return all(validate(i, cond) for i, cond in enumerate(output_condition_uris))
for i, cond in enumerate(output_condition_uris))
@lru_cache(maxsize=16384) @lru_cache(maxsize=16384)
def _input_valid(self, input_, operation, message, output_condition_uri=None): def _input_valid(self, input_, operation, message, output_condition_uri=None):
@ -473,8 +545,20 @@ class Transaction(object):
ccffill = input_.fulfillment ccffill = input_.fulfillment
try: try:
parsed_ffill = Fulfillment.from_uri(ccffill.serialize_uri()) parsed_ffill = Fulfillment.from_uri(ccffill.serialize_uri())
except (TypeError, ValueError, except TypeError as e:
ParsingError, ASN1DecodeError, ASN1EncodeError): print(f"Exception TypeError : {e}")
return False
except ValueError as e:
print(f"Exception ValueError : {e}")
return False
except ParsingError as e:
print(f"Exception ParsingError : {e}")
return False
except ASN1DecodeError as e:
print(f"Exception ASN1DecodeError : {e}")
return False
except ASN1EncodeError as e:
print(f"Exception ASN1EncodeError : {e}")
return False return False
if operation == self.CREATE: if operation == self.CREATE:
@ -484,10 +568,15 @@ class Transaction(object):
else: else:
output_valid = output_condition_uri == ccffill.condition_uri output_valid = output_condition_uri == ccffill.condition_uri
ffill_valid = False
if isinstance(parsed_ffill, ZenroomSha256):
ffill_valid = parsed_ffill.validate(message=message)
else:
message = sha3_256(message.encode()) message = sha3_256(message.encode())
if input_.fulfills: if input_.fulfills:
message.update('{}{}'.format( message.update(
input_.fulfills.txid, input_.fulfills.output).encode()) "{}{}".format(input_.fulfills.txid, input_.fulfills.output).encode()
)
# NOTE: We pass a timestamp to `.validate`, as in case of a timeout # NOTE: We pass a timestamp to `.validate`, as in case of a timeout
# condition we'll have to validate against it # condition we'll have to validate against it
@ -509,13 +598,13 @@ class Transaction(object):
dict: The Transaction as an alternative serialization format. dict: The Transaction as an alternative serialization format.
""" """
return { return {
'inputs': [input_.to_dict() for input_ in self.inputs], "inputs": [input_.to_dict() for input_ in self.inputs],
'outputs': [output.to_dict() for output in self.outputs], "outputs": [output.to_dict() for output in self.outputs],
'operation': str(self.operation), "operation": str(self.operation),
'metadata': self.metadata, "metadata": self.metadata,
'asset': self.asset, "asset": self.asset,
'version': self.version, "version": self.version,
'id': self._id, "id": self._id,
} }
@staticmethod @staticmethod
@ -533,12 +622,12 @@ class Transaction(object):
# NOTE: We remove the reference since we need `tx_dict` only for the # NOTE: We remove the reference since we need `tx_dict` only for the
# transaction's hash # transaction's hash
tx_dict = deepcopy(tx_dict) tx_dict = deepcopy(tx_dict)
for input_ in tx_dict['inputs']: for input_ in tx_dict["inputs"]:
# NOTE: Not all Cryptoconditions return a `signature` key (e.g. # NOTE: Not all Cryptoconditions return a `signature` key (e.g.
# ThresholdSha256), so setting it to `None` in any # ThresholdSha256), so setting it to `None` in any
# case could yield incorrect signatures. This is why we only # case could yield incorrect signatures. This is why we only
# set it to `None` if it's set in the dict. # set it to `None` if it's set in the dict.
input_['fulfillment'] = None input_["fulfillment"] = None
return tx_dict return tx_dict
@staticmethod @staticmethod
@ -550,7 +639,7 @@ class Transaction(object):
return self._id return self._id
def to_hash(self): def to_hash(self):
return self.to_dict()['id'] return self.to_dict()["id"]
@staticmethod @staticmethod
def _to_str(value): def _to_str(value):
@ -586,14 +675,19 @@ class Transaction(object):
transactions = [transactions] transactions = [transactions]
# create a set of the transactions' asset ids # create a set of the transactions' asset ids
asset_ids = {tx.id if tx.operation == tx.CREATE asset_ids = {
else tx.asset['id'] tx.id if tx.operation == tx.CREATE else tx.asset["id"]
for tx in transactions} for tx in transactions
}
# check that all the transasctions have the same asset id # check that all the transasctions have the same asset id
if len(asset_ids) > 1: if len(asset_ids) > 1:
raise AssetIdMismatch(('All inputs of all transactions passed' raise AssetIdMismatch(
' need to have the same asset id')) (
"All inputs of all transactions passed"
" need to have the same asset id"
)
)
return asset_ids.pop() return asset_ids.pop()
@staticmethod @staticmethod
@ -608,18 +702,20 @@ class Transaction(object):
tx_body = rapidjson.loads(rapidjson.dumps(tx_body)) tx_body = rapidjson.loads(rapidjson.dumps(tx_body))
try: try:
proposed_tx_id = tx_body['id'] proposed_tx_id = tx_body["id"]
except KeyError: except KeyError:
raise InvalidHash('No transaction id found!') raise InvalidHash("No transaction id found!")
tx_body['id'] = None tx_body["id"] = None
tx_body_serialized = Transaction._to_str(tx_body) tx_body_serialized = Transaction._to_str(tx_body)
valid_tx_id = Transaction._to_hash(tx_body_serialized) valid_tx_id = Transaction._to_hash(tx_body_serialized)
if proposed_tx_id != valid_tx_id: if proposed_tx_id != valid_tx_id:
err_msg = ("The transaction's id '{}' isn't equal to " err_msg = (
"the hash of its body, i.e. it's not valid.") "The transaction's id '{}' isn't equal to "
"the hash of its body, i.e. it's not valid."
)
raise InvalidHash(err_msg.format(proposed_tx_id)) raise InvalidHash(err_msg.format(proposed_tx_id))
@classmethod @classmethod
@ -633,17 +729,29 @@ class Transaction(object):
Returns: Returns:
:class:`~planetmint.transactions.common.transaction.Transaction` :class:`~planetmint.transactions.common.transaction.Transaction`
""" """
operation = tx.get('operation', Transaction.CREATE) if isinstance(tx, dict) else Transaction.CREATE operation = (
tx.get("operation", Transaction.CREATE)
if isinstance(tx, dict)
else Transaction.CREATE
)
cls = Transaction.resolve_class(operation) cls = Transaction.resolve_class(operation)
if not skip_schema_validation: if not skip_schema_validation:
cls.validate_id(tx) cls.validate_id(tx)
cls.validate_schema(tx) cls.validate_schema(tx)
inputs = [Input.from_dict(input_) for input_ in tx['inputs']] inputs = [Input.from_dict(input_) for input_ in tx["inputs"]]
outputs = [Output.from_dict(output) for output in tx['outputs']] outputs = [Output.from_dict(output) for output in tx["outputs"]]
return cls(tx['operation'], tx['asset'], inputs, outputs, return cls(
tx['metadata'], tx['version'], hash_id=tx['id'], tx_dict=tx) tx["operation"],
tx["asset"],
inputs,
outputs,
tx["metadata"],
tx["version"],
hash_id=tx["id"],
tx_dict=tx,
)
@classmethod @classmethod
def from_db(cls, planet, tx_dict_list): def from_db(cls, planet, tx_dict_list):
@ -669,22 +777,22 @@ class Transaction(object):
tx_map = {} tx_map = {}
tx_ids = [] tx_ids = []
for tx in tx_dict_list: for tx in tx_dict_list:
tx.update({'metadata': None}) tx.update({"metadata": None})
tx_map[tx['id']] = tx tx_map[tx["id"]] = tx
tx_ids.append(tx['id']) tx_ids.append(tx["id"])
assets = list(planet.get_assets(tx_ids)) assets = list(planet.get_assets(tx_ids))
for asset in assets: for asset in assets:
if asset is not None: if asset is not None:
tx = tx_map[asset['id']] tx = tx_map[asset["id"]]
del asset['id'] del asset["id"]
tx['asset'] = asset tx["asset"] = asset
tx_ids = list(tx_map.keys()) tx_ids = list(tx_map.keys())
metadata_list = list(planet.get_metadata(tx_ids)) metadata_list = list(planet.get_metadata(tx_ids))
for metadata in metadata_list: for metadata in metadata_list:
tx = tx_map[metadata['id']] tx = tx_map[metadata["id"]]
tx.update({'metadata': metadata.get('metadata')}) tx.update({"metadata": metadata.get("metadata")})
if return_list: if return_list:
tx_list = [] tx_list = []
@ -725,14 +833,13 @@ class Transaction(object):
input_tx = ctxn input_tx = ctxn
if input_tx is None: if input_tx is None:
raise InputDoesNotExist("input `{}` doesn't exist" raise InputDoesNotExist("input `{}` doesn't exist".format(input_txid))
.format(input_txid))
spent = planet.get_spent(input_txid, input_.fulfills.output, spent = planet.get_spent(
current_transactions) input_txid, input_.fulfills.output, current_transactions
)
if spent: if spent:
raise DoubleSpend('input `{}` was already spent' raise DoubleSpend("input `{}` was already spent".format(input_txid))
.format(input_txid))
output = input_tx.outputs[input_.fulfills.output] output = input_tx.outputs[input_.fulfills.output]
input_conditions.append(output) input_conditions.append(output)
@ -745,21 +852,32 @@ class Transaction(object):
# validate asset id # validate asset id
asset_id = self.get_asset_id(input_txs) asset_id = self.get_asset_id(input_txs)
if asset_id != self.asset['id']: if asset_id != self.asset["id"]:
raise AssetIdMismatch(('The asset id of the input does not' raise AssetIdMismatch(
' match the asset id of the' (
' transaction')) "The asset id of the input does not"
" match the asset id of the"
" transaction"
)
)
input_amount = sum([input_condition.amount for input_condition in input_conditions]) input_amount = sum(
output_amount = sum([output_condition.amount for output_condition in self.outputs]) [input_condition.amount for input_condition in input_conditions]
)
output_amount = sum(
[output_condition.amount for output_condition in self.outputs]
)
if output_amount != input_amount: if output_amount != input_amount:
raise AmountError(('The amount used in the inputs `{}`' raise AmountError(
' needs to be same as the amount used' (
' in the outputs `{}`') "The amount used in the inputs `{}`"
.format(input_amount, output_amount)) " needs to be same as the amount used"
" in the outputs `{}`"
).format(input_amount, output_amount)
)
if not self.inputs_valid(input_conditions): if not self.inputs_valid(input_conditions):
raise InvalidSignature('Transaction signature is invalid.') raise InvalidSignature("Transaction signature is invalid.")
return True return True

View File

@ -10,7 +10,7 @@ import rapidjson
import planetmint import planetmint
from planetmint.transactions.common.exceptions import ValidationError from planetmint.transactions.common.exceptions import ValidationError
from cryptoconditions import ThresholdSha256, Ed25519Sha256 from cryptoconditions import ThresholdSha256, Ed25519Sha256, ZenroomSha256
from planetmint.transactions.common.exceptions import ThresholdTooDeep from planetmint.transactions.common.exceptions import ThresholdTooDeep
from cryptoconditions.exceptions import UnsupportedTypeError from cryptoconditions.exceptions import UnsupportedTypeError
@ -43,8 +43,7 @@ def serialize(data):
str: JSON formatted string str: JSON formatted string
""" """
return rapidjson.dumps(data, skipkeys=False, ensure_ascii=False, return rapidjson.dumps(data, skipkeys=False, ensure_ascii=False, sort_keys=True)
sort_keys=True)
def deserialize(data): def deserialize(data):
@ -76,9 +75,9 @@ def validate_txn_obj(obj_name, obj, key, validation_fun):
Raises: Raises:
ValidationError: `validation_fun` will raise exception on failure ValidationError: `validation_fun` will raise exception on failure
""" """
backend = planetmint.config['database']['backend'] backend = planetmint.config["database"]["backend"]
if backend == 'localmongodb': if backend == "localmongodb":
data = obj.get(key, {}) data = obj.get(key, {})
if isinstance(data, dict): if isinstance(data, dict):
validate_all_keys_in_obj(obj_name, data, validation_fun) validate_all_keys_in_obj(obj_name, data, validation_fun)
@ -162,10 +161,12 @@ def validate_key(obj_name, key):
Raises: Raises:
ValidationError: will raise exception in case of regex match. ValidationError: will raise exception in case of regex match.
""" """
if re.search(r'^[$]|\.|\x00', key): if re.search(r"^[$]|\.|\x00", key):
error_str = ('Invalid key name "{}" in {} object. The ' error_str = (
'key name cannot contain characters ' 'Invalid key name "{}" in {} object. The '
'".", "$" or null characters').format(key, obj_name) "key name cannot contain characters "
'".", "$" or null characters'
).format(key, obj_name)
raise ValidationError(error_str) raise ValidationError(error_str)
@ -176,21 +177,26 @@ def _fulfillment_to_details(fulfillment):
fulfillment: Crypto-conditions Fulfillment object fulfillment: Crypto-conditions Fulfillment object
""" """
if fulfillment.type_name == 'ed25519-sha-256': if fulfillment.type_name == "ed25519-sha-256":
return { return {
'type': 'ed25519-sha-256', "type": "ed25519-sha-256",
'public_key': base58.b58encode(fulfillment.public_key).decode(), "public_key": base58.b58encode(fulfillment.public_key).decode(),
} }
if fulfillment.type_name == 'threshold-sha-256': if fulfillment.type_name == "threshold-sha-256":
subconditions = [ subconditions = [
_fulfillment_to_details(cond['body']) _fulfillment_to_details(cond["body"]) for cond in fulfillment.subconditions
for cond in fulfillment.subconditions
] ]
return { return {
'type': 'threshold-sha-256', "type": "threshold-sha-256",
'threshold': fulfillment.threshold, "threshold": fulfillment.threshold,
'subconditions': subconditions, "subconditions": subconditions,
}
if fulfillment.type_name == "zenroom-sha-256":
return {
"type": "zenroom-sha-256",
"public_key": base58.b58encode(fulfillment.public_key).decode(),
"script": base58.b58encode(fulfillment.script).decode(),
} }
raise UnsupportedTypeError(fulfillment.type_name) raise UnsupportedTypeError(fulfillment.type_name)
@ -205,15 +211,22 @@ def _fulfillment_from_details(data, _depth=0):
if _depth == 100: if _depth == 100:
raise ThresholdTooDeep() raise ThresholdTooDeep()
if data['type'] == 'ed25519-sha-256': if data["type"] == "ed25519-sha-256":
public_key = base58.b58decode(data['public_key']) public_key = base58.b58decode(data["public_key"])
return Ed25519Sha256(public_key=public_key) return Ed25519Sha256(public_key=public_key)
if data['type'] == 'threshold-sha-256': if data["type"] == "threshold-sha-256":
threshold = ThresholdSha256(data['threshold']) threshold = ThresholdSha256(data["threshold"])
for cond in data['subconditions']: for cond in data["subconditions"]:
cond = _fulfillment_from_details(cond, _depth + 1) cond = _fulfillment_from_details(cond, _depth + 1)
threshold.add_subfulfillment(cond) threshold.add_subfulfillment(cond)
return threshold return threshold
raise UnsupportedTypeError(data.get('type')) if data["type"] == "zenroom-sha-256":
public_key = base58.b58decode(data["public_key"])
script = base58.b58decode(data["script"])
# zenroom = ZenroomSha256(script=script, data=None, keys={public_key})
# TODO: assign to zenroom and evaluate the outcome
ZenroomSha256(script=script, data=None, keys={public_key})
raise UnsupportedTypeError(data.get("type"))

View File

@ -3,7 +3,7 @@
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
# Code is Apache-2.0 and docs are CC-BY-4.0 # Code is Apache-2.0 and docs are CC-BY-4.0
__version__ = '0.9.3' __version__ = '0.9.7'
__short_version__ = '0.9' __short_version__ = '0.9'
# Supported Tendermint versions # Supported Tendermint versions

209
setup.py
View File

@ -14,134 +14,167 @@ import sys
from setuptools import setup, find_packages from setuptools import setup, find_packages
if sys.version_info < (3, 9): if sys.version_info < (3, 9):
sys.exit('Please use Python version 3.9 or higher.') sys.exit("Please use Python version 3.9 or higher.")
with open('README.md') as readme_file: with open("README.md") as readme_file:
readme = readme_file.read() readme = readme_file.read()
# get the version # get the version
version = {} version = {}
with open('planetmint/version.py') as fp: with open("planetmint/version.py") as fp:
exec(fp.read(), version) exec(fp.read(), version)
def check_setuptools_features(): def check_setuptools_features():
"""Check if setuptools is up to date.""" """Check if setuptools is up to date."""
import pkg_resources import pkg_resources
try: try:
list(pkg_resources.parse_requirements('foo~=1.0')) list(pkg_resources.parse_requirements("foo~=1.0"))
except ValueError: except ValueError:
sys.exit('Your Python distribution comes with an incompatible version ' sys.exit(
'of `setuptools`. Please run:\n' "Your Python distribution comes with an incompatible version "
' $ pip3 install --upgrade setuptools\n' "of `setuptools`. Please run:\n"
'and then run this command again') " $ pip3 install --upgrade setuptools\n"
"and then run this command again"
)
import pathlib import pathlib
import pkg_resources import pkg_resources
with pathlib.Path('docs/root/requirements.txt').open() as requirements_txt: docs_require = [
docs_require= [ "aafigure==0.6",
str(requirement) "alabaster==0.7.12",
for requirement "Babel==2.10.1",
in pkg_resources.parse_requirements(requirements_txt) "certifi==2021.10.8",
] "charset-normalizer==2.0.12",
"commonmark==0.9.1",
"docutils==0.17.1",
"idna",
"imagesize==1.3.0",
"importlib-metadata==4.11.3",
"Jinja2==3.0.0",
"markdown-it-py==2.1.0",
"MarkupSafe==2.1.1",
"mdit-py-plugins==0.3.0",
"mdurl==0.1.1",
"myst-parser==0.17.2",
"packaging==21.3",
"pockets==0.9.1",
"Pygments==2.12.0",
"pyparsing==3.0.8",
"pytz==2022.1",
"PyYAML>=5.4.0",
"requests>=2.25i.1",
"six==1.16.0",
"snowballstemmer==2.2.0",
"Sphinx==4.5.0",
"sphinx-rtd-theme==1.0.0",
"sphinxcontrib-applehelp==1.0.2",
"sphinxcontrib-devhelp==1.0.2",
"sphinxcontrib-htmlhelp==2.0.0",
"sphinxcontrib-httpdomain==1.8.0",
"sphinxcontrib-jsmath==1.0.1",
"sphinxcontrib-napoleon==0.7",
"sphinxcontrib-qthelp==1.0.3",
"sphinxcontrib-serializinghtml==1.1.5",
"urllib3==1.26.9",
"wget==3.2",
"zipp==3.8.0",
"nest-asyncio==1.5.5",
"sphinx-press-theme==0.8.0",
]
check_setuptools_features() check_setuptools_features()
dev_require = [ dev_require = ["ipdb", "ipython", "watchdog", "logging_tree", "pre-commit", "twine"]
'ipdb',
'ipython',
'watchdog',
'logging_tree',
'pre-commit',
'twine'
]
tests_require = [ tests_require = [
'coverage', "coverage",
'pep8', "pep8",
'flake8', "flake8",
'flake8-quotes==0.8.1', "flake8-quotes==0.8.1",
'hypothesis>=5.3.0', "hypothesis>=5.3.0",
'pytest>=3.0.0', "pytest>=3.0.0",
'pytest-cov==2.8.1', "pytest-cov==2.8.1",
'pytest-mock', "pytest-mock",
'pytest-xdist', "pytest-xdist",
'pytest-flask', "pytest-flask",
'pytest-aiohttp', "pytest-aiohttp",
'pytest-asyncio', "pytest-asyncio",
'tox', "tox",
] + docs_require ] + docs_require
install_requires = [ install_requires = [
'chardet==3.0.4', "chardet==3.0.4",
'aiohttp==3.8.1', "aiohttp==3.8.1",
'abci==0.8.3', "abci==0.8.3",
'planetmint-cryptoconditions>=0.9.4', "planetmint-cryptoconditions>=0.9.9",
'flask-cors==3.0.10', "flask-cors==3.0.10",
'flask-restful==0.3.9', "flask-restful==0.3.9",
'flask==2.0.1', "flask==2.0.1",
'gunicorn==20.1.0', "gunicorn==20.1.0",
'jsonschema==3.2.0', "jsonschema==3.2.0",
'logstats==0.3.0', "logstats==0.3.0",
'packaging>=20.9', "packaging>=20.9",
# TODO Consider not installing the db drivers, or putting them in extras. # TODO Consider not installing the db drivers, or putting them in extras.
'pymongo==3.11.4', "protobuf==3.20.1",
'python-rapidjson==1.0', "pymongo==3.11.4",
'pyyaml==5.4.1', "python-rapidjson==1.0",
'requests==2.25.1', "pyyaml==5.4.1",
'setproctitle==1.2.2', "requests>=2.25.1",
'werkzeug==2.0.3', "setproctitle==1.2.2",
'nest-asyncio==1.5.5', "werkzeug==2.0.3",
'protobuf==3.20.1' "nest-asyncio==1.5.5",
"protobuf==3.20.1",
] ]
if sys.version_info < (3, 6): if sys.version_info < (3, 6):
install_requires.append('pysha3~=1.0.2') install_requires.append("pysha3~=1.0.2")
setup( setup(
name='Planetmint', name="Planetmint",
version=version['__version__'], version=version["__version__"],
description='Planetmint: The Blockchain Database', description="Planetmint: The Blockchain Database",
long_description=readme, long_description=readme,
long_description_content_type='text/markdown', long_description_content_type="text/markdown",
url='https://github.com/Planetmint/planetmint/', url="https://github.com/Planetmint/planetmint/",
author='Planetmint Contributors', author="Planetmint Contributors",
author_email='contact@ipdb.global', author_email="contact@ipdb.global",
license='AGPLv3', license="AGPLv3",
zip_safe=False, zip_safe=False,
python_requires='>=3.9', python_requires=">=3.9",
classifiers=[ classifiers=[
'Development Status :: 4 - Beta', "Development Status :: 4 - Beta",
'Intended Audience :: Developers', "Intended Audience :: Developers",
'Topic :: Database', "Topic :: Database",
'Topic :: Database :: Database Engines/Servers', "Topic :: Database :: Database Engines/Servers",
'Topic :: Software Development', "Topic :: Software Development",
'Natural Language :: English', "Natural Language :: English",
'License :: OSI Approved :: Apache Software License', "License :: OSI Approved :: Apache Software License",
'Programming Language :: Python :: 3.9', "Programming Language :: Python :: 3.9",
'Operating System :: MacOS :: MacOS X', "Operating System :: MacOS :: MacOS X",
'Operating System :: POSIX :: Linux', "Operating System :: POSIX :: Linux",
], ],
packages=find_packages(exclude=["tests*"]),
packages=find_packages(exclude=['tests*']), scripts=["pkg/scripts/planetmint-monit-config"],
scripts=['pkg/scripts/planetmint-monit-config'],
entry_points={ entry_points={
'console_scripts': [ "console_scripts": ["planetmint=planetmint.commands.planetmint:main"],
'planetmint=planetmint.commands.planetmint:main'
],
}, },
install_requires=install_requires, install_requires=install_requires,
setup_requires=['pytest-runner'], setup_requires=["pytest-runner"],
tests_require=tests_require, tests_require=tests_require,
extras_require={ extras_require={
'test': tests_require, "test": tests_require,
'dev': dev_require + tests_require + docs_require, "dev": dev_require + tests_require + docs_require,
'docs': docs_require, "docs": docs_require,
}, },
package_data={ package_data={
'planetmint.transactions.common.schema': ['v1.0/*.yaml','v2.0/*.yaml','v3.0/*.yaml' ], "planetmint.transactions.common.schema": [
"v1.0/*.yaml",
"v2.0/*.yaml",
"v3.0/*.yaml",
],
}, },
) )

View File

@ -0,0 +1,172 @@
import pytest
import json
import base58
from hashlib import sha3_256
from zenroom import zencode_exec
from cryptoconditions.types.ed25519 import Ed25519Sha256
from cryptoconditions.types.zenroom import ZenroomSha256
from planetmint.transactions.common.crypto import generate_key_pair
CONDITION_SCRIPT = """
Scenario 'ecdh': create the signature of an object
Given I have the 'keyring'
Given that I have a 'string dictionary' named 'houses' inside 'asset'
When I create the signature of 'houses'
Then print the 'signature'"""
FULFILL_SCRIPT = """Scenario 'ecdh': Bob verifies the signature from Alice
Given I have a 'ecdh public key' from 'Alice'
Given that I have a 'string dictionary' named 'houses' inside 'asset'
Given I have a 'signature' named 'signature' inside 'result'
When I verify the 'houses' has a signature in 'signature' by 'Alice'
Then print the string 'ok'"""
SK_TO_PK = """Scenario 'ecdh': Create the keypair
Given that I am known as '{}'
Given I have the 'keyring'
When I create the ecdh public key
When I create the bitcoin address
Then print my 'ecdh public key'
Then print my 'bitcoin address'"""
GENERATE_KEYPAIR = """Scenario 'ecdh': Create the keypair
Given that I am known as 'Pippo'
When I create the ecdh key
When I create the bitcoin key
Then print data"""
ZENROOM_DATA = {"also": "more data"}
HOUSE_ASSETS = {
"data": {
"houses": [
{
"name": "Harry",
"team": "Gryffindor",
},
{
"name": "Draco",
"team": "Slytherin",
},
],
}
}
metadata = {"units": 300, "type": "KG"}
def test_zenroom_signing():
biolabs = generate_key_pair()
version = "2.0"
alice = json.loads(zencode_exec(GENERATE_KEYPAIR).output)["keyring"]
bob = json.loads(zencode_exec(GENERATE_KEYPAIR).output)["keyring"]
zen_public_keys = json.loads(
zencode_exec(
SK_TO_PK.format("Alice"), keys=json.dumps({"keyring": alice})
).output
)
zen_public_keys.update(
json.loads(
zencode_exec(
SK_TO_PK.format("Bob"), keys=json.dumps({"keyring": bob})
).output
)
)
zenroomscpt = ZenroomSha256(
script=FULFILL_SCRIPT, data=ZENROOM_DATA, keys=zen_public_keys
)
print(f"zenroom is: {zenroomscpt.script}")
# CRYPTO-CONDITIONS: generate the condition uri
condition_uri_zen = zenroomscpt.condition.serialize_uri()
print(f"\nzenroom condition URI: {condition_uri_zen}")
# CRYPTO-CONDITIONS: construct an unsigned fulfillment dictionary
unsigned_fulfillment_dict_zen = {
"type": zenroomscpt.TYPE_NAME,
"public_key": base58.b58encode(biolabs.public_key).decode(),
}
output = {
"amount": "10",
"condition": {
"details": unsigned_fulfillment_dict_zen,
"uri": condition_uri_zen,
},
"public_keys": [
biolabs.public_key,
],
}
input_ = {
"fulfillment": None,
"fulfills": None,
"owners_before": [
biolabs.public_key,
],
}
metadata = {
"result": {
"output": ["ok"]
}
}
token_creation_tx = {
"operation": "CREATE",
"asset": HOUSE_ASSETS,
"metadata": metadata,
"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,
)
# major workflow:
# we store the fulfill script in the transaction/message (zenroom-sha)
# the condition script is used to fulfill the transaction and create the signature
#
# the server should ick the fulfill script and recreate the zenroom-sha and verify the signature
message = zenroomscpt.sign(message, CONDITION_SCRIPT, alice)
assert zenroomscpt.validate(message=message)
message = json.loads(message)
fulfillment_uri_zen = zenroomscpt.serialize_uri()
message["inputs"][0]["fulfillment"] = fulfillment_uri_zen
tx = message
tx["id"] = None
json_str_tx = json.dumps(tx, sort_keys=True, skipkeys=False, separators=(",", ":"))
# SHA3: hash the serialized id-less transaction to generate the id
shared_creation_txid = sha3_256(json_str_tx.encode()).hexdigest()
message["id"] = shared_creation_txid
from planetmint.models import Transaction
from planetmint.transactions.common.exceptions import (
SchemaValidationError,
ValidationError,
)
try:
tx_obj = Transaction.from_dict(message)
except SchemaValidationError:
assert ()
except ValidationError as e:
print(e)
assert ()
print(f"VALIDATED : {tx_obj}")
assert (tx_obj == False) is False

View File

@ -25,7 +25,7 @@ extras = None
commands = flake8 planetmint tests commands = flake8 planetmint tests
[flake8] [flake8]
ignore = E126 E127 W504 E302 E126 E305 ignore = E126 E127 W504 E302 E126 E305 W503 E712 F401
[testenv:docsroot] [testenv:docsroot]
basepython = {[base]basepython} basepython = {[base]basepython}