diff --git a/CHANGELOG.md b/CHANGELOG.md index b3ec2db..258087a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,10 @@ For reference, the possible headings are: * **Known Issues** * **Notes** +## [2.4.0] - 2023-29-03 +* **Added** Zenroom script validation +* **Changed** adjusted zenroom testing for new transaction script structure + ## [2.3.3] - 2023-10-03 * **Fixed** CI issues with the docker images * **Added** Tendermint, tarantool, and planetmint initialization to the all-in-one docker image diff --git a/planetmint/application/validator.py b/planetmint/application/validator.py index 44b3100..284208f 100644 --- a/planetmint/application/validator.py +++ b/planetmint/application/validator.py @@ -148,6 +148,10 @@ class Validator: logger.warning("Invalid transaction (%s): %s", type(e).__name__, e) return False + if self.validate_script(transaction) == False: + logger.warning("Invalid transaction script") + return False + if transaction.operation == Transaction.CREATE: self.validate_create_inputs(transaction, current_transactions) elif transaction.operation in [Transaction.TRANSFER, Transaction.VOTE]: @@ -156,6 +160,11 @@ class Validator: self.validate_compose_inputs(transaction, current_transactions) return transaction + + def validate_script(self, transaction: Transaction) -> bool: + if transaction.script: + return transaction.script.validate() + return True def validate_election(self, transaction, current_transactions=[]): # TODO: move somewhere else """Validate election transaction diff --git a/pyproject.toml b/pyproject.toml index 77e100d..fa3d7e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "planetmint" -version = "2.3.3" +version = "2.4.0" description = "Planetmint: The Blockchain Database" authors = ["Planetmint contributors"] license = "AGPLv3" diff --git a/tests/assets/test_zenroom.py b/tests/assets/test_zenroom.py new file mode 100644 index 0000000..ec2bc5d --- /dev/null +++ b/tests/assets/test_zenroom.py @@ -0,0 +1,119 @@ +import json +import base58 + +from hashlib import sha3_256 +from planetmint_cryptoconditions.types.ed25519 import Ed25519Sha256 +from transactions.common.crypto import generate_key_pair +from transactions.common.utils import _fulfillment_to_details +from ipld import multihash, marshal + +INITIAL_STATE = {"also": "more data"} +ZENROOM_SCRIPT = """ + Scenario 'test': Script verifies input + Given that I have a 'string dictionary' named 'houses' + Then print the string 'ok' +""" +SCRIPT_INPUT = { + "houses": [ + { + "name": "Harry", + "team": "Gryffindor", + }, + { + "name": "Draco", + "team": "Slytherin", + }, + ], +} + +metadata = {"units": 300, "type": "KG"} + +SCRIPT_OUTPUTS = ["ok"] + +def test_zenroom_validation(b): + biolabs = generate_key_pair() + version = "3.0" + + ed25519 = Ed25519Sha256(public_key=base58.b58decode(biolabs.public_key)) + + output = { + "amount": "10", + "condition": { + "details": _fulfillment_to_details(ed25519), + "uri": ed25519.condition_uri + }, + "public_keys": [ + biolabs.public_key, + ], + } + input_ = { + "fulfillment": None, + "fulfills": None, + "owners_before": [ + biolabs.public_key, + ], + } + script_ = { + "code": ZENROOM_SCRIPT, + "inputs": SCRIPT_INPUT, + "outputs": SCRIPT_OUTPUTS, + "state": "dd8bbd234f9869cab4cc0b84aa660e9b5ef0664559b8375804ee8dce75b10576", + "policies": {}, + } + metadata = {"result": {"output": ["ok"]}} + token_creation_tx = { + "operation": "CREATE", + "assets": [{"data": multihash(marshal({"test": "my asset"}))}], + "metadata": multihash(marshal(metadata)), + "script": script_, + "outputs": [ + output, + ], + "inputs": [ + input_, + ], + "version": version, + "id": None, + } + + # JSON: serialize the transaction-without-id to a json formatted string + tx = json.dumps( + token_creation_tx, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=False, + ) + + ed25519.sign(message=sha3_256(tx.encode()).digest(), private_key=base58.b58decode(biolabs.private_key)) + + tx = json.loads(tx) + tx["inputs"][0]["fulfillment"] = ed25519.serialize_uri() + tx["id"] = None + json_str_tx = json.dumps(tx, sort_keys=True, skipkeys=False, separators=(",", ":")) + shared_creation_txid = sha3_256(json_str_tx.encode()).hexdigest() + tx["id"] = shared_creation_txid + + from transactions.common.transaction import Transaction + from transactions.common.exceptions import ( + SchemaValidationError, + ValidationError, + ) + + try: + print(f"TX\n{tx}") + tx_obj = Transaction.from_dict(tx, False) + except SchemaValidationError as e: + print(e) + assert () + except ValidationError as e: + print(e) + assert () + + try: + b.validate_transaction(tx_obj) + except ValidationError as e: + print("Invalid transaction ({}): {}".format(type(e).__name__, e)) + assert () + + print(f"VALIDATED : {tx_obj}") + assert (tx_obj == False) is False diff --git a/tests/assets/test_zenroom_signing.py b/tests/assets/test_zenroom_signing.py deleted file mode 100644 index 43a69f8..0000000 --- a/tests/assets/test_zenroom_signing.py +++ /dev/null @@ -1,174 +0,0 @@ -import json -import base58 - -from hashlib import sha3_256 -from zenroom import zencode_exec -from planetmint_cryptoconditions.types.zenroom import ZenroomSha256 -from transactions.common.crypto import generate_key_pair -from ipld import multihash, marshal - -CONDITION_SCRIPT = """Scenario 'ecdh': create the signature of an object - Given I have the 'keyring' - Given that I have a 'string dictionary' named 'houses' - 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' - Given I have a 'signature' named 'signature' - 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 keyring""" - -INITIAL_STATE = {"also": "more data"} -SCRIPT_INPUT = { - "houses": [ - { - "name": "Harry", - "team": "Gryffindor", - }, - { - "name": "Draco", - "team": "Slytherin", - }, - ], -} - -metadata = {"units": 300, "type": "KG"} - - -def test_zenroom_signing(b): - biolabs = generate_key_pair() - version = "3.0" - - alice = json.loads(zencode_exec(GENERATE_KEYPAIR).output) - bob = json.loads(zencode_exec(GENERATE_KEYPAIR).output) - - zen_public_keys = json.loads(zencode_exec(SK_TO_PK.format("Alice"), keys=json.dumps(alice)).output) - zen_public_keys.update(json.loads(zencode_exec(SK_TO_PK.format("Bob"), keys=json.dumps(bob)).output)) - - zenroomscpt = ZenroomSha256(script=FULFILL_SCRIPT, data=INITIAL_STATE, 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, - ], - } - script_ = { - "code": {"type": "zenroom", "raw": "test_string", "parameters": [{"obj": "1"}, {"obj": "2"}]}, - "state": "dd8bbd234f9869cab4cc0b84aa660e9b5ef0664559b8375804ee8dce75b10576", - "input": SCRIPT_INPUT, - "output": ["ok"], - "policies": {}, - } - metadata = {"result": {"output": ["ok"]}} - token_creation_tx = { - "operation": "CREATE", - "assets": [{"data": multihash(marshal({"test": "my asset"}))}], - "metadata": multihash(marshal(metadata)), - "script": script_, - "outputs": [ - output, - ], - "inputs": [ - input_, - ], - "version": version, - "id": None, - } - - # JSON: serialize the transaction-without-id to a json formatted string - tx = json.dumps( - token_creation_tx, - sort_keys=True, - separators=(",", ":"), - ensure_ascii=False, - ) - script_ = json.dumps(script_) - # 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 - - signed_input = zenroomscpt.sign(script_, CONDITION_SCRIPT, alice) - - input_signed = json.loads(signed_input) - input_signed["input"]["signature"] = input_signed["output"]["signature"] - del input_signed["output"]["signature"] - del input_signed["output"]["logs"] - input_signed["output"] = ["ok"] # define expected output that is to be compared - input_msg = json.dumps(input_signed) - assert zenroomscpt.validate(message=input_msg) - - tx = json.loads(tx) - fulfillment_uri_zen = zenroomscpt.serialize_uri() - - tx["script"] = input_signed - tx["inputs"][0]["fulfillment"] = fulfillment_uri_zen - 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() - tx["id"] = shared_creation_txid - - from transactions.common.transaction import Transaction - from transactions.common.exceptions import ( - SchemaValidationError, - ValidationError, - ) - - try: - print(f"TX\n{tx}") - tx_obj = Transaction.from_dict(tx, False) - except SchemaValidationError as e: - print(e) - assert () - except ValidationError as e: - print(e) - assert () - - try: - b.validate_transaction(tx_obj) - except ValidationError as e: - print("Invalid transaction ({}): {}".format(type(e).__name__, e)) - assert () - - print(f"VALIDATED : {tx_obj}") - assert (tx_obj == False) is False