From 6f31d2a5fdaba4df47f9097e9025699673e6184a Mon Sep 17 00:00:00 2001 From: Brian Faust Date: Mon, 27 Aug 2018 08:35:10 +0300 Subject: [PATCH 01/68] chore: 0.1.0 changelog (#45) --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30d2ba87..1233719b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,3 +6,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## Unreleased + +## 0.1.0 - 2018-08-27 +- Initial Release From e13b01c465050cdbe6cc7a9feb51a37edf5bc709 Mon Sep 17 00:00:00 2001 From: Chris Johnson Date: Tue, 28 Aug 2018 03:00:45 -0500 Subject: [PATCH 02/68] chore: add visual studio support (#48) --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index a475b62b..73eaff10 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ __pycache__/ build/ dist/ *.egg-info +.vscode +.vs From ded2dc27ad34b94143cec0fd016842174f2ac3f2 Mon Sep 17 00:00:00 2001 From: Rok Date: Tue, 28 Aug 2018 10:04:23 +0200 Subject: [PATCH 03/68] misc: remove "contributions closed" from PR template (#46) --- .github/PULL_REQUEST_TEMPLATE.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 720408ee..0f953bf0 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,5 +1,3 @@ -‼️ **Contributions are closed** - ## Proposed changes Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request. If it fixes a bug or resolves a feature request, be sure to link to that issue. From f74bc62b448bffa26b741408b7b8f2aa5903214f Mon Sep 17 00:00:00 2001 From: dated Date: Tue, 28 Aug 2018 15:28:06 +0200 Subject: [PATCH 04/68] misc: link to docs (#49) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3f7ae71a..d7543e1b 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ If you have any questions, requests or ideas open an issue or ask us in #python ## Documentation -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/developers/sdk/cryptography/python.html). +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/api/sdk/cryptography/python.html). ## Security From d56fdb2cc2dc21faf1c1325a004adaf7448e290d Mon Sep 17 00:00:00 2001 From: Rok Date: Thu, 30 Aug 2018 04:48:44 +0200 Subject: [PATCH 05/68] refactor: create network objects instead of reading from config.ini (#55) * refactor: create network objects instead of reading from config.ini * chore: bump client version + log the change --- CHANGELOG.md | 6 +++++ config.ini | 14 ------------ crypto/configuration/network.py | 34 +++++++++++------------------ crypto/networks/__init__.py | 0 crypto/networks/devnet.py | 7 ++++++ crypto/networks/mainnet.py | 7 ++++++ crypto/networks/testnet.py | 7 ++++++ setup.py | 2 +- tests/configuration/test_network.py | 8 ++++--- 9 files changed, 46 insertions(+), 39 deletions(-) delete mode 100644 config.ini create mode 100644 crypto/networks/__init__.py create mode 100644 crypto/networks/devnet.py create mode 100644 crypto/networks/mainnet.py create mode 100644 crypto/networks/testnet.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 1233719b..26fce230 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,5 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## Unreleased +## 0.1.1 - 2018-08-29 + +### Fixed + +- created network objects instead of loading network settings from an ini file which is not included in the built package + ## 0.1.0 - 2018-08-27 - Initial Release diff --git a/config.ini b/config.ini deleted file mode 100644 index 3255a1c4..00000000 --- a/config.ini +++ /dev/null @@ -1,14 +0,0 @@ -[mainnet] -epoch = 2017-03-21 13:00:00 -version = 23 -wif = 170 - -[devnet] -epoch = 2017-03-21 13:00:00 -version = 30 -wif = 170 - -[testnet] -epoch = 2017-03-21 13:00:00 -version = 23 -wif = 186 diff --git a/crypto/configuration/network.py b/crypto/configuration/network.py index 7adcbc00..97a4bbab 100644 --- a/crypto/configuration/network.py +++ b/crypto/configuration/network.py @@ -1,26 +1,19 @@ -import os -from configparser import ConfigParser -from datetime import datetime - -config_file = os.path.abspath('config.ini') - -settings = ConfigParser() -settings.read(config_file) +from crypto.networks.devnet import Devnet network = {} -def set_network(network_name): +def set_network(network_object): """Set what network you want to use in the crypto library Args: - network_name (str): name of a network, default ones are ARK's mainnet, devnet & testnet + network_object (Network object): Testnet, Devnet, Mainnet """ global network network = { - 'epoch': datetime.strptime(settings.get(network_name, 'epoch'), '%Y-%m-%d %H:%M:%S'), - 'version': int(settings.get(network_name, 'version')), - 'wif': int(settings.get(network_name, 'wif')), + 'epoch': network_object.epoch, + 'version': network_object.version, + 'wif': network_object.wif, } @@ -31,7 +24,7 @@ def get_network(): dict: network settings (default network is devnet) """ if not network: - set_network('devnet') + set_network(Devnet) return network @@ -43,10 +36,9 @@ def set_custom_network(epoch, version, wif): version (int): chains version wif (int): chains wif """ - section_name = 'custom' - if section_name not in settings.sections(): - settings.add_section(section_name) - settings.set(section_name, 'epoch', epoch.strftime('%Y-%m-%d %H:%M:%S')) - settings.set(section_name, 'version', str(version)) - settings.set(section_name, 'wif', str(wif)) - set_network('custom') + global network + network = { + 'epoch': epoch, + 'version': version, + 'wif': wif, + } diff --git a/crypto/networks/__init__.py b/crypto/networks/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/crypto/networks/devnet.py b/crypto/networks/devnet.py new file mode 100644 index 00000000..6af399f0 --- /dev/null +++ b/crypto/networks/devnet.py @@ -0,0 +1,7 @@ +from datetime import datetime + + +class Devnet(object): + epoch = datetime(2017, 3, 21, 13, 00, 00) + version = 30 + wif = 170 diff --git a/crypto/networks/mainnet.py b/crypto/networks/mainnet.py new file mode 100644 index 00000000..7e648903 --- /dev/null +++ b/crypto/networks/mainnet.py @@ -0,0 +1,7 @@ +from datetime import datetime + + +class Mainnet(object): + epoch = datetime(2017, 3, 21, 13, 00, 00) + version = 23 + wif = 170 diff --git a/crypto/networks/testnet.py b/crypto/networks/testnet.py new file mode 100644 index 00000000..7644cecc --- /dev/null +++ b/crypto/networks/testnet.py @@ -0,0 +1,7 @@ +from datetime import datetime + + +class Testnet(object): + epoch = datetime(2017, 3, 21, 13, 00, 00) + version = 23 + wif = 186 diff --git a/setup.py b/setup.py index 6c0f06cb..1d34f936 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ setuptools.setup( name='arkecosystem-crypto', description='A simple Cryptography Implementation in Python for the ARK Blockchain.', - version='0.1.0', + version='0.1.1', author='Ark Ecosystem', author_email='info@ark.io', url='https://github.com/ArkEcosystem/python-crypto', diff --git a/tests/configuration/test_network.py b/tests/configuration/test_network.py index d7e1b617..e33f52d6 100644 --- a/tests/configuration/test_network.py +++ b/tests/configuration/test_network.py @@ -1,6 +1,8 @@ from datetime import datetime from crypto.configuration.network import get_network, set_custom_network, set_network +from crypto.networks.devnet import Devnet +from crypto.networks.mainnet import Mainnet def test_get_network(): @@ -9,10 +11,10 @@ def test_get_network(): def test_set_network(): - set_network('mainnet') + set_network(Mainnet) result = get_network() assert result['version'] == 23 - set_network('devnet') # set back to devnet so other tests don't fail + set_network(Devnet) # set back to devnet so other tests don't fail def test_set_custom_network(): @@ -22,4 +24,4 @@ def test_set_custom_network(): assert result['version'] == 11 assert result['wif'] == 130 assert result['epoch'] == epoch_time - set_network('devnet') # set back to devnet so other tests don't fail + set_network(Devnet) # set back to devnet so other tests don't fail From 3a0260c13a6a936a0030a681a6511a37f2f47557 Mon Sep 17 00:00:00 2001 From: Chris Johnson Date: Wed, 29 Aug 2018 22:01:24 -0500 Subject: [PATCH 06/68] chore: update pytest syntax (#54) Update the 'py.test' syntax to use 'pytest' for better cross-platform support. --- .travis.yml | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7260d085..cf1f7140 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ install: before_script: - flake8 . script: - - py.test -v -s --cov=crypto --cov-config=.coveragerc --cov-report xml + - pytest -v -s --cov=crypto --cov-config=.coveragerc --cov-report xml after_success: - bash <(curl -s https://codecov.io/bash) branches: diff --git a/Makefile b/Makefile index cec9cad5..bf9681ec 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ test: - py.test -v -s $(ARGS) + pytest -v -s $(ARGS) lint: flake8 . From ace580d28ed79a0b23afabf4f2678f1311f0dd5e Mon Sep 17 00:00:00 2001 From: supaiku <1311798+supaiku0@users.noreply.github.com> Date: Sun, 30 Sep 2018 12:25:51 +0200 Subject: [PATCH 07/68] fix: skip recipient_id in to_bytes for tx type 1 and 4 (#56) * fix: skip recipient_id in to_bytes for type 1 and 4 * style: fix linter --- crypto/transactions/transaction.py | 7 ++++++- .../deserializers/test_delegate_registration.py | 1 + .../deserializers/test_multi_signature_registration.py | 1 + .../deserializers/test_second_signature_registration.py | 1 + tests/transactions/deserializers/test_transfer.py | 1 + tests/transactions/deserializers/test_vote.py | 1 + 6 files changed, 11 insertions(+), 1 deletion(-) diff --git a/crypto/transactions/transaction.py b/crypto/transactions/transaction.py index 086f8df2..a870757c 100644 --- a/crypto/transactions/transaction.py +++ b/crypto/transactions/transaction.py @@ -91,7 +91,12 @@ def to_bytes(self, skip_signature=True, skip_second_signature=True): bytes_data += write_bit32(self.timestamp) bytes_data += write_high(self.senderPublicKey) - if self.recipientId: + skip_recipient_id = self.type in [ + TRANSACTION_SECOND_SIGNATURE_REGISTRATION, + TRANSACTION_MULTI_SIGNATURE_REGISTRATION + ] + + if self.recipientId and not skip_recipient_id: bytes_data += b58decode_check(self.recipientId) else: bytes_data += pack('21x') diff --git a/tests/transactions/deserializers/test_delegate_registration.py b/tests/transactions/deserializers/test_delegate_registration.py index df24443a..dfc37c2e 100644 --- a/tests/transactions/deserializers/test_delegate_registration.py +++ b/tests/transactions/deserializers/test_delegate_registration.py @@ -6,3 +6,4 @@ def test_delegate_registration_deserializer(): deserializer = Deserializer(serialized) actual = deserializer.deserialize() assert actual.asset['delegate'] == {'username': 'boldninja'} + actual.verify() diff --git a/tests/transactions/deserializers/test_multi_signature_registration.py b/tests/transactions/deserializers/test_multi_signature_registration.py index 26a4d80f..983307ae 100644 --- a/tests/transactions/deserializers/test_multi_signature_registration.py +++ b/tests/transactions/deserializers/test_multi_signature_registration.py @@ -13,3 +13,4 @@ def test_multi_signature_registration_deserializer(): '+0276dc5b8706a85ca9fdc46e571ac84e52fbb48e13ec7a165a80731b44ae89f1fc', '+02e8d5d17eb17bbc8d7bf1001d29a2d25d1249b7bb7a5b7ad8b7422063091f4b31' ] + actual.verify() diff --git a/tests/transactions/deserializers/test_second_signature_registration.py b/tests/transactions/deserializers/test_second_signature_registration.py index 6d2b96dd..7a54bd61 100644 --- a/tests/transactions/deserializers/test_second_signature_registration.py +++ b/tests/transactions/deserializers/test_second_signature_registration.py @@ -6,3 +6,4 @@ def test_second_signature_registration(): deserializer = Deserializer(serialized) actual = deserializer.deserialize() assert actual.asset['signature']['publicKey'] == '03699e966b2525f9088a6941d8d94f7869964a000efe65783d78ac82e1199fe609' # noqa + actual.verify() diff --git a/tests/transactions/deserializers/test_transfer.py b/tests/transactions/deserializers/test_transfer.py index fb9ddb04..b697e980 100644 --- a/tests/transactions/deserializers/test_transfer.py +++ b/tests/transactions/deserializers/test_transfer.py @@ -13,3 +13,4 @@ def test_transfer_deserializer(): assert actual.recipientId == 'D61mfSggzbvQgTUe6JhYKH2doHaqJ3Dyib' assert actual.senderPublicKey == '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' # noqa assert actual.id == 'da61c6cba363cc39baa0ca3f9ba2c5db81b9805045bd0b9fc58af07ad4206856' + actual.verify() diff --git a/tests/transactions/deserializers/test_vote.py b/tests/transactions/deserializers/test_vote.py index 3c91d333..e82600b0 100644 --- a/tests/transactions/deserializers/test_vote.py +++ b/tests/transactions/deserializers/test_vote.py @@ -6,3 +6,4 @@ def test_vote_deserializer(): deserializer = Deserializer(serialized) actual = deserializer.deserialize() assert actual.asset['votes'] == ['+022cca9529ec97a772156c152a00aad155ee6708243e65c9d211a589cb5d43234d'] # noqa + actual.verify() From 0163d942a0bdca4c3e76126d706510abff8a3ec3 Mon Sep 17 00:00:00 2001 From: faustbrian Date: Tue, 16 Oct 2018 10:09:54 +0300 Subject: [PATCH 08/68] chore: setup CircleCI --- .circleci/config.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..596ff434 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,22 @@ +version: 2 +jobs: + build: + docker: + - image: circleci/python:3.6.1 + working_directory: ~/arkecosystem/crypto + steps: + - checkout + - run: + name: install dependencies + command: | + python3 -m venv venv + . venv/bin/activate + pip install .[test] + - run: + name: run tests + command: | + . venv/bin/activate + pytest -v -s --cov=ark --cov-config=.coveragerc --cov-report xml + - store_artifacts: + path: test-reports + destination: test-reports From 4941618dd30e2c33ede631c1c5489d708118abb5 Mon Sep 17 00:00:00 2001 From: faustbrian Date: Tue, 16 Oct 2018 10:10:22 +0300 Subject: [PATCH 09/68] chore: delete .travis.yml --- .travis.yml | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index cf1f7140..00000000 --- a/.travis.yml +++ /dev/null @@ -1,18 +0,0 @@ -language: python -python: - - 3.4 - - 3.5 - - 3.6 -cache: pip -install: - - pip install .[test] -before_script: - - flake8 . -script: - - pytest -v -s --cov=crypto --cov-config=.coveragerc --cov-report xml -after_success: - - bash <(curl -s https://codecov.io/bash) -branches: - only: - - master - - develop From fe58284d044e380083b03266bd3f6687b2eb085e Mon Sep 17 00:00:00 2001 From: Edgar Goetzendorff Date: Wed, 17 Oct 2018 08:25:07 +0200 Subject: [PATCH 10/68] chore: replace travis badge with circleci (#57) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d7543e1b..5b1fb8be 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ > A simple Cryptography Implementation in Python for the ARK Blockchain. -[![Build Status](https://badgen.now.sh/travis/ArkEcosystem/python-crypto/master)](https://travis-ci.org/ArkEcosystem/python-crypto) +[![Build Status](https://badgen.now.sh/circleci/github/ArkEcosystem/python-crypto)](https://circleci.com/gh/ArkEcosystem/python-crypto) [![Codecov](https://badgen.now.sh/codecov/c/github/arkecosystem/python-crypto)](https://codecov.io/gh/arkecosystem/python-crypto) [![Latest Version](https://badgen.now.sh/github/release/ArkEcosystem/python-crypto)](https://github.com/ArkEcosystem/python-crypto/releases/latest) [![License: MIT](https://badgen.now.sh/badge/license/MIT/green)](https://opensource.org/licenses/MIT) From 6bbb2aaf1bfe15b4bb6d29e6c3b17a8beeef9283 Mon Sep 17 00:00:00 2001 From: Edgar Goetzendorff Date: Thu, 18 Oct 2018 17:00:56 +0200 Subject: [PATCH 11/68] chore: add codecov to circleci config (#58) * chore: add codecov to circleci config * chore: adjust .coveragerc * chore: adjust circleci config --- .circleci/config.yml | 6 +++++- .coveragerc | 11 +++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 596ff434..6050722b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,7 +16,11 @@ jobs: name: run tests command: | . venv/bin/activate - pytest -v -s --cov=ark --cov-config=.coveragerc --cov-report xml + pytest -v -s --cov=crypto --cov-config=.coveragerc --cov-report xml + - run: + name: Codecov + when: on_success + command: bash <(curl -s https://codecov.io/bash) - store_artifacts: path: test-reports destination: test-reports diff --git a/.coveragerc b/.coveragerc index 398ff08a..44c7c654 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,2 +1,13 @@ [run] branch = True +source = crypto + +[report] +exclude_lines = + if self.debug: + pragma: no cover + raise NotImplementedError + if __name__ == .__main__.: +ignore_errors = True +omit = + tests/* From 7414a37b6162aa8258bb9c6d7094d6d19fdac25c Mon Sep 17 00:00:00 2001 From: Edgar Goetzendorff Date: Fri, 19 Oct 2018 11:36:06 +0200 Subject: [PATCH 12/68] chore: store test results (#60) --- .circleci/config.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6050722b..6022f526 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,11 +16,14 @@ jobs: name: run tests command: | . venv/bin/activate - pytest -v -s --cov=crypto --cov-config=.coveragerc --cov-report xml + mkdir test-results + pytest -v -s --junitxml=test-reports/junit.xml --cov=crypto --cov-config=.coveragerc --cov-report xml - run: name: Codecov when: on_success command: bash <(curl -s https://codecov.io/bash) + - store_test_results: + path: test-results - store_artifacts: path: test-reports destination: test-reports From 0a2ca4b3a9520cce11f52df8dd2d62d822978964 Mon Sep 17 00:00:00 2001 From: Edgar Goetzendorff Date: Fri, 19 Oct 2018 11:37:06 +0200 Subject: [PATCH 13/68] fix: contributing link in pr template (#59) --- .github/PULL_REQUEST_TEMPLATE.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 0f953bf0..7837d925 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,11 +1,13 @@ ## Proposed changes - + ## Types of changes - + - [ ] Bugfix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) @@ -17,14 +19,16 @@ _Put an `x` in the boxes that apply_ - [ ] Other... Please describe: ## Checklist + -_Put an `x` in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code._ - -- [ ] I have read the [CONTRIBUTING](https://docs.ark.io/developers/guidelines/contributing.html) documentation +- [ ] I have read the [CONTRIBUTING](https://docs.ark.io/guidebook/contribution-guidelines/contributing.html) documentation - [ ] Lint and unit tests pass locally with my changes - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] I have added necessary documentation (if appropriate) ## Further comments - + From c0bf737835c2a11940a0d8fc6ff28e9b2dff353e Mon Sep 17 00:00:00 2001 From: Edgar Goetzendorff Date: Tue, 23 Oct 2018 11:45:31 +0200 Subject: [PATCH 14/68] chore: adhere to naming convention (#61) --- README.md | 8 ++++---- setup.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5b1fb8be..d45253da 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# ARK Python - Crypto +# Ark Python - Crypto

- +

-> A simple Cryptography Implementation in Python for the ARK Blockchain. +> A simple Cryptography Implementation in Python for the Ark Blockchain. [![Build Status](https://badgen.now.sh/circleci/github/ArkEcosystem/python-crypto)](https://circleci.com/gh/ArkEcosystem/python-crypto) [![Codecov](https://badgen.now.sh/codecov/c/github/arkecosystem/python-crypto)](https://codecov.io/gh/arkecosystem/python-crypto) @@ -22,7 +22,7 @@ Before you start contributing please take some time and check our official [Pyth 5. Run the entire test suite again, confirming that all tests pass including the ones you just added. 6. Send a GitHub Pull Request. GitHub Pull Requests are the expected method of code collaboration on this project. -If you have any questions, requests or ideas open an issue or ask us in #python on [ARK's Slack](https://ark.io/slack). +If you have any questions, requests or ideas open an issue or ask us in #python on the [ArkEcosystem Slack](https://ark.io/slack). ## Documentation diff --git a/setup.py b/setup.py index 1d34f936..a7a0c5c3 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ setuptools.setup( name='arkecosystem-crypto', - description='A simple Cryptography Implementation in Python for the ARK Blockchain.', + description='A simple Cryptography Implementation in Python for the Ark Blockchain.', version='0.1.1', author='Ark Ecosystem', author_email='info@ark.io', From 48d6dc34853e0af139fa84a6fd3a12f1c520d1a1 Mon Sep 17 00:00:00 2001 From: Rok Date: Sun, 11 Nov 2018 11:46:56 +0800 Subject: [PATCH 15/68] fix: pass network_version to the underlying call in address_from_private_key (#63) --- crypto/identity/address.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/identity/address.py b/crypto/identity/address.py index 1a8f2abc..b87a8a19 100644 --- a/crypto/identity/address.py +++ b/crypto/identity/address.py @@ -63,7 +63,7 @@ def address_from_passphrase(passphrase, network_version=None): network_version = network['version'] private_key = hashlib.sha256(passphrase.encode()).hexdigest() - address = address_from_private_key(private_key) + address = address_from_private_key(private_key, network_version) return address From 6203494853196cfc70861577563750a7e95a709e Mon Sep 17 00:00:00 2001 From: whitehat <34144820+whitehat@users.noreply.github.com> Date: Fri, 30 Nov 2018 11:17:23 +0000 Subject: [PATCH 16/68] fix: 64 length vendorfield not 63 (#64) --- crypto/transactions/transaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/transactions/transaction.py b/crypto/transactions/transaction.py index a870757c..ed351eb6 100644 --- a/crypto/transactions/transaction.py +++ b/crypto/transactions/transaction.py @@ -101,7 +101,7 @@ def to_bytes(self, skip_signature=True, skip_second_signature=True): else: bytes_data += pack('21x') - if self.vendorField and len(self.vendorField) < 64: + if self.vendorField and len(self.vendorField) < 65: bytes_data += self.vendorField bytes_data += pack('{}x'.format(64 - len(self.vendorField))) else: From deea0821f35bd8e66baaf9d090417d004ce9a5f4 Mon Sep 17 00:00:00 2001 From: Jolan Date: Tue, 18 Dec 2018 09:18:01 +0100 Subject: [PATCH 17/68] fix: messages now can work with both publickey and publicKey (#66) --- crypto/transactions/transaction.py | 2 +- crypto/utils/message.py | 21 ++++++++--------- tests/conftest.py | 10 +++++++-- tests/utils/test_message.py | 36 +++++++++++++++++++++--------- 4 files changed, 45 insertions(+), 24 deletions(-) diff --git a/crypto/transactions/transaction.py b/crypto/transactions/transaction.py index ed351eb6..a273540e 100644 --- a/crypto/transactions/transaction.py +++ b/crypto/transactions/transaction.py @@ -181,7 +181,7 @@ def verify(self): will happen. """ transaction = self.to_bytes() - message = Message(transaction, self.signature, self.senderPublicKey) + message = Message(message=transaction, signature=self.signature, publickey=self.senderPublicKey) is_valid = message.verify() if not is_valid: raise ArkInvalidTransaction('Transaction could not be verified') diff --git a/crypto/utils/message.py b/crypto/utils/message.py index 227a29ca..fbdbf8ba 100644 --- a/crypto/utils/message.py +++ b/crypto/utils/message.py @@ -6,11 +6,12 @@ class Message(object): - - def __init__(self, message, signature, public_key): - self.public_key = public_key - self.signature = signature - self.message = message + def __init__(self, **kwargs): + for k in kwargs.keys(): + if k in ['message', 'signature', 'publickey', 'publicKey']: + self.__setattr__(k, kwargs[k]) + else: + raise TypeError('Invalid keyword argument %s' % k) @classmethod def sign(cls, message, passphrase): @@ -23,11 +24,11 @@ def sign(cls, message, passphrase): Returns: Message: returns a message object """ - message_byes = message if isinstance(message, bytes) else message.encode() + message_bytes = message if isinstance(message, bytes) else message.encode() passphrase = passphrase.decode() if isinstance(passphrase, bytes) else passphrase private_key = PrivateKey.from_passphrase(passphrase) - signature = private_key.sign(message_byes) - return cls(message, signature, private_key.public_key) + signature = private_key.sign(message_bytes) + return cls(message=message, signature=signature, publicKey=private_key.public_key) def verify(self): """Verify the Message object @@ -36,7 +37,7 @@ def verify(self): bool: returns a boolean - true if verified, false if not """ message = self.message if isinstance(self.message, bytes) else self.message.encode() - key = PublicKey.from_hex(self.public_key) + key = PublicKey.from_hex(self.publickey) if hasattr(self, 'publickey') else PublicKey.from_hex(self.publicKey) signature = unhexlify(self.signature) is_verified = key.public_key.verify(signature, message) return is_verified @@ -48,7 +49,7 @@ def to_dict(self): dict: dictionary consiting of public_key, signature and message """ data = { - 'public_key': self.public_key, + ('publicKey' if hasattr(self, 'publicKey') else 'publickey'): (self.publicKey if hasattr(self, 'publicKey') else self.publickey), 'signature': self.signature, 'message': self.message, } diff --git a/tests/conftest.py b/tests/conftest.py index 122693cd..f425dfa1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -179,11 +179,17 @@ def transaction_type_8(): @pytest.fixture def message(): data = { - 'data': { - 'public_key': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', + 'camelCase_pk': { + 'publicKey': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', 'signature': '304402200fb4adddd1f1d652b544ea6ab62828a0a65b712ed447e2538db0caebfa68929e02205ecb2e1c63b29879c2ecf1255db506d671c8b3fa6017f67cfd1bf07e6edd1cc8', # noqa 'message': 'Hello World' }, + + 'snake_case_pk': { + 'publickey': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', + 'signature': '304402200fb4adddd1f1d652b544ea6ab62828a0a65b712ed447e2538db0caebfa68929e02205ecb2e1c63b29879c2ecf1255db506d671c8b3fa6017f67cfd1bf07e6edd1cc8', # noqa + 'message': 'Hello World' + }, 'passphrase': 'this is a top secret passphrase' } return data diff --git a/tests/utils/test_message.py b/tests/utils/test_message.py index 979d2803..a80e330c 100644 --- a/tests/utils/test_message.py +++ b/tests/utils/test_message.py @@ -4,24 +4,38 @@ def test_signing(message): - result = Message.sign(message['data']['message'], message['passphrase']) - assert result.to_dict() == message['data'] + result = Message.sign(message['camelCase_pk']['message'], message['passphrase']) + assert result.to_dict() == message['camelCase_pk'] -def test_verify(message): - result = Message(**message['data']) +def test_verify_with_publickey(message): + result = Message(**message['snake_case_pk']) + assert result.verify() is True + + +def test_verify_with_publicKey(message): + result = Message(**message['camelCase_pk']) assert result.verify() is True def test_to_dict(message): - result = Message(**message['data']) - assert result.to_dict() == message['data'] + result = Message(**message['camelCase_pk']) + assert result.to_dict() == message['camelCase_pk'] + + +def test_to_json_with_publicKey(message): + result = Message(**message['camelCase_pk']) + json_data = result.to_json() + data = json.loads(json_data) + assert data['signature'] == message['camelCase_pk']['signature'] + assert data['publicKey'] == message['camelCase_pk']['publicKey'] + assert data['message'] == message['camelCase_pk']['message'] -def test_to_json(message): - result = Message(**message['data']) +def test_to_json_with_publickey(message): + result = Message(**message['snake_case_pk']) json_data = result.to_json() data = json.loads(json_data) - assert data['signature'] == message['data']['signature'] - assert data['public_key'] == message['data']['public_key'] - assert data['message'] == message['data']['message'] + assert data['signature'] == message['snake_case_pk']['signature'] + assert data['publickey'] == message['snake_case_pk']['publickey'] + assert data['message'] == message['snake_case_pk']['message'] From 8c33e4d00f5ca8dabc086e62d9a777a4f93f0906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=BCnich?= Date: Mon, 24 Dec 2018 22:22:19 -0500 Subject: [PATCH 18/68] docs: fix documentation sdk url (#68) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d45253da..7321395c 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ If you have any questions, requests or ideas open an issue or ask us in #python ## Documentation -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/api/sdk/cryptography/python.html). +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/sdk/cryptography/python.html). ## Security From 52a26f3393e3911cef03a44676725ba8da64a7b0 Mon Sep 17 00:00:00 2001 From: Pedro Souza Date: Tue, 25 Dec 2018 01:52:21 -0200 Subject: [PATCH 19/68] refactor: ensure that the transaction amount is an unsigned integer (#67) --- crypto/transactions/builder/transfer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crypto/transactions/builder/transfer.py b/crypto/transactions/builder/transfer.py index cfde8374..48757124 100644 --- a/crypto/transactions/builder/transfer.py +++ b/crypto/transactions/builder/transfer.py @@ -17,7 +17,10 @@ def __init__(self, recipientId, amount, vendorField=None, fee=None): """ super().__init__() self.transaction.recipientId = recipientId - self.transaction.amount = amount + if type(amount) == int and amount > 0: + self.transaction.amount = amount + else: + raise ValueError('Amount is not valid') self.transaction.vendorField = vendorField.encode() if vendorField else None if fee: self.transaction.fee = fee From 367224f8677ca629c507fea22b368da4b1d03f46 Mon Sep 17 00:00:00 2001 From: Brian Faust Date: Tue, 12 Feb 2019 09:09:28 +0200 Subject: [PATCH 20/68] chore: setup code owners (#71) --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 CODEOWNERS diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 00000000..6eb4b0c7 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @faustbrian @supaiku0 From 5850bc1efe595545f3f458cc4830eed7c7b3b90b Mon Sep 17 00:00:00 2001 From: vulet <38090089+vulet@users.noreply.github.com> Date: Wed, 22 May 2019 22:36:46 -0400 Subject: [PATCH 21/68] chore: update contribution link (#79) --- .github/CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 027481b4..825605e1 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1 +1 @@ -Please see [CONTRIBUTING](https://docs.ark.io/developers/guidelines/contributing.html) for details before opening your pull request. +Please see [CONTRIBUTING](https://docs.ark.io/guidebook/contribution-guidelines/contributing.html) for details before opening your pull request. From 7be9cb4a1273161123b20923d10995b3a6a1c555 Mon Sep 17 00:00:00 2001 From: Edgar Goetzendorff Date: Fri, 24 May 2019 04:50:59 +0200 Subject: [PATCH 22/68] feat: support new vendorfield length (#80) --- crypto/transactions/transaction.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crypto/transactions/transaction.py b/crypto/transactions/transaction.py index ed351eb6..1413b8ce 100644 --- a/crypto/transactions/transaction.py +++ b/crypto/transactions/transaction.py @@ -101,9 +101,10 @@ def to_bytes(self, skip_signature=True, skip_second_signature=True): else: bytes_data += pack('21x') - if self.vendorField and len(self.vendorField) < 65: + if self.vendorField and len(self.vendorField) <= 255: bytes_data += self.vendorField - bytes_data += pack('{}x'.format(64 - len(self.vendorField))) + if len(self.vendorField) < 64: + bytes_data += pack('{}x'.format(64 - len(self.vendorField))) else: bytes_data += pack('64x') From 236f57739cae0aab6231a6a611f267fac7c32f7b Mon Sep 17 00:00:00 2001 From: Brian Faust Date: Mon, 27 May 2019 09:54:03 +0200 Subject: [PATCH 23/68] chore: use organization-wide GitHub Configuration (#82) --- .github/CODE_OF_CONDUCT.md | 71 ----------------------- .github/CONTRIBUTING.md | 1 - .github/ISSUE_TEMPLATE/Bug_report.md | 27 --------- .github/ISSUE_TEMPLATE/Feature_request.md | 16 ----- .github/PULL_REQUEST_TEMPLATE.md | 34 ----------- 5 files changed, 149 deletions(-) delete mode 100644 .github/CODE_OF_CONDUCT.md delete mode 100644 .github/CONTRIBUTING.md delete mode 100644 .github/ISSUE_TEMPLATE/Bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/Feature_request.md delete mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md deleted file mode 100644 index b6b28dab..00000000 --- a/.github/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,71 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, gender identity and expression, level of experience, -education, socio-economic status, nationality, personal appearance, race, -religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at info@ark.io. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][https://www.contributor-covenant.org], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index 825605e1..00000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1 +0,0 @@ -Please see [CONTRIBUTING](https://docs.ark.io/guidebook/contribution-guidelines/contributing.html) for details before opening your pull request. diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md deleted file mode 100644 index bfa4acc9..00000000 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Server (please complete the following information):** - - OS: [e.g. Ubuntu] - - Version [e.g. 16.04] - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md deleted file mode 100644 index a09db44f..00000000 --- a/.github/ISSUE_TEMPLATE/Feature_request.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 7837d925..00000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,34 +0,0 @@ -## Proposed changes - - -## Types of changes - - -- [ ] Bugfix (non-breaking change which fixes an issue) -- [ ] New feature (non-breaking change which adds functionality) -- [ ] Refactoring (improve a current implementation without adding a new feature or fixing a bug) -- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) -- [ ] Build (changes that affect the build system) -- [ ] Docs (documentation only changes) -- [ ] Test (adding missing tests or fixing existing tests) -- [ ] Other... Please describe: - -## Checklist - - -- [ ] I have read the [CONTRIBUTING](https://docs.ark.io/guidebook/contribution-guidelines/contributing.html) documentation -- [ ] Lint and unit tests pass locally with my changes -- [ ] I have added tests that prove my fix is effective or that my feature works -- [ ] I have added necessary documentation (if appropriate) - -## Further comments - From f365fbc028d88ec36711fd06229b801899ba7038 Mon Sep 17 00:00:00 2001 From: ItsANameToo <35610748+ItsANameToo@users.noreply.github.com> Date: Mon, 27 May 2019 10:09:49 +0200 Subject: [PATCH 24/68] chore: add itsanametoo to code owners (#83) --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 6eb4b0c7..f59ef0ec 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1 @@ -* @faustbrian @supaiku0 +* @faustbrian @supaiku0 @ItsANameToo From aed332f4a1698c5850615aed392b2b4452d91d96 Mon Sep 17 00:00:00 2001 From: Brian Faust Date: Mon, 22 Jul 2019 08:11:29 +0300 Subject: [PATCH 25/68] chore: setup probot/stale configuration (#86) --- .github/stale.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 00000000..0d0b1c99 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1 @@ +_extends: .github From bbde5da2ce1970dac5962ef97dc25b2b7ba81a2d Mon Sep 17 00:00:00 2001 From: Brian Faust Date: Mon, 22 Jul 2019 09:22:49 +0300 Subject: [PATCH 26/68] chore(readme): add lead maintainer information (#87) --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7321395c..c7f3d9be 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ [![Latest Version](https://badgen.now.sh/github/release/ArkEcosystem/python-crypto)](https://github.com/ArkEcosystem/python-crypto/releases/latest) [![License: MIT](https://badgen.now.sh/badge/license/MIT/green)](https://opensource.org/licenses/MIT) +> Lead Maintainer: [Brian Faust](https://github.com/faustbrian) + ## Guide for contributing Before you start contributing please take some time and check our official [Python Development Guidelines](https://github.com/ArkEcosystem/development-guidelines/blob/master/Python/README.md) which follow the widely accepted PEP8 Python Style Guide. 🖋 @@ -34,11 +36,8 @@ If you discover a security vulnerability within this package, please send an e-m ## Credits -- [Rok Halužan](https://github.com/roks0n) -- [Tomaž Šifrer](https://github.com/tsifrer) -- [Brian Faust](https://github.com/faustbrian) -- [All Contributors](../../contributors) +This project exists thanks to all the people who [contribute](../../contributors). ## License -[MIT](LICENSE) © [ArkEcosystem](https://ark.io) +[MIT](LICENSE) © [ARK Ecosystem](https://ark.io) From 92b72590f0d3e6e9fd3457bd3833e5872c5d5a22 Mon Sep 17 00:00:00 2001 From: Brian Faust Date: Sat, 21 Sep 2019 08:48:39 +0300 Subject: [PATCH 27/68] ci: setup github action workflow for testing (#92) --- .github/workflows/test.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..753bc469 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,28 @@ +name: Test + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + max-parallel: 4 + matrix: + python-version: [3.5, 3.6, 3.7] + + steps: + - uses: actions/checkout@v1 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: install dependencies + run: | + python3 -m venv venv + . venv/bin/activate + pip install .[test] + - name: run tests + run: | + . venv/bin/activate + mkdir test-results + pytest -v -s --junitxml=test-reports/junit.xml --cov=crypto --cov-config=.coveragerc --cov-report xml From e9cde8dcbc37d79a58d2b70b45da82084bccb68b Mon Sep 17 00:00:00 2001 From: Brian Faust Date: Sat, 21 Sep 2019 08:50:40 +0300 Subject: [PATCH 28/68] ci: remove old CircleCI configuration --- .circleci/config.yml | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 6022f526..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,29 +0,0 @@ -version: 2 -jobs: - build: - docker: - - image: circleci/python:3.6.1 - working_directory: ~/arkecosystem/crypto - steps: - - checkout - - run: - name: install dependencies - command: | - python3 -m venv venv - . venv/bin/activate - pip install .[test] - - run: - name: run tests - command: | - . venv/bin/activate - mkdir test-results - pytest -v -s --junitxml=test-reports/junit.xml --cov=crypto --cov-config=.coveragerc --cov-report xml - - run: - name: Codecov - when: on_success - command: bash <(curl -s https://codecov.io/bash) - - store_test_results: - path: test-results - - store_artifacts: - path: test-reports - destination: test-reports From f1e91701ec0ac0d58a3ae48119d797c77f677f6d Mon Sep 17 00:00:00 2001 From: Brian Faust Date: Mon, 23 Sep 2019 10:21:49 +0300 Subject: [PATCH 29/68] ci: adjust job triggers (#94) --- .github/workflows/test.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 753bc469..095c2781 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,9 +1,14 @@ name: Test -on: [push, pull_request] +on: + push: + branches: + - '**' + pull_request: + types: [opened] jobs: - build: + unit: runs-on: ubuntu-latest strategy: max-parallel: 4 From f1f70ab46a829ff3d10d545c682bc2ebae005395 Mon Sep 17 00:00:00 2001 From: Brian Faust Date: Mon, 23 Sep 2019 11:45:48 +0300 Subject: [PATCH 30/68] ci: report coverage to codecov (#95) --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 095c2781..490e037b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,3 +31,5 @@ jobs: . venv/bin/activate mkdir test-results pytest -v -s --junitxml=test-reports/junit.xml --cov=crypto --cov-config=.coveragerc --cov-report xml + - name: Codecov + run: bash <(curl -s https://codecov.io/bash) -t ${{ secrets.CODECOV_TOKEN }} From 6058091968e4fb3806673a7537f9d4bf35685b7e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 27 Sep 2019 13:27:41 +0300 Subject: [PATCH 31/68] chore(deps): add renovate.json (#96) --- renovate.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 renovate.json diff --git a/renovate.json b/renovate.json new file mode 100644 index 00000000..042ab22c --- /dev/null +++ b/renovate.json @@ -0,0 +1,3 @@ +{ + "extends": ["config:base", ":preserveSemverRanges"] +} From e17c2a28f4c7dac5504b4e2b4d30de5db2fef8b6 Mon Sep 17 00:00:00 2001 From: Edgar Goetzendorff Date: Sat, 19 Oct 2019 08:11:20 +0200 Subject: [PATCH 32/68] chore: update workflow config (#98) --- .github/workflows/test.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 490e037b..030dfd99 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,11 +1,12 @@ name: Test on: - push: - branches: - - '**' - pull_request: - types: [opened] + push: + branches: + - "master" + - "develop" + pull_request: + types: [ready_for_review, synchronize, opened] jobs: unit: From e80fdf88798f056e48fe15390d676009886b7591 Mon Sep 17 00:00:00 2001 From: Jolan Date: Mon, 23 Dec 2019 04:59:42 +0100 Subject: [PATCH 33/68] feat: AIP 11 & 18 implementation (#100) --- .gitignore | 2 + crypto/constants.py | 41 ++- crypto/schnorr/__init__.py | 0 crypto/schnorr/schnorr.py | 262 ++++++++++++++++++ crypto/transactions/builder/base.py | 63 ++++- .../builder/delegate_registration.py | 2 + .../builder/delegate_resignation.py | 8 +- crypto/transactions/builder/htlc_claim.py | 28 ++ crypto/transactions/builder/htlc_lock.py | 37 +++ crypto/transactions/builder/htlc_refund.py | 28 ++ crypto/transactions/builder/ipfs.py | 12 +- crypto/transactions/builder/multi_payment.py | 13 +- .../builder/multi_signature_registration.py | 33 ++- .../builder/second_signature_registration.py | 2 + .../transactions/builder/timelock_transfer.py | 17 -- crypto/transactions/builder/transfer.py | 8 +- crypto/transactions/builder/vote.py | 2 + crypto/transactions/deserializer.py | 78 ++---- .../deserializers/delegate_registration.py | 3 +- .../deserializers/delegate_resignation.py | 14 + .../transactions/deserializers/htlc_claim.py | 24 ++ .../transactions/deserializers/htlc_lock.py | 41 +++ .../transactions/deserializers/htlc_refund.py | 22 ++ crypto/transactions/deserializers/ipfs.py | 38 +++ .../deserializers/multi_payment.py | 37 +++ .../multi_signature_registration.py | 22 +- .../second_signature_registration.py | 5 +- crypto/transactions/deserializers/transfer.py | 3 +- crypto/transactions/deserializers/vote.py | 3 +- crypto/transactions/serializer.py | 38 +-- .../serializers/delegate_registration.py | 2 + crypto/transactions/serializers/htlc_claim.py | 15 + crypto/transactions/serializers/htlc_lock.py | 24 ++ .../transactions/serializers/htlc_refund.py | 12 + crypto/transactions/serializers/ipfs.py | 13 +- .../transactions/serializers/multi_payment.py | 8 +- .../multi_signature_registration.py | 18 +- .../serializers/timelock_transfer.py | 21 -- crypto/transactions/serializers/transfer.py | 1 + crypto/transactions/serializers/vote.py | 3 + crypto/transactions/transaction.py | 162 +++++------ tests/conftest.py | 250 ++++++++++++----- tests/resources/test_transaction.py | 4 +- .../builder/test_delegate_registration.py | 18 +- .../builder/test_delegate_resignation.py | 22 +- tests/transactions/builder/test_htlc_claim.py | 31 +++ tests/transactions/builder/test_htlc_lock.py | 34 +++ .../transactions/builder/test_htlc_refund.py | 28 ++ tests/transactions/builder/test_ipfs.py | 27 +- .../builder/test_multi_payment.py | 28 +- .../test_multi_signature_registration.py | 44 ++- .../test_second_signature_registration.py | 19 +- .../builder/test_timelock_transfer.py | 8 - tests/transactions/builder/test_transfer.py | 21 +- tests/transactions/builder/test_vote.py | 18 +- .../test_delegate_registration.py | 17 +- .../test_delegate_resignation.py | 21 ++ .../deserializers/test_htlc_claim.py | 23 ++ .../deserializers/test_htlc_lock.py | 25 ++ .../deserializers/test_htlc_refund.py | 22 ++ tests/transactions/deserializers/test_ipfs.py | 22 ++ .../deserializers/test_multi_payment.py | 25 ++ .../test_multi_signature_registration.py | 32 ++- .../test_second_signature_registration.py | 19 +- .../deserializers/test_transfer.py | 42 ++- tests/transactions/deserializers/test_vote.py | 18 +- .../serializers/test_delegate_registration.py | 2 +- .../serializers/test_delegate_resignation.py | 9 +- .../serializers/test_htlc_claim.py | 6 + .../serializers/test_htlc_lock.py | 6 + .../serializers/test_htlc_refund.py | 6 + tests/transactions/serializers/test_ipfs.py | 5 +- .../serializers/test_multi_payment.py | 9 +- .../test_multi_signature_registration.py | 2 +- .../test_second_signature_registration.py | 2 +- .../serializers/test_timelock_transfer.py | 9 - .../transactions/serializers/test_transfer.py | 2 +- tests/transactions/serializers/test_vote.py | 2 +- tests/transactions/test_transaction.py | 5 +- tests/utils/test_message.py | 11 +- 80 files changed, 1610 insertions(+), 449 deletions(-) create mode 100644 crypto/schnorr/__init__.py create mode 100644 crypto/schnorr/schnorr.py create mode 100644 crypto/transactions/builder/htlc_claim.py create mode 100644 crypto/transactions/builder/htlc_lock.py create mode 100644 crypto/transactions/builder/htlc_refund.py delete mode 100644 crypto/transactions/builder/timelock_transfer.py create mode 100644 crypto/transactions/deserializers/delegate_resignation.py create mode 100644 crypto/transactions/deserializers/htlc_claim.py create mode 100644 crypto/transactions/deserializers/htlc_lock.py create mode 100644 crypto/transactions/deserializers/htlc_refund.py create mode 100644 crypto/transactions/deserializers/ipfs.py create mode 100644 crypto/transactions/deserializers/multi_payment.py create mode 100644 crypto/transactions/serializers/htlc_claim.py create mode 100644 crypto/transactions/serializers/htlc_lock.py create mode 100644 crypto/transactions/serializers/htlc_refund.py delete mode 100644 crypto/transactions/serializers/timelock_transfer.py create mode 100644 tests/transactions/builder/test_htlc_claim.py create mode 100644 tests/transactions/builder/test_htlc_lock.py create mode 100644 tests/transactions/builder/test_htlc_refund.py delete mode 100644 tests/transactions/builder/test_timelock_transfer.py create mode 100644 tests/transactions/deserializers/test_delegate_resignation.py create mode 100644 tests/transactions/deserializers/test_htlc_claim.py create mode 100644 tests/transactions/deserializers/test_htlc_lock.py create mode 100644 tests/transactions/deserializers/test_htlc_refund.py create mode 100644 tests/transactions/deserializers/test_ipfs.py create mode 100644 tests/transactions/deserializers/test_multi_payment.py create mode 100644 tests/transactions/serializers/test_htlc_claim.py create mode 100644 tests/transactions/serializers/test_htlc_lock.py create mode 100644 tests/transactions/serializers/test_htlc_refund.py delete mode 100644 tests/transactions/serializers/test_timelock_transfer.py diff --git a/.gitignore b/.gitignore index 73eaff10..5176d6e9 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ dist/ *.egg-info .vscode .vs +venv +venv/ \ No newline at end of file diff --git a/crypto/constants.py b/crypto/constants.py index 210e4a46..e5cf084f 100644 --- a/crypto/constants.py +++ b/crypto/constants.py @@ -1,12 +1,16 @@ +from enum import Enum + TRANSACTION_TRANSFER = 0 TRANSACTION_SECOND_SIGNATURE_REGISTRATION = 1 TRANSACTION_DELEGATE_REGISTRATION = 2 TRANSACTION_VOTE = 3 TRANSACTION_MULTI_SIGNATURE_REGISTRATION = 4 TRANSACTION_IPFS = 5 -TRANSACTION_TIMELOCK_TRANSFER = 6 -TRANSACTION_MULTI_PAYMENT = 7 -TRANSACTION_DELEGATE_RESIGNATION = 8 +TRANSACTION_MULTI_PAYMENT = 6 +TRANSACTION_DELEGATE_RESIGNATION = 7 +TRANSACTION_HTLC_LOCK = 8 +TRANSACTION_HTLC_CLAIM = 9 +TRANSACTION_HTLC_REFUND = 10 TRANSACTION_TYPES = { TRANSACTION_TRANSFER: 'transfer', @@ -15,9 +19,11 @@ TRANSACTION_VOTE: 'vote', TRANSACTION_MULTI_SIGNATURE_REGISTRATION: 'multi_signature_registration', TRANSACTION_IPFS: 'ipfs', - TRANSACTION_TIMELOCK_TRANSFER: 'timelock_transfer', TRANSACTION_MULTI_PAYMENT: 'multi_payment', TRANSACTION_DELEGATE_RESIGNATION: 'delegate_resignation', + TRANSACTION_HTLC_LOCK: 'htlc_lock', + TRANSACTION_HTLC_CLAIM: 'htlc_claim', + TRANSACTION_HTLC_REFUND: 'htlc_refund', } TRANSACTION_FEES = { @@ -26,8 +32,27 @@ TRANSACTION_DELEGATE_REGISTRATION: 2500000000, TRANSACTION_VOTE: 100000000, TRANSACTION_MULTI_SIGNATURE_REGISTRATION: 500000000, - TRANSACTION_IPFS: 0, - TRANSACTION_TIMELOCK_TRANSFER: 0, - TRANSACTION_MULTI_PAYMENT: 0, - TRANSACTION_DELEGATE_RESIGNATION: 0, + TRANSACTION_IPFS: 500000000, + TRANSACTION_MULTI_PAYMENT: 10000000, + TRANSACTION_DELEGATE_RESIGNATION: 2500000000, + TRANSACTION_HTLC_LOCK: 10000000, + TRANSACTION_HTLC_CLAIM: 0, + TRANSACTION_HTLC_REFUND: 0, } + + +class TRANSACTION_TYPE_GROUP(Enum): + def __int__(self): + return int(self.value) + + TEST = 0 + CORE = 1 + RESERVED = 1000 # Everything above is available to anyone + + +class HTLC_LOCK_EXPIRATION_TYPE(Enum): + def __str__(self): + return int(self.value) + + EPOCH_TIMESTAMP = 1 + BLOCK_HEIGHT = 2 diff --git a/crypto/schnorr/__init__.py b/crypto/schnorr/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/crypto/schnorr/schnorr.py b/crypto/schnorr/schnorr.py new file mode 100644 index 00000000..9f65e7ed --- /dev/null +++ b/crypto/schnorr/schnorr.py @@ -0,0 +1,262 @@ +# From Toons implementation +# see here https://github.com/Moustikitos/dpos/blob/master/dposlib/ark/secp256k1/schnorr.py + +import hashlib +from binascii import unhexlify +from builtins import int + +p = int(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F) +n = int(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141) + + +def point_from_encoded(pubkey): + """ + Decode and decompress a ``secp256k1`` point. + Args: + pubkey (:class:`bytes`): compressed and encoded point + Returns: + :class:`list`: ``secp256k1`` point + """ + pubkey = bytearray(pubkey) + x = int_from_bytes(pubkey[1:]) + y = y_from_x(x) + if y is None: + raise ValueError('Point not on ``secp256k1`` curve') + elif y % 2 != pubkey[0] - 2: + y = -y % p + return [x, y] + + +def y_from_x(x): + """ + Compute :class:`P.y` from :class:`P.x` according to ``y²=x³+7``. + """ + y_sq = (pow(x, 3, p) + 7) % p + y = pow(y_sq, (p + 1) // 4, p) + if pow(y, 2, p) != y_sq: + return None + return y + + +class Point(list): + """ + ``secp256k1`` point . Initialization can be done with sole ``x`` value. + :class:`Point` overrides ``*`` and ``+`` operators which accepts + :class:`list` as argument and returns :class:`Point`. It extends + :class:`list` and allows item access using ``__getattr__``/``__setattr__`` + operators. + """ + + x = property( + lambda cls: list.__getitem__(cls, 0), + lambda cls, v: [ + list.__setitem__(cls, 0, int(v)), + list.__setitem__(cls, 1, y_from_x(int(v))) + ], + None, '' + ) + y = property( + lambda cls: list.__getitem__(cls, 1), + None, None, '' + ) + + def __init__(self, *xy): + if len(xy) == 0: + xy = (0, None) + elif len(xy) == 1: + xy += (y_from_x(int(xy[0])), ) + list.__init__(self, [int(e) if e is not None else e for e in xy[:2]]) + + def __mul__(self, k): + if isinstance(k, int): + return Point(*point_mul(self, k)) + else: + raise TypeError("'%s' should be an int" % k) + __rmul__ = __mul__ + + def __add__(self, P): + if isinstance(P, list): + return Point(*point_add(self, P)) + else: + raise TypeError("'%s' should be a 2-int-length list" % P) + __radd__ = __add__ + + @staticmethod + def decode(pubkey): + """ + Decode and decompress a ``secp256k1`` point. + """ + return Point(*point_from_encoded(pubkey)) + + def encode(self): + """ + Decode and decompress a ``secp256k1`` point. + """ + return encoded_from_point(self) + + +G = Point(0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798) + + +def point_add(P1, P2): + """ + Add ``secp256k1`` points. + Args: + P1 (:class:`list`): first ``secp256k1`` point + P2 (:class:`list`): second ``secp256k1`` point + Returns: + :class:`list`: ``secp256k1`` point + """ + if (P1 is None): + return P2 + if (P2 is None): + return P1 + if (x(P1) == x(P2) and y(P1) != y(P2)): + raise ValueError('One of the point is not on the curve') + if (P1 == P2): + lam = (3 * x(P1) * x(P1) * pow(2 * y(P1), p - 2, p)) % p + else: + lam = ((y(P2) - y(P1)) * pow(x(P2) - x(P1), p - 2, p)) % p + x3 = (lam * lam - x(P1) - x(P2)) % p + return [x3, (lam * (x(P1) - x3) - y(P1)) % p] + + +def bcrypto410_verify(msg, pubkey, sig): + if len(msg) != 32: + raise ValueError('The message must be a 32-byte array.') + if len(sig) != 64: + raise ValueError('The signature must be a 64-byte array.') + + P = Point.decode(pubkey) + r, s = int_from_bytes(sig[:32]), int_from_bytes(sig[32:]) + if r >= p or s >= n: + return False + + e = int_from_bytes(hash_sha256(sig[0:32] + pubkey + msg)) % n + R = Point(*(G*s + point_mul(P, n-e))) # P*(n-e) does not work... + if R is None or not is_quad(R.y) or R.x != r: + return False + return True + + +def b410_schnorr_verify(message, publicKey, signature): + return bcrypto410_verify( + hash_sha256(message), + Point.decode(unhexlify(publicKey)).encode(), + unhexlify(signature) + ) + + +def x(P): + """ + Return :class:`P.x` or :class:`P[0]`. + Args: + P (:class:`list`): ``secp256k1`` point + Returns: + :class:`int`: x + """ + return P[0] + + +def bytes_from_int(x): + return int(x).to_bytes(32, byteorder='big') + + +def int_from_bytes(b): + return int.from_bytes(b, byteorder='big') + + +def jacobi(x): + return pow(x, (p - 1) // 2, p) + + +def is_quad(x): + return jacobi(x) == 1 + + +def hash_sha256(b): + """ + Args: + b (:class:`bytes` or :class:`str`): sequence to be hashed + Returns: + :class:`bytes`: sha256 hash + """ + return hashlib.sha256( + b if isinstance(b, bytes) else b.encode('utf-8') + ).digest() + + +def point_mul(P, n): + """ + Multiply ``secp256k1`` point with scalar. + Args: + P (:class:`list`): ``secp256k1`` point + n (:class:`int`): scalar + Returns: + :class:`list`: ``secp256k1`` point + """ + R = None + for i in range(256): + if ((n >> i) & 1): + R = point_add(R, P) + P = point_add(P, P) + return R + + +def y(P): + """ + Return :class:`P.y` or :class:`P[1]`. + + Args: + P (:class:`list`): ``secp256k1`` point + Returns: + :class:`int`: y + """ + return P[1] + + +def encoded_from_point(P): + """ + Encode and compress a ``secp256k1`` point: + * ``bytes(2) || bytes(x)`` if y is even + * ``bytes(3) || bytes(x)`` if y is odd + + Args: + P (:class:`list`): ``secp256k1`` point + Returns: + :class:`bytes`: compressed and encoded point + """ + return (b'\x03' if y(P) & 1 else b'\x02') + bytes_from_int(x(P)) + + +# https://github.com/bcoin-org/bcrypto/blob/v4.1.0/lib/js/schnorr.js +def bcrypto410_sign(msg, seckey0): + if len(msg) != 32: + raise ValueError('The message must be a 32-byte array.') + + seckey = int_from_bytes(seckey0) + if not (1 <= seckey <= n - 1): + raise ValueError( + 'The secret key must be an integer in the range 1..n-1.' + ) + + k0 = int_from_bytes(hash_sha256(seckey0 + msg)) % n + if k0 == 0: + raise RuntimeError( + 'Failure. This happens only with negligible probability.' + ) + + R = G * k0 + Rraw = bytes_from_int(R.x) + e = int_from_bytes( + hash_sha256(Rraw + encoded_from_point(G*seckey) + msg) + ) % n + + seckey %= n + k0 %= n + k = n - k0 if not is_quad(R.y) else k0 + + s = (k + e * seckey) % n + s %= n + + return Rraw + bytes_from_int(s) diff --git a/crypto/transactions/builder/base.py b/crypto/transactions/builder/base.py index 9c68ea5e..473cc22f 100644 --- a/crypto/transactions/builder/base.py +++ b/crypto/transactions/builder/base.py @@ -1,5 +1,11 @@ +import hashlib +from binascii import hexlify, unhexlify + from crypto.configuration.fee import get_fee +from crypto.constants import HTLC_LOCK_EXPIRATION_TYPE, TRANSACTION_TYPE_GROUP +from crypto.identity.private_key import PrivateKey from crypto.identity.public_key import PublicKey +from crypto.schnorr import schnorr from crypto.transactions.transaction import Transaction from crypto.utils.message import Message @@ -10,6 +16,12 @@ def __init__(self): self.transaction = Transaction() self.transaction.type = getattr(self, 'transaction_type', None) self.transaction.fee = get_fee(getattr(self, 'transaction_type', None)) + self.transaction.nonce = getattr(self, 'nonce', None) + self.transaction.typeGroup = getattr(self, 'typeGroup', 1) + self.transaction.signatures = getattr(self, 'signatures', None) + self.transaction.version = getattr(self, 'version', 2) + if self.transaction.type != 0: + self.transaction.amount = getattr(self, 'amount', 0) def to_dict(self): return self.transaction.to_dict() @@ -17,15 +29,16 @@ def to_dict(self): def to_json(self): return self.transaction.to_json() - def sign(self, passphrase): + def schnorr_sign(self, passphrase): """Sign the transaction using the given passphrase Args: passphrase (str): passphrase associated with the account sending this transaction """ self.transaction.senderPublicKey = PublicKey.from_passphrase(passphrase) - message = Message.sign(self.transaction.to_bytes(), passphrase) - self.transaction.signature = message.signature + msg = hashlib.sha256(self.transaction.to_bytes(False, True, False)).digest() + secret = unhexlify(PrivateKey.from_passphrase(passphrase).to_hex()) + self.transaction.signature = hexlify(schnorr.bcrypto410_sign(msg, secret)) self.transaction.id = self.transaction.get_id() def second_sign(self, passphrase): @@ -38,8 +51,44 @@ def second_sign(self, passphrase): self.transaction.signSignature = message.signature self.transaction.id = self.transaction.get_id() - def verify(self): - self.transaction.verify() + def multi_sign(self, passphrase, index): + if not self.transaction.signatures: + self.transaction.signatures = [] + + index = len(self.transaction.signatures) if index == -1 else index + + msg = hashlib.sha256(self.transaction.to_bytes()).digest() + secret = unhexlify(PrivateKey.from_passphrase(passphrase).to_hex()) + signature = hexlify(schnorr.bcrypto410_sign(msg, secret)) + + index_formatted = hex(index).replace('x', '') + self.transaction.signatures.append(index_formatted + signature.decode()) + + def schnorr_verify(self): + return self.transaction.verify_schnorr() + + def schnorr_verify_multisig(self): + return self.transaction.verify_schnorr_multisig() + + def set_nonce(self, nonce): + self.transaction.nonce = nonce + + def set_amount(self, amount): + self.transaction.amount = amount + + def set_sender_public_key(self, public_key): + self.transaction.senderPublicKey = public_key + + def set_expiration(self, expiration): + if type(expiration) == int: + self.transaction.expiration = expiration + else: + types = {HTLC_LOCK_EXPIRATION_TYPE.EPOCH_TIMESTAMP: 1, HTLC_LOCK_EXPIRATION_TYPE.BLOCK_HEIGHT: 2} + self.transaction.expiration = types[expiration] - def second_verify(self): - self.transaction.second_verify() + def set_type_group(self, type_group): + if type(type_group) == int: + self.transaction.typeGroup = type_group + else: + types = {TRANSACTION_TYPE_GROUP.TEST: 0, TRANSACTION_TYPE_GROUP.CORE: 1, TRANSACTION_TYPE_GROUP.RESERVED: 1000} + self.transaction.typeGroup = types[type_group] diff --git a/crypto/transactions/builder/delegate_registration.py b/crypto/transactions/builder/delegate_registration.py index ba3c1796..04a0a0f7 100644 --- a/crypto/transactions/builder/delegate_registration.py +++ b/crypto/transactions/builder/delegate_registration.py @@ -15,7 +15,9 @@ def __init__(self, username, fee=None): fee (int, optional): fee used for the transaction (default is already set) """ super().__init__() + self.transaction.asset['delegate'] = {'username': username} + if fee: self.transaction.fee = fee diff --git a/crypto/transactions/builder/delegate_resignation.py b/crypto/transactions/builder/delegate_resignation.py index db1f2b17..4075dd81 100644 --- a/crypto/transactions/builder/delegate_resignation.py +++ b/crypto/transactions/builder/delegate_resignation.py @@ -1,4 +1,4 @@ -from crypto.constants import TRANSACTION_DELEGATE_RESIGNATION +from crypto.constants import TRANSACTION_DELEGATE_RESIGNATION, TRANSACTION_TYPE_GROUP from crypto.transactions.builder.base import BaseTransactionBuilder @@ -13,5 +13,11 @@ def __init__(self, fee=None): fee (int, optional): fee used for the transaction (default is already set) """ super().__init__() + + self.transaction.typeGroup = self.get_type_group() + if fee: self.transaction.fee = fee + + def get_type_group(self): + return TRANSACTION_TYPE_GROUP.CORE.value diff --git a/crypto/transactions/builder/htlc_claim.py b/crypto/transactions/builder/htlc_claim.py new file mode 100644 index 00000000..4f245ad9 --- /dev/null +++ b/crypto/transactions/builder/htlc_claim.py @@ -0,0 +1,28 @@ +from crypto.constants import TRANSACTION_HTLC_CLAIM, TRANSACTION_TYPE_GROUP +from crypto.transactions.builder.base import BaseTransactionBuilder + + +class HtlcClaim(BaseTransactionBuilder): + + transaction_type = TRANSACTION_HTLC_CLAIM + + def __init__(self, lock_transaction_id, unlock_secret, fee=None): + """Create a timelock transaction + + Args: + fee (int, optional): fee used for the transaction (default is already set) + """ + super().__init__() + + self.transaction.typeGroup = self.get_type_group() + + self.transaction.asset['claim'] = { + 'lockTransactionId': lock_transaction_id, + 'unlockSecret': unlock_secret + } + + if fee: + self.transaction.fee = fee + + def get_type_group(self): + return TRANSACTION_TYPE_GROUP.CORE.value diff --git a/crypto/transactions/builder/htlc_lock.py b/crypto/transactions/builder/htlc_lock.py new file mode 100644 index 00000000..5f825eb6 --- /dev/null +++ b/crypto/transactions/builder/htlc_lock.py @@ -0,0 +1,37 @@ +from crypto.constants import TRANSACTION_HTLC_LOCK, TRANSACTION_TYPE_GROUP +from crypto.transactions.builder.base import BaseTransactionBuilder + + +class HtlcLock(BaseTransactionBuilder): + + transaction_type = TRANSACTION_HTLC_LOCK + + def __init__(self, recipient_id, secret_hash, expiration_type, expiration_value, fee=None): + """Create a timelock transaction + + Args: + recipient_id (str): recipient identifier + secret_hash (str): a hash of the secret. The SAME hash must be used in the corresponding “claim” transaction + expiration_type (int): type of the expiration. Either block height or network epoch timestamp based + expiration_value (int): Expiration of transaction in seconds or height depending on expiration_type + fee (int, optional): fee used for the transaction (default is already set) + """ + super().__init__() + + self.transaction.recipientId = recipient_id + + self.transaction.typeGroup = self.get_type_group() + + self.transaction.asset['lock'] = { + 'secretHash': secret_hash, + 'expiration': { + 'type': expiration_type, + 'value': expiration_value + } + } + + if fee: + self.transaction.fee = fee + + def get_type_group(self): + return TRANSACTION_TYPE_GROUP.CORE.value diff --git a/crypto/transactions/builder/htlc_refund.py b/crypto/transactions/builder/htlc_refund.py new file mode 100644 index 00000000..c18e1b5d --- /dev/null +++ b/crypto/transactions/builder/htlc_refund.py @@ -0,0 +1,28 @@ +from crypto.constants import TRANSACTION_HTLC_REFUND, TRANSACTION_TYPE_GROUP +from crypto.transactions.builder.base import BaseTransactionBuilder + + +class HtlcRefund(BaseTransactionBuilder): + + transaction_type = TRANSACTION_HTLC_REFUND + + def __init__(self, lock_transaction_id, fee=None): + """Create a timelock transaction + + Args: + lock_transaction_id (str) : HTLC lock transaction ID we wish to claim + fee (int, optional): fee used for the transaction (default is already set) + """ + super().__init__() + + self.transaction.typeGroup = self.get_type_group() + + self.transaction.asset['refund'] = { + 'lockTransactionId': lock_transaction_id, + } + + if fee: + self.transaction.fee = fee + + def get_type_group(self): + return TRANSACTION_TYPE_GROUP.CORE.value diff --git a/crypto/transactions/builder/ipfs.py b/crypto/transactions/builder/ipfs.py index 80e438d0..e1adf2c4 100644 --- a/crypto/transactions/builder/ipfs.py +++ b/crypto/transactions/builder/ipfs.py @@ -1,4 +1,4 @@ -from crypto.constants import TRANSACTION_IPFS +from crypto.constants import TRANSACTION_IPFS, TRANSACTION_TYPE_GROUP from crypto.transactions.builder.base import BaseTransactionBuilder @@ -6,12 +6,20 @@ class IPFS(BaseTransactionBuilder): transaction_type = TRANSACTION_IPFS - def __init__(self, fee=None): + def __init__(self, ipfs_id, fee=None): """Create an ipfs transaction Args: + ipfs_id (str): ipfs transaction identifier fee (int, optional): fee used for the transaction (default is already set) """ super().__init__() + + self.transaction.asset['ipfs'] = ipfs_id + self.transaction.typeGroup = self.get_type_group() + if fee: self.transaction.fee = fee + + def get_type_group(self): + return TRANSACTION_TYPE_GROUP.CORE.value diff --git a/crypto/transactions/builder/multi_payment.py b/crypto/transactions/builder/multi_payment.py index 18c31779..a95b98eb 100644 --- a/crypto/transactions/builder/multi_payment.py +++ b/crypto/transactions/builder/multi_payment.py @@ -1,4 +1,4 @@ -from crypto.constants import TRANSACTION_MULTI_PAYMENT +from crypto.constants import TRANSACTION_MULTI_PAYMENT, TRANSACTION_TYPE_GROUP from crypto.transactions.builder.base import BaseTransactionBuilder @@ -13,5 +13,16 @@ def __init__(self, fee=None): fee (int, optional): fee used for the transaction (default is already set) """ super().__init__() + + self.transaction.typeGroup = self.get_type_group() + + self.transaction.asset['payments'] = [] + if fee: self.transaction.fee = fee + + def get_type_group(self): + return TRANSACTION_TYPE_GROUP.CORE.value + + def add_payment(self, amount, recipient_id): + self.transaction.asset['payments'].append({'amount': amount, 'recipientId': recipient_id}) diff --git a/crypto/transactions/builder/multi_signature_registration.py b/crypto/transactions/builder/multi_signature_registration.py index 44d7ab94..56b664d6 100644 --- a/crypto/transactions/builder/multi_signature_registration.py +++ b/crypto/transactions/builder/multi_signature_registration.py @@ -1,4 +1,4 @@ -from crypto.constants import TRANSACTION_MULTI_SIGNATURE_REGISTRATION +from crypto.constants import TRANSACTION_FEES, TRANSACTION_MULTI_SIGNATURE_REGISTRATION from crypto.transactions.builder.base import BaseTransactionBuilder @@ -6,23 +6,32 @@ class MultiSignatureRegistration(BaseTransactionBuilder): transaction_type = TRANSACTION_MULTI_SIGNATURE_REGISTRATION - def __init__(self, min_signatures, lifetime, keysgroup, fee=None): + def __init__(self, fee=None): """Create a new multi signature transaction Args: - min_signatures (int): minimum required signatures - lifetime (int): transaction lifetime - keysgroup (list): list of signatures required fee (int, optional): fee used for the transaction (default is already set) """ super().__init__() + self.transaction.asset = { - 'multisignature': { - 'min': min_signatures, - 'lifetime': lifetime, - 'keysgroup': keysgroup, + 'multiSignature': { + 'min': None, + 'publicKeys': [], }, } - # default transaction fee for this type i set in the base builder - transaction_fee = fee or self.transaction.fee - self.transaction.fee = (len(keysgroup) + 1) * transaction_fee + + if fee: + self.transaction.fee = fee + + def set_min(self, minimum_participants): + self.transaction.asset['multiSignature']['min'] = minimum_participants + + def set_public_keys(self, public_keys): + self.transaction.asset['multiSignature']['publicKeys'] = public_keys + self.transaction.fee = (len(public_keys) + 1) * self.transaction.fee + + def add_participant(self, public_key): + self.transaction.asset['multiSignature']['publicKeys'].append(public_key) + self.transaction.fee = (len(self.transaction.asset['multiSignature']['publicKeys']) + 1) * \ + TRANSACTION_FEES.get(TRANSACTION_MULTI_SIGNATURE_REGISTRATION) diff --git a/crypto/transactions/builder/second_signature_registration.py b/crypto/transactions/builder/second_signature_registration.py index b5b84e10..d35c6f67 100644 --- a/crypto/transactions/builder/second_signature_registration.py +++ b/crypto/transactions/builder/second_signature_registration.py @@ -15,7 +15,9 @@ def __init__(self, second_passphrase, fee=None): fee (int, optional): fee used for the transaction (default is already set) """ super().__init__() + public_key = PublicKey.from_passphrase(second_passphrase) self.transaction.asset['signature'] = {'publicKey': public_key} + if fee: self.transaction.fee = fee diff --git a/crypto/transactions/builder/timelock_transfer.py b/crypto/transactions/builder/timelock_transfer.py deleted file mode 100644 index 863f6526..00000000 --- a/crypto/transactions/builder/timelock_transfer.py +++ /dev/null @@ -1,17 +0,0 @@ -from crypto.constants import TRANSACTION_TIMELOCK_TRANSFER -from crypto.transactions.builder.base import BaseTransactionBuilder - - -class TimelockTransfer(BaseTransactionBuilder): - - transaction_type = TRANSACTION_TIMELOCK_TRANSFER - - def __init__(self, fee=None): - """Create a timelock transaction - - Args: - fee (int, optional): fee used for the transaction (default is already set) - """ - super().__init__() - if fee: - self.transaction.fee = fee diff --git a/crypto/transactions/builder/transfer.py b/crypto/transactions/builder/transfer.py index 48757124..5d2f15ce 100644 --- a/crypto/transactions/builder/transfer.py +++ b/crypto/transactions/builder/transfer.py @@ -16,11 +16,15 @@ def __init__(self, recipientId, amount, vendorField=None, fee=None): fee (int, optional): fee used for the transaction (default is already set) """ super().__init__() + self.transaction.recipientId = recipientId + if type(amount) == int and amount > 0: - self.transaction.amount = amount + self.transaction.amount = amount else: - raise ValueError('Amount is not valid') + raise ValueError('Amount is not valid') + self.transaction.vendorField = vendorField.encode() if vendorField else None + if fee: self.transaction.fee = fee diff --git a/crypto/transactions/builder/vote.py b/crypto/transactions/builder/vote.py index 98fca254..baf9be9f 100644 --- a/crypto/transactions/builder/vote.py +++ b/crypto/transactions/builder/vote.py @@ -15,7 +15,9 @@ def __init__(self, vote, fee=None): fee (int, optional): fee used for the transaction (default is already set) """ super().__init__() + self.transaction.asset['votes'] = [vote] + if fee: self.transaction.fee = fee diff --git a/crypto/transactions/deserializer.py b/crypto/transactions/deserializer.py index 2499f287..c0d9f73f 100644 --- a/crypto/transactions/deserializer.py +++ b/crypto/transactions/deserializer.py @@ -3,13 +3,9 @@ from hashlib import sha256 from importlib import import_module -from binary.unsigned_integer.reader import read_bit32, read_bit64, read_bit8 +from binary.unsigned_integer.reader import read_bit8, read_bit16, read_bit32, read_bit64 -from crypto.constants import ( - TRANSACTION_MULTI_SIGNATURE_REGISTRATION, TRANSACTION_SECOND_SIGNATURE_REGISTRATION, - TRANSACTION_TYPES, TRANSACTION_VOTE -) -from crypto.identity.address import address_from_public_key +from crypto.constants import TRANSACTION_TYPES from crypto.transactions.deserializers.base import BaseDeserializer @@ -34,30 +30,29 @@ def deserialize(self): transaction = Transaction() transaction.version = read_bit8(self.serialized, offset=1) transaction.network = read_bit8(self.serialized, offset=2) - transaction.type = read_bit8(self.serialized, offset=3) - transaction.timestamp = read_bit32(self.serialized, offset=4) - transaction.senderPublicKey = hexlify(self.serialized)[16:66+16].decode() - transaction.fee = read_bit64(self.serialized, offset=41) + transaction.typeGroup = read_bit32(self.serialized, offset=3) + transaction.type = read_bit16(self.serialized, offset=7) + transaction.nonce = read_bit64(self.serialized, offset=9) + transaction.senderPublicKey = hexlify(self.serialized)[34:66+34].decode() + transaction.fee = read_bit64(self.serialized, offset=50) - vendor_field_length = read_bit8(self.serialized, offset=49) + vendor_field_length = read_bit8(self.serialized, offset=58) if vendor_field_length > 0: - vendor_field_offset = (49 + 8) * 2 + vendor_field_offset = (58 + 8) * 2 vendorField_take = vendor_field_length * 2 transaction.vendorFieldHex = hexlify( self.serialized )[vendor_field_offset:vendorField_take] - asset_offset = (49 + 1) * 2 + vendor_field_length * 2 + asset_offset = (58 + 1) * 2 + vendor_field_length * 2 - handled_tranasction = self._handle_transaction_type(asset_offset, transaction) - transaction.amount = handled_tranasction.amount - transaction.version = handled_tranasction.version - if transaction.version == 1: - transaction = self._handle_version_one(transaction) - elif transaction.version == 2: + handled_transaction = self._handle_transaction_type(asset_offset, transaction) + transaction.amount = handled_transaction.amount + transaction.version = handled_transaction.version + if transaction.version == 2: transaction = self._handle_version_two(transaction) else: - raise Exception('should this ever happen?') # todo: do we need this? + raise Exception('should this ever happen?') return transaction @@ -86,8 +81,8 @@ def _handle_transaction_type(self, asset_offset, transaction): break return deserializer(self.serialized, asset_offset, transaction).deserialize() - def _handle_version_one(self, transaction): - """Handle deserialization for version one + def _handle_version_two(self, transaction): + """Handle deserialization for version two Args: transaction (object): Transaction resource object @@ -95,44 +90,7 @@ def _handle_version_one(self, transaction): Returns: object: Transaction resource object of currently deserialized data """ - if transaction.secondSignature: - transaction.secondSignature = transaction.secondSignature - - if transaction.type is TRANSACTION_VOTE: - transaction.recipientId = address_from_public_key( - transaction.senderPublicKey, transaction.network - ) - - if transaction.type is TRANSACTION_MULTI_SIGNATURE_REGISTRATION: - transaction.asset['multisignature']['keysgroup'] = [ - '+{}'.format(key) for key in transaction.asset['multisignature']['keysgroup'] - ] - - if transaction.vendorFieldHex: - transaction.vendorField = unhexlify(transaction.vendorFieldHex) - if not transaction.id: - transaction.id = transaction.get_id() - - if transaction.type is TRANSACTION_SECOND_SIGNATURE_REGISTRATION: - transaction.recipientId = address_from_public_key( - transaction.senderPublicKey, transaction.network - ) - - if transaction.type is TRANSACTION_MULTI_SIGNATURE_REGISTRATION: - transaction.recipientId = address_from_public_key( - transaction.senderPublicKey, transaction.network - ) + transaction.id = sha256(unhexlify(transaction.serialize(False, True, False))).hexdigest() return transaction - - def _handle_version_two(self, transaction): - """Handle deserialization for version two - - Args: - transaction (object): Transaction resource object - - Returns: - object: Transaction resource object of currently deserialized data - """ - transaction.id = sha256(transaction) # todo serialize diff --git a/crypto/transactions/deserializers/delegate_registration.py b/crypto/transactions/deserializers/delegate_registration.py index e2372917..763641be 100644 --- a/crypto/transactions/deserializers/delegate_registration.py +++ b/crypto/transactions/deserializers/delegate_registration.py @@ -19,7 +19,8 @@ def deserialize(self): self.transaction.asset['delegate'] = {'username': username.decode()} self.transaction.parse_signatures( - hexlify(self.serialized), + hexlify(self.serialized).decode(), self.asset_offset + (username_length + 1) * 2 ) + return self.transaction diff --git a/crypto/transactions/deserializers/delegate_resignation.py b/crypto/transactions/deserializers/delegate_resignation.py new file mode 100644 index 00000000..eaf4bf8a --- /dev/null +++ b/crypto/transactions/deserializers/delegate_resignation.py @@ -0,0 +1,14 @@ +from binascii import hexlify + +from crypto.transactions.deserializers.base import BaseDeserializer + + +class DelegateResignationDeserializer(BaseDeserializer): + + def deserialize(self): + self.transaction.parse_signatures( + hexlify(self.serialized).decode(), + self.asset_offset + ) + + return self.transaction diff --git a/crypto/transactions/deserializers/htlc_claim.py b/crypto/transactions/deserializers/htlc_claim.py new file mode 100644 index 00000000..865b0259 --- /dev/null +++ b/crypto/transactions/deserializers/htlc_claim.py @@ -0,0 +1,24 @@ +from binascii import hexlify + +from crypto.transactions.deserializers.base import BaseDeserializer + + +class HtlcClaimDeserializer(BaseDeserializer): + + def deserialize(self): + starting_position = int(self.asset_offset / 2) + lock_transaction_id = hexlify(self.serialized[starting_position:starting_position + 32]) + + unlock_secret = self.serialized[starting_position + 32:starting_position + 64] + + self.transaction.asset['claim'] = { + 'lockTransactionId': lock_transaction_id.decode(), + 'unlockSecret': unlock_secret.decode() + } + + self.transaction.parse_signatures( + hexlify(self.serialized).decode(), + self.asset_offset + (64 * 2) + ) + + return self.transaction diff --git a/crypto/transactions/deserializers/htlc_lock.py b/crypto/transactions/deserializers/htlc_lock.py new file mode 100644 index 00000000..dfbd52bc --- /dev/null +++ b/crypto/transactions/deserializers/htlc_lock.py @@ -0,0 +1,41 @@ +from binascii import hexlify, unhexlify + +from base58 import b58encode_check + +from binary.unsigned_integer.reader import read_bit8, read_bit32, read_bit64 + +from crypto.transactions.deserializers.base import BaseDeserializer + + +class HtlcLockDeserializer(BaseDeserializer): + + def deserialize(self): + starting_position = int(self.asset_offset / 2) + + self.transaction.amount = read_bit64(self.serialized, offset=starting_position) + + secret_hash = hexlify(self.serialized)[(starting_position + 8) * 2:(starting_position + 8 + 32) * 2] + + expiration_type = read_bit8(self.serialized, offset=99) + + expiration_value = read_bit32(self.serialized, offset=100) + + recipient_start_index = (starting_position + 45) * 2 + recipientId = hexlify(self.serialized)[recipient_start_index:recipient_start_index + 42] + self.transaction.recipientId = b58encode_check(unhexlify(recipientId)).decode() + + self.transaction.asset['lock'] = { + 'secretHash': secret_hash.decode() + } + + self.transaction.asset['lock']['expiration'] = { + 'type': expiration_type, + 'value': expiration_value + } + + self.transaction.parse_signatures( + hexlify(self.serialized).decode(), + self.asset_offset + (8 + 32 + 1 + 4 + 21) * 2 + ) + + return self.transaction diff --git a/crypto/transactions/deserializers/htlc_refund.py b/crypto/transactions/deserializers/htlc_refund.py new file mode 100644 index 00000000..9260c1e0 --- /dev/null +++ b/crypto/transactions/deserializers/htlc_refund.py @@ -0,0 +1,22 @@ +from binascii import hexlify + +from crypto.transactions.deserializers.base import BaseDeserializer + + +class HtlcRefundDeserializer(BaseDeserializer): + + def deserialize(self): + starting_position = int(self.asset_offset) + + lock_transaction_id = hexlify(self.serialized)[starting_position:starting_position + 64] + + self.transaction.asset['refund'] = { + 'lockTransactionId': lock_transaction_id.decode() + } + + self.transaction.parse_signatures( + hexlify(self.serialized).decode(), + self.asset_offset + 64 + ) + + return self.transaction diff --git a/crypto/transactions/deserializers/ipfs.py b/crypto/transactions/deserializers/ipfs.py new file mode 100644 index 00000000..1cf91a3b --- /dev/null +++ b/crypto/transactions/deserializers/ipfs.py @@ -0,0 +1,38 @@ +from binascii import hexlify, unhexlify + +from base58 import b58encode + +from binary.unsigned_integer.reader import read_bit8 +from binary.unsigned_integer.writer import write_bit8 + +from crypto.transactions.deserializers.base import BaseDeserializer + + +class IPFSDeserializer(BaseDeserializer): + + def deserialize(self): + starting_position = int(self.asset_offset / 2) + + hash_function = read_bit8(self.serialized, starting_position) & 0xff + ipfs_hash_length = read_bit8(self.serialized, starting_position + 1) & 0xff + + start_index = (starting_position + 2) * 2 + end_index = start_index + (ipfs_hash_length * 2) + ipfs_hash = hexlify(self.serialized)[start_index:end_index] + + temp_buffer = bytes() + + temp_buffer += write_bit8(hash_function) + temp_buffer += write_bit8(ipfs_hash_length) + temp_buffer += unhexlify(ipfs_hash) + + self.transaction.asset = { + 'ipfs': b58encode(temp_buffer).decode() + } + + self.transaction.parse_signatures( + hexlify(self.serialized).decode(), + self.asset_offset + (ipfs_hash_length + 2) * 2 + ) + + return self.transaction diff --git a/crypto/transactions/deserializers/multi_payment.py b/crypto/transactions/deserializers/multi_payment.py new file mode 100644 index 00000000..f981f241 --- /dev/null +++ b/crypto/transactions/deserializers/multi_payment.py @@ -0,0 +1,37 @@ +from binascii import hexlify, unhexlify + +from base58 import b58encode_check + +from binary.unsigned_integer.reader import read_bit16, read_bit64 + +from crypto.transactions.deserializers.base import BaseDeserializer + + +class MultiPaymentDeserializer(BaseDeserializer): + + def deserialize(self): + starting_position = int(self.asset_offset / 2) + + payment_length = read_bit16(self.serialized, starting_position) & 0xff + + self.transaction.asset['payments'] = [] + + index = 0 + + for payment in range(payment_length): + amount = read_bit64(self.serialized, offset=starting_position + 2 + index) + + recipient_start_index = (starting_position + 10 + index) * 2 + recipientId = hexlify(self.serialized)[recipient_start_index:recipient_start_index + 42] + recipientId = b58encode_check(unhexlify(recipientId)).decode() + + self.transaction.asset['payments'].append({'amount': amount, 'recipientId': recipientId}) + + index += 21 + 8 + + self.transaction.parse_signatures( + hexlify(self.serialized).decode(), + self.asset_offset + 4 + (payment_length * (21 + 8)) * 2 + ) + + return self.transaction diff --git a/crypto/transactions/deserializers/multi_signature_registration.py b/crypto/transactions/deserializers/multi_signature_registration.py index 390fe776..f8cc46da 100644 --- a/crypto/transactions/deserializers/multi_signature_registration.py +++ b/crypto/transactions/deserializers/multi_signature_registration.py @@ -11,23 +11,27 @@ def deserialize(self): starting_position = int(self.asset_offset / 2) self.transaction.asset = { - 'multisignature': { + 'multiSignature': { 'min': read_bit8(self.serialized, starting_position) & 0xff, - 'lifetime': read_bit8(self.serialized, starting_position + 2) & 0xff, - 'keysgroup': [] + 'publicKeys': [] } } - count = read_bit8(self.serialized, starting_position + 3) & 0xff + count = read_bit8(self.serialized, starting_position + 1) & 0xff + for index in range(count): - index_start = int(self.asset_offset) + 6 + index_start = int(self.asset_offset) + 4 + if (index > 0): index_start += index * 66 - signature = hexlify(self.serialized)[index_start:index_start + 66].decode() - self.transaction.asset['multisignature']['keysgroup'].append(signature) + public_key = hexlify(self.serialized)[index_start:index_start + 66].decode() + self.transaction.asset['multiSignature']['publicKeys'].append(public_key) + + self.transaction.signatures = [] if self.transaction.signatures is None else self.transaction.signatures self.transaction.parse_signatures( - hexlify(self.serialized), - self.asset_offset + 6 + (count * 66) + hexlify(self.serialized).decode(), + self.asset_offset + 4 + (count * 66) ) + return self.transaction diff --git a/crypto/transactions/deserializers/second_signature_registration.py b/crypto/transactions/deserializers/second_signature_registration.py index 68cb8ec9..0b690818 100644 --- a/crypto/transactions/deserializers/second_signature_registration.py +++ b/crypto/transactions/deserializers/second_signature_registration.py @@ -7,12 +7,15 @@ class SecondSignatureRegistrationDeserializer(BaseDeserializer): def deserialize(self): starting_position = int(self.asset_offset) + public_key = hexlify(self.serialized)[starting_position:starting_position + 66] + self.transaction.asset = { 'signature': { 'publicKey': public_key.decode() } } - self.transaction.parse_signatures(hexlify(self.serialized), self.asset_offset + 66) + self.transaction.parse_signatures(hexlify(self.serialized).decode(), self.asset_offset + 66) + return self.transaction diff --git a/crypto/transactions/deserializers/transfer.py b/crypto/transactions/deserializers/transfer.py index 8dd3fec9..e77566fe 100644 --- a/crypto/transactions/deserializers/transfer.py +++ b/crypto/transactions/deserializers/transfer.py @@ -20,7 +20,8 @@ def deserialize(self): self.transaction.recipientId = b58encode_check(unhexlify(recipientId)).decode() self.transaction.parse_signatures( - hexlify(self.serialized), + hexlify(self.serialized).decode(), self.asset_offset + (8 + 4 + 21) * 2 ) + return self.transaction diff --git a/crypto/transactions/deserializers/vote.py b/crypto/transactions/deserializers/vote.py index a21cd3f8..eb32d919 100644 --- a/crypto/transactions/deserializers/vote.py +++ b/crypto/transactions/deserializers/vote.py @@ -26,7 +26,8 @@ def deserialize(self): self.transaction.asset['votes'].append(vote) self.transaction.parse_signatures( - hexlify(self.serialized), + hexlify(self.serialized).decode(), self.asset_offset + 2 + (vote_length * 34 * 2) ) + return self.transaction diff --git a/crypto/transactions/serializer.py b/crypto/transactions/serializer.py index 3bda1f10..b0e8100d 100644 --- a/crypto/transactions/serializer.py +++ b/crypto/transactions/serializer.py @@ -2,8 +2,8 @@ from binascii import hexlify, unhexlify from importlib import import_module -from binary.hex.writer import write_high, write_low -from binary.unsigned_integer.writer import write_bit32, write_bit64, write_bit8 +from binary.hex.writer import write_high +from binary.unsigned_integer.writer import write_bit8, write_bit16, write_bit32, write_bit64 from crypto.configuration.network import get_network from crypto.constants import TRANSACTION_TYPES @@ -20,7 +20,7 @@ def __init__(self, transaction): raise ArkSerializerException('No transaction data provided') self.transaction = transaction - def serialize(self): + def serialize(self, skip_signature=True, skip_second_signature=True, skip_multi_signature=True, raw=False): """Perform AIP11 compliant serialization Returns: @@ -28,29 +28,32 @@ def serialize(self): """ network_config = get_network() bytes_data = bytes() + bytes_data += write_bit8(0xff) - bytes_data += write_low(self.transaction.get('version') or 0x01) + + bytes_data += write_bit8(self.transaction.get('version') or 0x02) bytes_data += write_bit8(self.transaction.get('network') or network_config['version']) - bytes_data += write_low(self.transaction['type']) - bytes_data += write_bit32(self.transaction['timestamp']) - bytes_data += write_high(self.transaction['senderPublicKey']) - bytes_data += write_bit64(self.transaction['fee']) + bytes_data += write_bit32(self.transaction.get('typeGroup') or 0x01) + bytes_data += write_bit16(self.transaction.get('type')) + bytes_data += write_bit64(self.transaction.get('nonce') or 0x01) + + bytes_data += write_high(self.transaction.get('senderPublicKey')) + bytes_data += write_bit64(self.transaction.get('fee')) if self.transaction.get('vendorField'): - vendorFieldLength = len(self.transaction['vendorField']) + vendorFieldLength = len(self.transaction.get('vendorField')) bytes_data += write_bit8(vendorFieldLength) - bytes_data += self.transaction['vendorField'] + bytes_data += self.transaction['vendorField'].encode() elif self.transaction.get('vendorFieldHex'): vendorField_hex_length = len(self.transaction['vendorFieldHex']) bytes_data += write_bit8(vendorField_hex_length / 2) bytes_data += self.transaction['vendorFieldHex'] else: bytes_data += write_bit8(0x00) - bytes_data = self._handle_transaction_type(bytes_data) - bytes_data = self._handle_signature(bytes_data) + bytes_data = self._handle_signature(bytes_data, skip_signature, skip_second_signature, skip_multi_signature) - return hexlify(bytes_data).decode() + return bytes_data if raw else hexlify(bytes_data).decode() def _handle_transaction_type(self, bytes_data): """Serialize transaction specific data (eg. delegate registration) @@ -77,7 +80,7 @@ def _handle_transaction_type(self, bytes_data): break return serializer(self.transaction, bytes_data).serialize() - def _handle_signature(self, bytes_data): + def _handle_signature(self, bytes_data, skip_signature, skip_second_signature, skip_multi_signature): """Serialize signature data of the transaction Args: @@ -86,16 +89,15 @@ def _handle_signature(self, bytes_data): Returns: bytes: bytes string """ - if self.transaction.get('signature'): + if not skip_signature and self.transaction.get('signature'): bytes_data += unhexlify(self.transaction['signature']) - if self.transaction.get('secondSignature'): + if not skip_second_signature and self.transaction.get('secondSignature'): bytes_data += unhexlify(self.transaction['secondSignature']) elif self.transaction.get('signSignature'): bytes_data += unhexlify(self.transaction['signSignature']) - if self.transaction.get('signatures'): - bytes_data += write_bit8(0xff) + if not skip_multi_signature and self.transaction.get('signatures'): bytes_data += unhexlify(''.join(self.transaction['signatures'])) return bytes_data diff --git a/crypto/transactions/serializers/delegate_registration.py b/crypto/transactions/serializers/delegate_registration.py index fd6be01a..72a12b57 100644 --- a/crypto/transactions/serializers/delegate_registration.py +++ b/crypto/transactions/serializers/delegate_registration.py @@ -11,6 +11,8 @@ class DelegateRegistrationSerializer(BaseSerializer): def serialize(self): delegate_bytes = hexlify(self.transaction['asset']['delegate']['username'].encode()) + self.bytes_data += write_bit8(len(delegate_bytes) // 2) self.bytes_data += unhexlify(delegate_bytes) + return self.bytes_data diff --git a/crypto/transactions/serializers/htlc_claim.py b/crypto/transactions/serializers/htlc_claim.py new file mode 100644 index 00000000..6ab1804a --- /dev/null +++ b/crypto/transactions/serializers/htlc_claim.py @@ -0,0 +1,15 @@ +from binascii import hexlify, unhexlify + +from crypto.transactions.serializers.base import BaseSerializer + + +class HtlcClaimSerializer(BaseSerializer): + """Serializer handling timelock claim data + """ + + def serialize(self): + self.bytes_data += unhexlify(self.transaction['asset']['claim']['lockTransactionId']) + unlock_secret = hexlify(self.transaction['asset']['claim']['unlockSecret'].encode()) + self.bytes_data += unhexlify(unlock_secret) + + return self.bytes_data diff --git a/crypto/transactions/serializers/htlc_lock.py b/crypto/transactions/serializers/htlc_lock.py new file mode 100644 index 00000000..30fb2ec4 --- /dev/null +++ b/crypto/transactions/serializers/htlc_lock.py @@ -0,0 +1,24 @@ +from binascii import hexlify, unhexlify + +from base58 import b58decode_check + +from binary.hex.writer import write_high +from binary.unsigned_integer.writer import write_bit8, write_bit32, write_bit64 + +from crypto.transactions.serializers.base import BaseSerializer + + +class HtlcLockSerializer(BaseSerializer): + """Serializer handling timelock transfer data + """ + + def serialize(self): + self.bytes_data += write_bit64(self.transaction['amount']) + self.bytes_data += unhexlify(self.transaction['asset']['lock']['secretHash'].encode()) + self.bytes_data += write_bit8(self.transaction['asset']['lock']['expiration']['type']) + self.bytes_data += write_bit32(self.transaction['asset']['lock']['expiration']['value']) + + recipientId = hexlify(b58decode_check(self.transaction['recipientId'])) + self.bytes_data += write_high(recipientId) + + return self.bytes_data diff --git a/crypto/transactions/serializers/htlc_refund.py b/crypto/transactions/serializers/htlc_refund.py new file mode 100644 index 00000000..1ef2ce52 --- /dev/null +++ b/crypto/transactions/serializers/htlc_refund.py @@ -0,0 +1,12 @@ +from binascii import unhexlify + +from crypto.transactions.serializers.base import BaseSerializer + + +class HtlcRefundSerializer(BaseSerializer): + """Serializer handling timelock refund data + """ + + def serialize(self): + self.bytes_data += unhexlify(self.transaction['asset']['refund']['lockTransactionId'].encode()) + return self.bytes_data diff --git a/crypto/transactions/serializers/ipfs.py b/crypto/transactions/serializers/ipfs.py index fc24ff4e..88dd629b 100644 --- a/crypto/transactions/serializers/ipfs.py +++ b/crypto/transactions/serializers/ipfs.py @@ -1,6 +1,8 @@ -from binascii import unhexlify +from binascii import hexlify -from binary.unsigned_integer.writer import write_bit8 +from base58 import b58decode + +from binary.hex.writer import write_high from crypto.transactions.serializers.base import BaseSerializer @@ -10,7 +12,8 @@ class IPFSSerializer(BaseSerializer): """ def serialize(self): - dag = self.transaction['asset']['ipfs']['dag'] - self.bytes_data += write_bit8(len(dag) // 2) - self.bytes_data += unhexlify(dag) + ipfs = hexlify(b58decode(self.transaction['asset']['ipfs'])) + + self.bytes_data += write_high(ipfs) + return self.bytes_data diff --git a/crypto/transactions/serializers/multi_payment.py b/crypto/transactions/serializers/multi_payment.py index a76ec8b7..f8a53f5e 100644 --- a/crypto/transactions/serializers/multi_payment.py +++ b/crypto/transactions/serializers/multi_payment.py @@ -3,7 +3,7 @@ from base58 import b58decode_check from binary.hex.writer import write_high -from binary.unsigned_integer.writer import write_bit32, write_bit64 +from binary.unsigned_integer.writer import write_bit16, write_bit64 from crypto.transactions.serializers.base import BaseSerializer @@ -13,9 +13,11 @@ class MultiPaymentSerializer(BaseSerializer): """ def serialize(self): - self.bytes_data += write_bit32(len(self.transaction['asset']['payments'])) + self.bytes_data += write_bit16(len(self.transaction['asset']['payments'])) + for payment in self.transaction['asset']['payments']: self.bytes_data += write_bit64(payment['amount']) - recipientId = b58decode_check(hexlify(payment['recipientId']).encode()) + recipientId = hexlify(b58decode_check(payment['recipientId'])) self.bytes_data += write_high(recipientId) + return self.bytes_data diff --git a/crypto/transactions/serializers/multi_signature_registration.py b/crypto/transactions/serializers/multi_signature_registration.py index cac6a7cc..b87fe0bc 100644 --- a/crypto/transactions/serializers/multi_signature_registration.py +++ b/crypto/transactions/serializers/multi_signature_registration.py @@ -5,19 +5,15 @@ from crypto.transactions.serializers.base import BaseSerializer -class DelegateRegistrationSerializer(BaseSerializer): +class MultiSignatureSerializer(BaseSerializer): """Serializer handling delegate registration data """ def serialize(self): - keysgroup = [] - if self.transaction.get('version') is None or self.transaction['version'] == 1: - for key in self.transaction['asset']['multisignature']['keysgroup']: - keysgroup.append(key[1::] if key.startswith('+') else key) - else: - keysgroup = self.transaction['asset']['multisignature']['keysgroup'] - self.bytes_data += write_bit8(self.transaction['asset']['multisignature']['min']) - self.bytes_data += write_bit8(len(self.transaction['asset']['multisignature']['keysgroup'])) - self.bytes_data += write_bit8(self.transaction['asset']['multisignature']['lifetime']) - self.bytes_data += unhexlify(''.join(keysgroup)) + public_keys_length = len(self.transaction['asset']['multiSignature']['publicKeys']) + self.bytes_data += write_bit8(self.transaction['asset']['multiSignature']['min']) + self.bytes_data += write_bit8(public_keys_length) + for key in self.transaction['asset']['multiSignature']['publicKeys']: + self.bytes_data += unhexlify(key.encode()) + return self.bytes_data diff --git a/crypto/transactions/serializers/timelock_transfer.py b/crypto/transactions/serializers/timelock_transfer.py deleted file mode 100644 index 48f0cd77..00000000 --- a/crypto/transactions/serializers/timelock_transfer.py +++ /dev/null @@ -1,21 +0,0 @@ -from binascii import hexlify - -from base58 import b58decode_check - -from binary.hex.writer import write_high, write_low -from binary.unsigned_integer.writer import write_bit32, write_bit64 - -from crypto.transactions.serializers.base import BaseSerializer - - -class TimelockTransferSerializer(BaseSerializer): - """Serializer handling timelock transfer data - """ - - def serialize(self): - self.bytes_data += write_bit64(self.transaction['recipientId']['amount']) - self.bytes_data += write_low(self.transaction['recipientId']['timelocktype']) - self.bytes_data += write_bit32(self.transaction['recipientId']['timelock']) - recipientId = hexlify(b58decode_check(self.transaction['recipientId'])) - self.bytes_data += write_high(recipientId) - return self.bytes_data diff --git a/crypto/transactions/serializers/transfer.py b/crypto/transactions/serializers/transfer.py index eb3f6447..c6c66ba8 100644 --- a/crypto/transactions/serializers/transfer.py +++ b/crypto/transactions/serializers/transfer.py @@ -17,4 +17,5 @@ def serialize(self): self.bytes_data += write_bit32(self.transaction.get('expiration', 0)) recipientId = hexlify(b58decode_check(self.transaction['recipientId'])) self.bytes_data += write_high(recipientId) + return self.bytes_data diff --git a/crypto/transactions/serializers/vote.py b/crypto/transactions/serializers/vote.py index e6a7498a..82574794 100644 --- a/crypto/transactions/serializers/vote.py +++ b/crypto/transactions/serializers/vote.py @@ -11,11 +11,14 @@ class VoteSerializer(BaseSerializer): def serialize(self): vote_bytes = [] + for vote in self.transaction['asset']['votes']: if vote.startswith('+'): vote_bytes.append('01{}'.format(vote[1::])) else: vote_bytes.append('00{}'.format(vote[1::])) + self.bytes_data += write_bit8(len(self.transaction['asset']['votes'])) self.bytes_data += unhexlify(''.join(vote_bytes)) + return self.bytes_data diff --git a/crypto/transactions/transaction.py b/crypto/transactions/transaction.py index 2c634fb1..e2ce9369 100644 --- a/crypto/transactions/transaction.py +++ b/crypto/transactions/transaction.py @@ -1,23 +1,18 @@ import json from binascii import unhexlify from hashlib import sha256 -from struct import pack - -from base58 import b58decode_check from binary.hex.writer import write_high -from binary.unsigned_integer.writer import write_bit32, write_bit64, write_bit8 +from binary.unsigned_integer.writer import write_bit8 from crypto.constants import ( TRANSACTION_DELEGATE_REGISTRATION, TRANSACTION_MULTI_SIGNATURE_REGISTRATION, TRANSACTION_SECOND_SIGNATURE_REGISTRATION, TRANSACTION_VOTE ) from crypto.exceptions import ArkInvalidTransaction +from crypto.schnorr import schnorr from crypto.transactions.deserializer import Deserializer from crypto.transactions.serializer import Serializer -from crypto.utils.message import Message -from crypto.utils.slot import get_time - TRANSACTION_ATTRIBUTES = { 'amount': 0, @@ -31,11 +26,15 @@ 'signature': None, 'signatures': None, 'signSignature': None, - 'timestamp': get_time, + 'nonce': None, 'type': None, + 'typeGroup': None, 'vendorField': None, 'vendorFieldHex': None, 'version': None, + 'lockTransactionId': None, + 'lockSecret': None, + 'expiration': None } @@ -55,7 +54,7 @@ def get_id(self): Returns: str: """ - return sha256(self.to_bytes(False, False)).hexdigest() + return sha256(self.to_bytes(skip_signature=False, skip_second_signature=False, skip_multi_signature=False)).hexdigest() def to_dict(self): """Convert the transaction into a dictionary representation @@ -76,45 +75,21 @@ def to_json(self): data = self.to_dict() return json.dumps(data) - def to_bytes(self, skip_signature=True, skip_second_signature=True): + def to_bytes(self, skip_signature=True, skip_second_signature=True, skip_multi_signature=True): """Convert the transaction to its byte representation Args: skip_signature (bool, optional): do you want to skip the signature skip_second_signature (bool, optional): do you want to skip the 2nd signature + skip_multi_signature (bool, optional): do you want to skip multi signature Returns: bytes: bytes representation of the transaction """ - bytes_data = bytes() - bytes_data += write_bit8(self.type) - bytes_data += write_bit32(self.timestamp) - bytes_data += write_high(self.senderPublicKey) - - skip_recipient_id = self.type in [ - TRANSACTION_SECOND_SIGNATURE_REGISTRATION, - TRANSACTION_MULTI_SIGNATURE_REGISTRATION - ] - - if self.recipientId and not skip_recipient_id: - bytes_data += b58decode_check(self.recipientId) - else: - bytes_data += pack('21x') - - if self.vendorField and len(self.vendorField) <= 255: - bytes_data += self.vendorField - if len(self.vendorField) < 64: - bytes_data += pack('{}x'.format(64 - len(self.vendorField))) - else: - bytes_data += pack('64x') - - bytes_data += write_bit64(self.amount) - bytes_data += write_bit64(self.fee) - - bytes_data = self._handle_transaction_type(bytes_data) - bytes_data = self._handle_signature(bytes_data, skip_signature, skip_second_signature) - - return bytes_data + return Serializer(self.to_dict()).serialize(skip_signature=skip_signature, + skip_second_signature=skip_second_signature, + skip_multi_signature=skip_multi_signature, + raw=True) def parse_signatures(self, serialized, start_offset): """Parse the signature, second signature and multi signatures @@ -127,78 +102,77 @@ def parse_signatures(self, serialized, start_offset): None: methods returns nothing """ - self.signature = serialized[start_offset:] - multi_signature_offset = 0 - - if not len(self.signature): - self.signature = None - return - - signature_length = int(self.signature[2:4], base=16) + 2 - self.signature = serialized[start_offset: start_offset + (signature_length * 2)] - multi_signature_offset += signature_length * 2 - self.signSignature = serialized[start_offset + (signature_length * 2):] + signature_end_offset = start_offset + (64 * 2) - if not self.signSignature: - self.signSignature = None - elif 'ff' == self.signSignature[:2]: - self.signSignature = None - else: - secondSignature_length = int(self.signSignature[2:4], base=16) - self.signSignature = self.signSignature[:secondSignature_length * 2] - multi_signature_offset += secondSignature_length * 2 + if len(serialized) - signature_end_offset % 65 != 0: + self.signature = serialized[start_offset:signature_end_offset] - signatures = serialized[:start_offset + multi_signature_offset] + second_signature_end_offset = signature_end_offset + (64 * 2) + if len(serialized) - signature_end_offset > 0 and (len(serialized) - signature_end_offset) % 64 == 0: + self.signSignature = serialized[signature_end_offset:second_signature_end_offset] - if not signatures: - return + if len(serialized) - second_signature_end_offset > 0 and (len(serialized) - signature_end_offset) % 65 == 0: + multi_sig_part = serialized[signature_end_offset:] + index = 0 + index_size = 2 + signature_size = 128 - if 'ff' != signatures[:2]: - return + while index != len(multi_sig_part): + signature_index = multi_sig_part[index:index + index_size] + signature = multi_sig_part[index + index_size:index + index_size + signature_size] + index += index_size + signature_size + signature_formatted = signature_index + signature + self.signatures.append(signature_formatted) - signatures = signatures[2:] - self.signatures = [] + return - while True: - mlength = int(signatures[2:4], base=16) - if mlength > 0: - self.signatures.append(signatures[:(mlength + 2) * 2]) - else: - break + def serialize(self, skip_signature=True, skip_second_signature=True, skip_multi_signature=True): + """Perform AIP11 compliant serialization. - signatures = signatures[(mlength + 2) * 2:] - if not signatures: - break + Args: + skip_signature (bool, optional): do you want to skip the signature + skip_second_signature (bool, optional): do you want to skip the 2nd signature + skip_multi_signature (bool, optional): do you want to skip multi signature - def serialize(self): + Returns: + str: Serialized string + """ data = self.to_dict() - return Serializer(data).serialize() + return Serializer(data).serialize(skip_signature, skip_second_signature, skip_multi_signature) def deserialize(self, serialized): + """Perform AIP11 compliant deserialization. + + Args: + serialized (str): parses a given serialized string + + Returns: + crypto.transactions.transaction.Transaction: Transaction + """ return Deserializer(serialized).deserialize() - def verify(self): - """Verify the transaction. Method will raise an exception if invalid, if it's valid nothing - will happen. + def verify_schnorr(self): + """Verify the transaction. Method will raise an exception if invalid, if it's valid it will + returns True """ - transaction = self.to_bytes() - message = Message(message=transaction, signature=self.signature, publickey=self.senderPublicKey) - is_valid = message.verify() + is_valid = schnorr.b410_schnorr_verify(self.to_bytes(), self.senderPublicKey, self.signature) + if not is_valid: raise ArkInvalidTransaction('Transaction could not be verified') - def second_verify(self, passphrase): - """Verify the transaction using the 2nd passphrase + return is_valid - Args: - passphrase (str): 2nd passphrase associated with the account sending this transaction + def verify_schnorr_multisig(self): + """Verify the multisignatures transaction. Method will raise an exception if invalid, it will + returns True """ - transaction = sha256(self.to_bytes()).digest() - message = Message(transaction, self.signSignature, self.senderPublicKey) - is_valid = message.verify() + is_valid = schnorr.b410_schnorr_verify(self.to_bytes(True, True, False), self.senderPublicKey, self.signature) + if not is_valid: raise ArkInvalidTransaction('Transaction could not be verified') + return is_valid + def _handle_transaction_type(self, bytes_data): """Handled each transaction type differently @@ -217,12 +191,12 @@ def _handle_transaction_type(self, bytes_data): elif self.type == TRANSACTION_VOTE: bytes_data += ''.join(self.asset['votes']).encode() elif self.type == TRANSACTION_MULTI_SIGNATURE_REGISTRATION: - bytes_data += write_bit8(self.asset['multisignature']['min']) - bytes_data += write_bit8(self.asset['multisignature']['lifetime']) - bytes_data += ''.join(self.asset['multisignature']['keysgroup']).encode() + bytes_data += write_bit8(self.asset['multiSignature']['min']) + bytes_data += ''.join(self.asset['multiSignature']['publicKeys']).encode() + return bytes_data - def _handle_signature(self, bytes_data, skip_signature, skip_second_signature): + def _handle_signature(self, bytes_data, skip_signature, skip_second_signature, skip_multi_signature): """Internal method, used to handle the signature Args: @@ -238,4 +212,6 @@ def _handle_signature(self, bytes_data, skip_signature, skip_second_signature): bytes_data += write_high(self.signature) if not skip_second_signature and self.signSignature: bytes_data += write_high(self.signSignature) + if not skip_multi_signature and self.signatures: + bytes_data += write_high(self.signatures) return bytes_data diff --git a/tests/conftest.py b/tests/conftest.py index f425dfa1..151b1e94 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,18 +6,19 @@ def transaction_type_0(): """Transaction of type "transfer" """ data = { - 'version': 1, - 'network': 30, + 'version': 2, + 'network': 23, + 'typeGroup': 1, 'type': 0, - 'timestamp': 0, - 'senderPublicKey': '03cb7bca143376721d0e9e3f3ccb0dc2e7e8470c06e630c3cef73f03e309b558ad', - 'fee': 0, - 'amount': 12500000000000000, + 'nonce': 1, + 'senderPublicKey': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', + 'fee': 10000000, + 'amount': 200000000, 'expiration': 0, - 'recipientId': 'DGihocTkwDygiFvmg6aG8jThYTic47GzU9', - 'signature': '3044022016ecdf3039e69514c7d75861b22fc076496b61c07a1fcf793dc4f5c76fa0532b0220579c4c0c9d13720f9db5d9df29ed8ceab0adc266c6c160d612d4894dc5867eb1', # noqa - 'id': 'e40ce11cab82736da1cc91191716f3c1f446ca7b6a9f4f93b7120ef105ba06e8', - 'serialized': 'ff011e000000000003cb7bca143376721d0e9e3f3ccb0dc2e7e8470c06e630c3cef73f03e309b558ad0000000000000000000040b10baf682c00000000001e7f048c40fd8a0442ffe79e0aa804f27fd5db15943044022016ecdf3039e69514c7d75861b22fc076496b61c07a1fcf793dc4f5c76fa0532b0220579c4c0c9d13720f9db5d9df29ed8ceab0adc266c6c160d612d4894dc5867eb1' # noqa + 'recipientId': 'AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', + 'signature': '136c29d921b58ae3194020b82e9808f9cd54f7178cb34678f570f28226b8e56ba0ad318297a3bacbb37ab22ddaa5dbf1901cda3ec2d2bca5ce98d6407839ab9b', # noqa + 'id': '129517023bd895b682bbb38b1d1f99e9222bd487899c843da22d8572b0fb52a8', + 'serialized': 'ff02170100000000000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19280969800000000000000c2eb0b0000000000000000170995750207ecaf0ccf251c1265b92ad84f553662136c29d921b58ae3194020b82e9808f9cd54f7178cb34678f570f28226b8e56ba0ad318297a3bacbb37ab22ddaa5dbf1901cda3ec2d2bca5ce98d6407839ab9b' # noqa } return data @@ -27,22 +28,22 @@ def transaction_type_1(): """Transaction of type "second signature registration" """ data = { - 'version': 1, - 'network': 30, + 'version': 2, + 'network': 23, + 'typeGroup': 1, 'type': 1, - 'timestamp': 4895203, - 'senderPublicKey': '03a02b9d5fdd1307c2ee4652ba54d492d1fd11a7d1bb3f3a44c4a05e79f19de933', + 'nonce': 1, + 'senderPublicKey': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', 'fee': 500000000, 'asset': { 'signature': { - 'publicKey': '0292d580f200d041861d78b3de5ff31c6665b7a092ac3890d9132593beb9aa8513' + 'publicKey': '03699e966b2525f9088a6941d8d94f7869964a000efe65783d78ac82e1199fe609' } }, - 'signature': '3045022100e4fe1f3fb2845ad5f6ab377f247ffb797661d7516626bdc1d2f0f73eca582b4d02200ada103bdbff439d57c7aaa266f30ce74ff4385f0c77a486070033061b71650c', # noqa + 'signature': 'f9a1e2244c8318e8be85482fc02659e5c1775d246d73d5d0699ae4a1d5e3a3e84f9dcf68ee015f943d2a82eb829f35abd7901279761d96f6b43431520e955c67', # noqa 'amount': 0, - 'recipientId': 'D7seWn8JLVwX4nHd9hh2Lf7gvZNiRJ7qLk', - 'id': '62c36be3e5176771a476d813f64082a8f4e3861c0356438bdf1cc91eebcc9b0d', - 'serialized': 'ff011e01e3b14a0003a02b9d5fdd1307c2ee4652ba54d492d1fd11a7d1bb3f3a44c4a05e79f19de9330065cd1d00000000000292d580f200d041861d78b3de5ff31c6665b7a092ac3890d9132593beb9aa85133045022100e4fe1f3fb2845ad5f6ab377f247ffb797661d7516626bdc1d2f0f73eca582b4d02200ada103bdbff439d57c7aaa266f30ce74ff4385f0c77a486070033061b71650c' # noqa + 'id': '173a3230159b45d772b2e0348f42af53913bf3e376397f29b8e0bda290badbe4', + 'serialized': 'ff02170100000001000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed1920065cd1d000000000003699e966b2525f9088a6941d8d94f7869964a000efe65783d78ac82e1199fe609f9a1e2244c8318e8be85482fc02659e5c1775d246d73d5d0699ae4a1d5e3a3e84f9dcf68ee015f943d2a82eb829f35abd7901279761d96f6b43431520e955c67' # noqa } return data @@ -52,21 +53,22 @@ def transaction_type_2(): """Transaction of type "delegate registration" """ data = { - 'version': 1, - 'network': 30, + 'version': 2, + 'network': 23, + 'typeGroup': 1, 'type': 2, - 'timestamp': 0, - 'senderPublicKey': '03e5b39a83e6c7c952c5908089d4524bb8dda93acc2b2b953247e43dc4fe9aa3d1', - 'fee': 0, + 'nonce': 1, + 'senderPublicKey': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', + 'fee': 2500000000, 'asset': { 'delegate': { - 'username': 'genesis_1' + 'username': 'boldninja' } }, - 'signature': '3045022100e3e38811778023e6f17fefd447f179d45ab92c398c7cfb1e34e2f6e1b167c95a022070c36439ecec0fc3c43850070f29515910435d389e059579878d61b5ff2ea337', # noqa + 'signature': 'eaf4b4dfd7903c32cf6c145ddf0744e86536719f5790b4286b08f1a10f0ad183bc601efc8a49a2a7b41758601a1793693afa1781cf0a63a8f72b08d5a1aaba1e', # noqa 'amount': 0, - 'id': 'eb0146ac79afc228f0474a5ae1c4771970ae7880450b998c401029f522cd8a21', - 'serialized': 'ff011e020000000003e5b39a83e6c7c952c5908089d4524bb8dda93acc2b2b953247e43dc4fe9aa3d10000000000000000000967656e657369735f313045022100e3e38811778023e6f17fefd447f179d45ab92c398c7cfb1e34e2f6e1b167c95a022070c36439ecec0fc3c43850070f29515910435d389e059579878d61b5ff2ea337' # noqa + 'id': 'cfd113d8cd9fd46b07030c14fac38c1d3fc0eca991e999eab9d0152ea96ab0dc', + 'serialized': 'ff02170100000002000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19200f90295000000000009626f6c646e696e6a61eaf4b4dfd7903c32cf6c145ddf0744e86536719f5790b4286b08f1a10f0ad183bc601efc8a49a2a7b41758601a1793693afa1781cf0a63a8f72b08d5a1aaba1e' # noqa } return data @@ -76,20 +78,20 @@ def transaction_type_3(): """Transaction of type "vote" """ data = { - 'version': 1, - 'network': 30, + 'version': 2, + 'network': 23, + 'typeGroup': 1, 'type': 3, - 'timestamp': 4349399, - 'senderPublicKey': '0374e9a97611540a9ce4812b0980e62d3c5141ea964c2cab051f14a78284570dcd', + 'nonce': 1, + 'senderPublicKey': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', 'fee': 100000000, 'asset': { - 'votes': ['+02dcb94d73fb54e775f734762d26975d57f18980314f3b67bc52beb393893bc706'] + 'votes': ['+022cca9529ec97a772156c152a00aad155ee6708243e65c9d211a589cb5d43234d'] }, - 'signature': '3045022100af1e5d6f3c9eff8699192ad1b827e7cf7c60040bd2f704360a1f1fbadf6bc1cf022048238b7175369861436d895adaeeeb31ceb453e543dbf20218a4a5b688650482', # noqa + 'signature': '86007f8e6a982bc271ec063c20f158734f0bc1e23e0e1abf9edeaa208b4810fa1d466171bba79a5c00b0a4c698728f68aa0748d98613cac247c014ee84a6fc41', # noqa 'amount': 0, - 'recipientId': 'DRac35wghMcmUSe5jDMLBDLWkVVjyKZFxK', - 'id': 'a430dbe34172d205ec251875b14438e58e4bd6cf4efc1ebb3da4c206b002115b', - 'serialized': 'ff011e03d75d42000374e9a97611540a9ce4812b0980e62d3c5141ea964c2cab051f14a78284570dcd00e1f5050000000000010102dcb94d73fb54e775f734762d26975d57f18980314f3b67bc52beb393893bc7063045022100af1e5d6f3c9eff8699192ad1b827e7cf7c60040bd2f704360a1f1fbadf6bc1cf022048238b7175369861436d895adaeeeb31ceb453e543dbf20218a4a5b688650482' # noqa + 'id': '2c5d71028607674411c8e37e316a015eccbeb9ba486fddfbd393dc421540a90a', + 'serialized': 'ff02170100000003000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19200e1f50500000000000101022cca9529ec97a772156c152a00aad155ee6708243e65c9d211a589cb5d43234d86007f8e6a982bc271ec063c20f158734f0bc1e23e0e1abf9edeaa208b4810fa1d466171bba79a5c00b0a4c698728f68aa0748d98613cac247c014ee84a6fc41' # noqa } return data @@ -99,35 +101,32 @@ def transaction_type_4(): """Transaction of type "multi signature registration" """ data = { - 'version': 1, + 'version': 2, 'network': 23, - 'id': 'cbd6862966bb1b03ba742397b7e5a88d6eefb393a362ead0d605723b840db2af', - 'blockid': '1844069042066945391', + 'typeGroup': 1, 'type': 4, - 'timestamp': 10112114, + 'nonce': 1, + 'senderPublicKey': '0205d9bbe71c343ac9a6a83a4344fd404c3534fc7349827097d0835d160bc2b896', + 'id': 'c868aad20165a336c35e324378f0c12008d18af4c1025291efcb7539c7c917f0', 'amount': 0, 'fee': 2000000000, - 'senderId': 'AMw3TiLrmVmwmFVwRzn96kkUsUpFTqsAEX', - 'senderPublicKey': '036928c98ee53a1f52ed01dd87db10ffe1980eb47cd7c0a7d688321f47b5d7d760', - 'signature': '30440220324d89c5792e4a54ae70b4f1e27e2f87a8b7169cc6f2f7b2c83dba894960f987022053b8d0ae23ff9d1769364db7b6fd03216d93753c82a711c3558045e787bc01a5', # noqa - 'signSignature': '304402201fcd54a9ac9c0269b8cec213566ddf43207798e2cf9ca1ce3c5d315d66321c6902201aa94c4ed3e5e479a12220aa886b259e488eb89b697c711f91e8c03b9620e0b1', # noqa - 'signatures': [ - '304502210097f17c8eecf36f86a967cc52a83fa661e4ffc70cc4ea08df58673669406d424c0220798f5710897b75dda42f6548f841afbe4ed1fa262097112cf5a1b3f7dade60e4', # noqa - '304402201a4a4c718bfdc699bbb891b2e89be018027d2dcd10640b5ddf07802424dab78e02204ec7c7d505d2158c3b51fdd3843d16aecd2eaaa4c6c7a555ef123c5e59fd41fb', # noqa - '304402207e660489bced5ce80c33d45c86781b63898775ab4a231bb48780f97b40073a63022026f0cefd0d83022d822522ab4366a82e3b89085c328817919939f2efeabd913d' # noqa - ], + 'signature': 'f5e9859c955bf8917b308ea21c88daf58661686c2017e476dcf735ad7f00aebf8e6effda3fe99e5f33f6007db7db9c9155796d9b5d31c53bd6156364a6a765d0', # noqa 'asset': { - 'multisignature': { - 'min': 2, - 'keysgroup': [ - '+03543c6cc3545be6bac09c82721973a052c690658283472e88f24d14739f75acc8', - '+0276dc5b8706a85ca9fdc46e571ac84e52fbb48e13ec7a165a80731b44ae89f1fc', - '+02e8d5d17eb17bbc8d7bf1001d29a2d25d1249b7bb7a5b7ad8b7422063091f4b31' + 'multiSignature': { + 'publicKeys': [ + '0205d9bbe71c343ac9a6a83a4344fd404c3534fc7349827097d0835d160bc2b896', + '03df0a1eb42d99b5de395cead145ba1ec2ea837be308c7ce3a4e8018b7efc7fdb8', + '03860d76b1df09659ac282cea3da5bd84fc45729f348a4a8e5f802186be72dc17f' ], - 'lifetime': 24 + 'min': 2, } }, - 'serialized': 'ff011704724c9a00036928c98ee53a1f52ed01dd87db10ffe1980eb47cd7c0a7d688321f47b5d7d76000943577000000000002031803543c6cc3545be6bac09c82721973a052c690658283472e88f24d14739f75acc80276dc5b8706a85ca9fdc46e571ac84e52fbb48e13ec7a165a80731b44ae89f1fc02e8d5d17eb17bbc8d7bf1001d29a2d25d1249b7bb7a5b7ad8b7422063091f4b3130440220324d89c5792e4a54ae70b4f1e27e2f87a8b7169cc6f2f7b2c83dba894960f987022053b8d0ae23ff9d1769364db7b6fd03216d93753c82a711c3558045e787bc01a5304402201fcd54a9ac9c0269b8cec213566ddf43207798e2cf9ca1ce3c5d315d66321c6902201aa94c4ed3e5e479a12220aa886b259e488eb89b697c711f91e8c03b9620e0b1ff304502210097f17c8eecf36f86a967cc52a83fa661e4ffc70cc4ea08df58673669406d424c0220798f5710897b75dda42f6548f841afbe4ed1fa262097112cf5a1b3f7dade60e4304402201a4a4c718bfdc699bbb891b2e89be018027d2dcd10640b5ddf07802424dab78e02204ec7c7d505d2158c3b51fdd3843d16aecd2eaaa4c6c7a555ef123c5e59fd41fb304402207e660489bced5ce80c33d45c86781b63898775ab4a231bb48780f97b40073a63022026f0cefd0d83022d822522ab4366a82e3b89085c328817919939f2efeabd913d' # noqa + 'signatures': [ + '0064900cb2cc3db6ca9c7e3bd363b322cdc4a39e051f655e9867935e1bb856b6dcce52845c031c690808f40340bc827bbaacd7b04bceff866cb0d386ab84715174', # noqa + '01dd363ccc101a958bded1a5db1c08f13283fc7cee53da93dfe00785eb406512467ff8e445f8ad843744ac4179f30f942645dfd5bdf5f2bfc344ad02393053880a', # noqa + '02d0012f035dc3fd54173c83d40217914653488fe9ce592dca34234163181d255281f2be7033725cfc4a6786509e7fabbaf0be8cf50882fc7b66fe94f259fd004e' # noqa + ], + 'serialized': 'ff021701000000040001000000000000000205d9bbe71c343ac9a6a83a4344fd404c3534fc7349827097d0835d160bc2b89600943577000000000002030205d9bbe71c343ac9a6a83a4344fd404c3534fc7349827097d0835d160bc2b89603df0a1eb42d99b5de395cead145ba1ec2ea837be308c7ce3a4e8018b7efc7fdb803860d76b1df09659ac282cea3da5bd84fc45729f348a4a8e5f802186be72dc17ff5e9859c955bf8917b308ea21c88daf58661686c2017e476dcf735ad7f00aebf8e6effda3fe99e5f33f6007db7db9c9155796d9b5d31c53bd6156364a6a765d00064900cb2cc3db6ca9c7e3bd363b322cdc4a39e051f655e9867935e1bb856b6dcce52845c031c690808f40340bc827bbaacd7b04bceff866cb0d386ab8471517401dd363ccc101a958bded1a5db1c08f13283fc7cee53da93dfe00785eb406512467ff8e445f8ad843744ac4179f30f942645dfd5bdf5f2bfc344ad02393053880a02d0012f035dc3fd54173c83d40217914653488fe9ce592dca34234163181d255281f2be7033725cfc4a6786509e7fabbaf0be8cf50882fc7b66fe94f259fd004e' # noqa } return data @@ -137,41 +136,154 @@ def transaction_type_5(): """Transaction of type "ipfs" """ data = { - 'data': {}, - 'serialised': '' + 'version': 2, + 'network': 23, + 'typeGroup': 1, + 'type': 5, + 'nonce': 1, + 'senderPublicKey': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', + 'fee': 500000000, + 'amount': 0, + 'asset': { + 'ipfs': 'QmR45FmbVVrixReBwJkhEKde2qwHYaQzGxu4ZoDeswuF9w' + }, + 'signature': '0b6e81b123de99e953d3073a8760d3213ab5f5cf512e65a2dd73aebb410966d8fbc59e775deb4f23c51be0847402b5e1d4ee68732b3e6d8e8914d259d7e373eb', + 'id': '818228ce634b46c488f3b2df8fd02bd50331ebdedb44df5b9b11b97b01e9fb36', + 'serialized': 'ff02170100000005000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed1920065cd1d000000000012202853f0f11ab91d73b73a2a86606103f45dd469ad2e89ec6f9a25febe8758d3fe0b6e81b123de99e953d3073a8760d3213ab5f5cf512e65a2dd73aebb410966d8fbc59e775deb4f23c51be0847402b5e1d4ee68732b3e6d8e8914d259d7e373eb' } return data @pytest.fixture def transaction_type_6(): - """Transaction of type "timelock transfer" + """Transaction of type "multi payment" """ data = { - 'data': {}, - 'serialised': '' + 'version': 2, + 'network': 23, + 'typeGroup': 1, + 'type': 6, + 'nonce': 1, + 'senderPublicKey': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', + 'fee': 10000000, + 'amount': 0, + 'asset': { + 'payments': [ + { + 'amount': 1, + 'recipientId': 'AHXtmB84sTZ9Zd35h9Y1vfFvPE2Xzqj8ri' + }, + { + 'amount': 2, + 'recipientId': 'AZFEPTWnn2Sn8wDZgCRF8ohwKkrmk2AZi1' + }, + ], + }, + 'signature': '672e89e66a9c5d7d95c21ccd07a89a111f02823146c06f14689d2cf1efd645fb648258fcf2280486d2cae19f391796d72145d2a8e6f261e887e34cd1998bdb65', + 'id': 'e8c7293d428048f8678dc6c88cb8b32bd49c8ae9b02018297c1889d9bd33ba8d', + 'serialized': 'ff02170100000006000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed1928096980000000000000200010000000000000017134b5be4b327ddf9c2bb47fec8a1a44189e90f74020000000000000017bfa6aec83cf1bd03a0cab9f35c85ff51a3e9f041672e89e66a9c5d7d95c21ccd07a89a111f02823146c06f14689d2cf1efd645fb648258fcf2280486d2cae19f391796d72145d2a8e6f261e887e34cd1998bdb65' } return data @pytest.fixture def transaction_type_7(): - """Transaction of type "multi payment" + """Transaction of type "delegate resignation" """ data = { - 'data': {}, - 'serialised': '' + 'version': 2, + 'network': 23, + 'typeGroup': 1, + 'type': 7, + 'nonce': 1, + 'senderPublicKey': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', + 'fee': 2500000000, + 'amount': 0, + 'signature': 'bdc048ca7eb5688cc01921aecf5914118cfc78eacc23825efa6d75094a683127cc02512dc59e1e0631fa8956f482eabc54933d23011a8337ea9cab99abed504d', + 'id': '707b4deb339e717dfef44c40db0692015ce9bbab015c007b016b8a46b341e859', + 'serialized': 'ff02170100000007000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19200f902950000000000bdc048ca7eb5688cc01921aecf5914118cfc78eacc23825efa6d75094a683127cc02512dc59e1e0631fa8956f482eabc54933d23011a8337ea9cab99abed504d' } return data @pytest.fixture def transaction_type_8(): - """Transaction of type "delegate resignation" + """Transaction of type "HTLC lock" """ data = { - 'data': {}, - 'serialised': '' + 'version': 2, + 'network': 23, + 'typeGroup': 1, + 'type': 8, + 'nonce': 1, + 'senderPublicKey': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', + 'fee': 10000000, + 'amount': 200000000, + 'recipientId': 'AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', + 'asset': { + 'lock': { + 'secretHash': '0f128d401958b1b30ad0d10406f47f9489321017b4614e6cb993fc63913c5454', + 'expiration': { + 'type': 1, + 'value': 1573455822 + } + }, + }, + 'signature': '7fe939b22a1da166b6ea58e3964651236fb4e0739f9716dedf92986f37df71ea7993e9a97b4a1686c0ad08028dcae08b7cb4a54b8a4db57e72b839a611e86358', + 'id': 'e1b34afa54bbf34de5c00716b92246c5248c2135221ece169db877ca60a14007', + 'serialized': 'ff02170100000008000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19280969800000000000000c2eb0b000000000f128d401958b1b30ad0d10406f47f9489321017b4614e6cb993fc63913c545401ce07c95d170995750207ecaf0ccf251c1265b92ad84f5536627fe939b22a1da166b6ea58e3964651236fb4e0739f9716dedf92986f37df71ea7993e9a97b4a1686c0ad08028dcae08b7cb4a54b8a4db57e72b839a611e86358' + + } + return data + +@pytest.fixture +def transaction_type_9(): + """Transaction of type "HTLC claim" + """ + data = { + 'version': 2, + 'network': 23, + 'typeGroup': 1, + 'type': 9, + 'nonce': 1, + 'senderPublicKey': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', + 'fee': 0, + 'amount': 0, + 'asset': { + 'claim': { + 'lockTransactionId': '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4', + 'unlockSecret': 'my secret that should be 32bytes' + }, + }, + 'signature': '381188e6a3c0da8823ab37cf7562724b3920f4fc8a40cb259ae297bd7237b511cbfdbcb46b7afa319ad1c2d8cc3d8cdc33a437c8b17867777b891d03c036dfb9', + 'id': '846c5ee8a328376416735da43056d154d41e264564def42fb28b373c0d895c46', + 'serialized': 'ff02170100000009000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192000000000000000000943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb46d792073656372657420746861742073686f756c642062652033326279746573381188e6a3c0da8823ab37cf7562724b3920f4fc8a40cb259ae297bd7237b511cbfdbcb46b7afa319ad1c2d8cc3d8cdc33a437c8b17867777b891d03c036dfb9' + + } + return data + +@pytest.fixture +def transaction_type_10(): + """Transaction of type "HTLC refund" + """ + data = { + 'version': 2, + 'network': 23, + 'typeGroup': 1, + 'type': 10, + 'nonce': 1, + 'senderPublicKey': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', + 'fee': 0, + 'amount': 0, + 'asset': { + 'refund': { + 'lockTransactionId': '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4', + }, + }, + 'signature': '16d9ef1dceb0cbb105a45af6bdde9439055f07197643f9e2837312463330fd02ec7b13d1242becfe333c1b8ab2ea91c0c8240390d86f0fb0f6cdc22ec6ac64f1', + 'id': '9356aa730990a2ea8e9871ffa65800f34ef1a4bec3215d89c950e72d82a34e91', + 'serialized': 'ff0217010000000a000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192000000000000000000943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb416d9ef1dceb0cbb105a45af6bdde9439055f07197643f9e2837312463330fd02ec7b13d1242becfe333c1b8ab2ea91c0c8240390d86f0fb0f6cdc22ec6ac64f1' + } return data diff --git a/tests/resources/test_transaction.py b/tests/resources/test_transaction.py index 6d38a56a..0ac1024d 100644 --- a/tests/resources/test_transaction.py +++ b/tests/resources/test_transaction.py @@ -3,7 +3,7 @@ def test_transaction_serialize(transaction_type_0): transaction = Transaction(**transaction_type_0) - serialized = transaction.serialize() + serialized = transaction.serialize(False, True, True) assert serialized == transaction_type_0['serialized'] @@ -20,4 +20,4 @@ def test_transaction_deserialize(transaction_type_0): assert data['version'] == transaction_type_0['version'] assert data['network'] == transaction_type_0['network'] assert data['type'] == transaction_type_0['type'] - assert data['timestamp'] == transaction_type_0['timestamp'] + assert data['nonce'] == transaction_type_0['nonce'] diff --git a/tests/transactions/builder/test_delegate_registration.py b/tests/transactions/builder/test_delegate_registration.py index 8fa3f59c..43e042e7 100644 --- a/tests/transactions/builder/test_delegate_registration.py +++ b/tests/transactions/builder/test_delegate_registration.py @@ -1,15 +1,27 @@ -from crypto.constants import TRANSACTION_DELEGATE_REGISTRATION +from crypto.configuration.network import set_network +from crypto.constants import TRANSACTION_DELEGATE_REGISTRATION, TRANSACTION_TYPE_GROUP +from crypto.networks.devnet import Devnet from crypto.transactions.builder.delegate_registration import DelegateRegistration +set_network(Devnet) + def test_delegate_registration_transaction(): """Test if a delegate registration transaction gets built """ delegate_name = 'mr.delegate' + transaction = DelegateRegistration(delegate_name) - transaction.sign('testing') + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.set_nonce(1) + transaction.schnorr_sign('testing') transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 assert transaction_dict['signature'] assert transaction_dict['asset']['delegate']['username'] == delegate_name assert transaction_dict['type'] is TRANSACTION_DELEGATE_REGISTRATION - transaction.verify() # if no exception is raised, it means the transaction is valid + assert transaction_dict['typeGroup'] == 1 + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_delegate_resignation.py b/tests/transactions/builder/test_delegate_resignation.py index 5e5c9093..27305f7b 100644 --- a/tests/transactions/builder/test_delegate_resignation.py +++ b/tests/transactions/builder/test_delegate_resignation.py @@ -1,8 +1,24 @@ -import pytest +from crypto.configuration.network import set_network +from crypto.constants import TRANSACTION_DELEGATE_RESIGNATION, TRANSACTION_TYPE_GROUP +from crypto.networks.devnet import Devnet +from crypto.transactions.builder.delegate_resignation import DelegateResignation + +set_network(Devnet) -@pytest.mark.skip(reason='not implemented') def test_delegate_resignation_transaction(): """Test if delegate resignation transaction gets built """ - pass + transaction = DelegateResignation() + transaction.set_nonce(1) + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.schnorr_sign('testing') + transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 + assert transaction_dict['signature'] + assert transaction_dict['type'] is TRANSACTION_DELEGATE_RESIGNATION + assert transaction_dict['typeGroup'] == 1 + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_htlc_claim.py b/tests/transactions/builder/test_htlc_claim.py new file mode 100644 index 00000000..891a8110 --- /dev/null +++ b/tests/transactions/builder/test_htlc_claim.py @@ -0,0 +1,31 @@ +from crypto.configuration.network import set_network +from crypto.constants import TRANSACTION_HTLC_CLAIM, TRANSACTION_TYPE_GROUP +from crypto.networks.devnet import Devnet +from crypto.transactions.builder.htlc_claim import HtlcClaim + +set_network(Devnet) + + +def test_htlc_claim_transaction(): + """Test if timelock transaction gets built + """ + lock_transaction_id = '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' + + # This should be the hashed secret used for HTLC Lock transaction + unlock_secret = 'my secret that should be 32bytes' + + transaction = HtlcClaim(lock_transaction_id, unlock_secret) + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.set_nonce(1) + transaction.schnorr_sign('testing') + transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 + assert transaction_dict['signature'] + assert transaction_dict['type'] is TRANSACTION_HTLC_CLAIM + assert transaction_dict['typeGroup'] == 1 + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['asset']['claim']['lockTransactionId'] == '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' + assert transaction_dict['asset']['claim']['unlockSecret'] == 'my secret that should be 32bytes' + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_htlc_lock.py b/tests/transactions/builder/test_htlc_lock.py new file mode 100644 index 00000000..92a39d50 --- /dev/null +++ b/tests/transactions/builder/test_htlc_lock.py @@ -0,0 +1,34 @@ +from crypto.configuration.network import set_network +from crypto.constants import TRANSACTION_HTLC_LOCK, TRANSACTION_TYPE_GROUP +from crypto.networks.devnet import Devnet +from crypto.transactions.builder.htlc_lock import HtlcLock + +set_network(Devnet) + + +def test_htlc_lock_transaction(): + """Test if timelock transaction gets built + """ + transaction = HtlcLock( + recipient_id='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', + secret_hash='0f128d401958b1b30ad0d10406f47f9489321017b4614e6cb993fc63913c5454', + expiration_type=1, + expiration_value=1573455822 + ) + + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.set_nonce(1) + transaction.schnorr_sign('testing') + transaction_dict = transaction.to_dict() + + assert transaction_dict['recipientId'] == 'AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC' + assert transaction_dict['nonce'] == 1 + assert transaction_dict['signature'] + assert transaction_dict['type'] is TRANSACTION_HTLC_LOCK + assert transaction_dict['typeGroup'] == 1 + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['asset']['lock']['secretHash'] == '0f128d401958b1b30ad0d10406f47f9489321017b4614e6cb993fc63913c5454' + assert transaction_dict['asset']['lock']['expiration']['type'] == 1 + assert transaction_dict['asset']['lock']['expiration']['value'] == 1573455822 + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_htlc_refund.py b/tests/transactions/builder/test_htlc_refund.py new file mode 100644 index 00000000..116e1bdd --- /dev/null +++ b/tests/transactions/builder/test_htlc_refund.py @@ -0,0 +1,28 @@ +from crypto.configuration.network import set_network +from crypto.constants import TRANSACTION_HTLC_REFUND, TRANSACTION_TYPE_GROUP +from crypto.networks.devnet import Devnet +from crypto.transactions.builder.htlc_refund import HtlcRefund + +set_network(Devnet) + + +def test_timelock_refund_transaction(): + """Test if timelock transaction gets built + """ + transaction = HtlcRefund( + lock_transaction_id='943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' + ) + + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.set_nonce(1) + transaction.schnorr_sign('testing') + transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 + assert transaction_dict['signature'] + assert transaction_dict['type'] is TRANSACTION_HTLC_REFUND + assert transaction_dict['typeGroup'] == 1 + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['asset']['refund']['lockTransactionId'] == '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_ipfs.py b/tests/transactions/builder/test_ipfs.py index b88d4fc8..ecca2e83 100644 --- a/tests/transactions/builder/test_ipfs.py +++ b/tests/transactions/builder/test_ipfs.py @@ -1,8 +1,29 @@ -import pytest +from base58 import b58encode + +from crypto.configuration.network import set_network +from crypto.constants import TRANSACTION_IPFS, TRANSACTION_TYPE_GROUP +from crypto.networks.devnet import Devnet +from crypto.transactions.builder.ipfs import IPFS + +set_network(Devnet) -@pytest.mark.skip(reason='not implemented') def test_ipfs_transaction(): """Test if ipfs transaction gets built """ - pass + ipfs_id = b58encode('hello') + + transaction = IPFS(ipfs_id) + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.set_nonce(1) + transaction.schnorr_sign('testing') + transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 + assert transaction_dict['signature'] + assert transaction_dict['type'] is TRANSACTION_IPFS + assert transaction_dict['typeGroup'] == 1 + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['asset']['ipfs'] == ipfs_id + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_multi_payment.py b/tests/transactions/builder/test_multi_payment.py index 1ead3f50..9314efd0 100644 --- a/tests/transactions/builder/test_multi_payment.py +++ b/tests/transactions/builder/test_multi_payment.py @@ -1,8 +1,30 @@ -import pytest +from crypto.configuration.network import set_network +from crypto.constants import TRANSACTION_MULTI_PAYMENT, TRANSACTION_TYPE_GROUP +from crypto.networks.devnet import Devnet +from crypto.transactions.builder.multi_payment import MultiPayment + +set_network(Devnet) -@pytest.mark.skip(reason='not implemented') def test_multi_payment_transaction(): """Test if multi payment transaction gets built """ - pass + transaction = MultiPayment() + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.set_nonce(1) + transaction.add_payment(1, 'AHXtmB84sTZ9Zd35h9Y1vfFvPE2Xzqj8ri') + transaction.add_payment(2, 'ATK14wxyYxbELq2b91bAfBY8Vmh9J6MDej') + transaction.schnorr_sign('testing') + transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 + assert transaction_dict['signature'] + assert transaction_dict['type'] is TRANSACTION_MULTI_PAYMENT + assert transaction_dict['typeGroup'] == 1 + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['asset']['payments'][0]['amount'] == 1 + assert transaction_dict['asset']['payments'][0]['recipientId'] == 'AHXtmB84sTZ9Zd35h9Y1vfFvPE2Xzqj8ri' + assert transaction_dict['asset']['payments'][1]['amount'] == 2 + assert transaction_dict['asset']['payments'][1]['recipientId'] == 'ATK14wxyYxbELq2b91bAfBY8Vmh9J6MDej' + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_multi_signature_registration.py b/tests/transactions/builder/test_multi_signature_registration.py index bb35c9c0..bd471d65 100644 --- a/tests/transactions/builder/test_multi_signature_registration.py +++ b/tests/transactions/builder/test_multi_signature_registration.py @@ -1,19 +1,45 @@ -from crypto.constants import TRANSACTION_MULTI_SIGNATURE_REGISTRATION +from crypto.configuration.network import set_network +from crypto.constants import TRANSACTION_MULTI_SIGNATURE_REGISTRATION, TRANSACTION_TYPE_GROUP +from crypto.networks.devnet import Devnet from crypto.transactions.builder.multi_signature_registration import MultiSignatureRegistration +set_network(Devnet) + def test_multi_signature_registration_transaction(): """Test if a second signature registration transaction gets built """ - keysgroup = [ - '03a02b9d5fdd1307c2ee4652ba54d492d1fd11a7d1bb3f3a44c4a05e79f19de933', - '13a02b9d5fdd1307c2ee4652ba54d492d1fd11a7d1bb3f3a44c4a05e79f19de933', - '23a02b9d5fdd1307c2ee4652ba54d492d1fd11a7d1bb3f3a44c4a05e79f19de933', + publicKeys = [ + '0205d9bbe71c343ac9a6a83a4344fd404c3534fc7349827097d0835d160bc2b896', + '03df0a1eb42d99b5de395cead145ba1ec2ea837be308c7ce3a4e8018b7efc7fdb8', ] - transaction = MultiSignatureRegistration(2, 255, keysgroup) - transaction.sign('secret') - transaction.second_sign('second secret') + + transaction = MultiSignatureRegistration() + transaction.set_sender_public_key('0205d9bbe71c343ac9a6a83a4344fd404c3534fc7349827097d0835d160bc2b896') + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.set_nonce(1) + transaction.set_min(2) + transaction.set_public_keys(publicKeys) + transaction.add_participant('03860d76b1df09659ac282cea3da5bd84fc45729f348a4a8e5f802186be72dc17f') + transaction.multi_sign('this is a top secret passphrase 1', 0) + transaction.multi_sign('this is a top secret passphrase 2', 1) + transaction.multi_sign('this is a top secret passphrase 3', 2) + + transaction.schnorr_sign('this is a top secret passphrase 1') + transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 + assert transaction_dict['version'] == 2 + assert transaction_dict['fee'] == 2000000000 assert transaction_dict['signature'] assert transaction_dict['type'] is TRANSACTION_MULTI_SIGNATURE_REGISTRATION - transaction.verify() # if no exception is raised, it means the transaction is valid + assert transaction_dict['typeGroup'] == 1 + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['asset']['multiSignature']['min'] == 2 + + assert transaction_dict['asset']['multiSignature']['publicKeys'][0] == publicKeys[0] + assert transaction_dict['asset']['multiSignature']['publicKeys'][1] == publicKeys[1] + assert transaction_dict['asset']['multiSignature']['publicKeys'][2] == '03860d76b1df09659ac282cea3da5bd84fc45729f348a4a8e5f802186be72dc17f' + + transaction.schnorr_verify_multisig() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_second_signature_registration.py b/tests/transactions/builder/test_second_signature_registration.py index a39a569f..1ed58134 100644 --- a/tests/transactions/builder/test_second_signature_registration.py +++ b/tests/transactions/builder/test_second_signature_registration.py @@ -1,13 +1,24 @@ -from crypto.constants import TRANSACTION_SECOND_SIGNATURE_REGISTRATION +from crypto.configuration.network import set_network +from crypto.constants import TRANSACTION_SECOND_SIGNATURE_REGISTRATION, TRANSACTION_TYPE_GROUP +from crypto.networks.devnet import Devnet from crypto.transactions.builder.second_signature_registration import SecondSignatureRegistration +set_network(Devnet) + def test_second_signature_registration_transaction(): """Test if a second signature registration transaction gets built """ - transaction = SecondSignatureRegistration('this is the second passphrase') - transaction.sign('testing') + transaction = SecondSignatureRegistration('this is a top secret passphrase') + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.set_nonce(1) + transaction.schnorr_sign('testing') transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 assert transaction_dict['signature'] assert transaction_dict['type'] is TRANSACTION_SECOND_SIGNATURE_REGISTRATION - transaction.verify() # if no exception is raised, it means the transaction is valid + assert transaction_dict['typeGroup'] == 1 + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_timelock_transfer.py b/tests/transactions/builder/test_timelock_transfer.py deleted file mode 100644 index f3e3dc74..00000000 --- a/tests/transactions/builder/test_timelock_transfer.py +++ /dev/null @@ -1,8 +0,0 @@ -import pytest - - -@pytest.mark.skip(reason='not implemented') -def test_timelock_transaction(): - """Test if timelock transaction gets built - """ - pass diff --git a/tests/transactions/builder/test_transfer.py b/tests/transactions/builder/test_transfer.py index 20ba6e06..1f5282db 100644 --- a/tests/transactions/builder/test_transfer.py +++ b/tests/transactions/builder/test_transfer.py @@ -1,21 +1,30 @@ -from crypto.constants import TRANSACTION_TRANSFER +from crypto.configuration.network import set_network +from crypto.constants import TRANSACTION_TRANSFER, TRANSACTION_TYPE_GROUP +from crypto.networks.devnet import Devnet from crypto.transactions.builder.transfer import Transfer +set_network(Devnet) + def test_transfer_transaction(): """Test if a transfer transaction gets built """ transaction = Transfer( - recipientId='AXoXnFi4z1Z6aFvjEYkDVCtBGW2PaRiM25', - amount=1000, - vendorField='Hello Pythonistas!' + recipientId='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', + amount=200000000, ) - transaction.sign('This is a top secret passphrase') + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.set_nonce(1) + transaction.schnorr_sign('this is a top secret passphrase') transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 assert transaction_dict['signature'] assert transaction_dict['type'] is TRANSACTION_TRANSFER + assert transaction_dict['typeGroup'] == 1 + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - transaction.verify() # if no exception is raised, it means the transaction is valid + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid def test_parse_signatures(transaction_type_0): diff --git a/tests/transactions/builder/test_vote.py b/tests/transactions/builder/test_vote.py index 6175019e..2d7e862c 100644 --- a/tests/transactions/builder/test_vote.py +++ b/tests/transactions/builder/test_vote.py @@ -1,15 +1,27 @@ -from crypto.constants import TRANSACTION_VOTE +from crypto.configuration.network import set_network +from crypto.constants import TRANSACTION_TYPE_GROUP, TRANSACTION_VOTE +from crypto.networks.devnet import Devnet from crypto.transactions.builder.vote import Vote +set_network(Devnet) + def test_vote_transaction(): """Test if a vote transaction gets built """ vote = '+034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' + transaction = Vote(vote) - transaction.sign('testing') + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.set_nonce(1) + transaction.schnorr_sign('testing') transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 assert transaction_dict['signature'] assert transaction_dict['asset']['votes'] assert transaction_dict['type'] is TRANSACTION_VOTE - transaction.verify() # if no exception is raised, it means the transaction is valid + assert transaction_dict['typeGroup'] == 1 + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/deserializers/test_delegate_registration.py b/tests/transactions/deserializers/test_delegate_registration.py index dfc37c2e..66cad428 100644 --- a/tests/transactions/deserializers/test_delegate_registration.py +++ b/tests/transactions/deserializers/test_delegate_registration.py @@ -2,8 +2,21 @@ def test_delegate_registration_deserializer(): - serialized = 'ff011e02a5b87502034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19200f90295000000000009626f6c646e696e6a61304402205fe105d2d23b66d2dbae3bd12bc0d1df498936a7614c71c0481bbf5159ad8d2002201084f5c24e802964b3075ac6feac91429c356c960a5faa8ef3c397a4b25c299a' # noqa + serialized = 'ff02170100000002000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19200f90295000000000009626f6c646e696e6a61eaf4b4dfd7903c32cf6c145ddf0744e86536719f5790b4286b08f1a10f0ad183bc601efc8a49a2a7b41758601a1793693afa1781cf0a63a8f72b08d5a1aaba1e' # noqa + deserializer = Deserializer(serialized) actual = deserializer.deserialize() + + assert actual.version == 2 + assert actual.network == 23 + assert actual.typeGroup == 1 + assert actual.type == 2 + assert actual.nonce == 1 + assert actual.senderPublicKey == '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' + assert actual.fee == 2500000000 assert actual.asset['delegate'] == {'username': 'boldninja'} - actual.verify() + assert actual.signature == 'eaf4b4dfd7903c32cf6c145ddf0744e86536719f5790b4286b08f1a10f0ad183bc601efc8a49a2a7b41758601a1793693afa1781cf0a63a8f72b08d5a1aaba1e' + assert actual.amount == 0 + assert actual.id == 'cfd113d8cd9fd46b07030c14fac38c1d3fc0eca991e999eab9d0152ea96ab0dc' + + actual.verify_schnorr() diff --git a/tests/transactions/deserializers/test_delegate_resignation.py b/tests/transactions/deserializers/test_delegate_resignation.py new file mode 100644 index 00000000..a54fd3a8 --- /dev/null +++ b/tests/transactions/deserializers/test_delegate_resignation.py @@ -0,0 +1,21 @@ +from crypto.transactions.deserializer import Deserializer + + +def test_delegate_resignation_deserializer(): + serialized = 'ff02170100000007000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19200f902950000000000bdc048ca7eb5688cc01921aecf5914118cfc78eacc23825efa6d75094a683127cc02512dc59e1e0631fa8956f482eabc54933d23011a8337ea9cab99abed504d' # noqa + + deserializer = Deserializer(serialized) + actual = deserializer.deserialize() + + assert actual.version == 2 + assert actual.network == 23 + assert actual.typeGroup == 1 + assert actual.type == 7 + assert actual.nonce == 1 + assert actual.senderPublicKey == '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' + assert actual.fee == 2500000000 + assert actual.signature == 'bdc048ca7eb5688cc01921aecf5914118cfc78eacc23825efa6d75094a683127cc02512dc59e1e0631fa8956f482eabc54933d23011a8337ea9cab99abed504d' + assert actual.amount == 0 + assert actual.id == '707b4deb339e717dfef44c40db0692015ce9bbab015c007b016b8a46b341e859' + + actual.verify_schnorr() diff --git a/tests/transactions/deserializers/test_htlc_claim.py b/tests/transactions/deserializers/test_htlc_claim.py new file mode 100644 index 00000000..3929c164 --- /dev/null +++ b/tests/transactions/deserializers/test_htlc_claim.py @@ -0,0 +1,23 @@ +from crypto.transactions.deserializer import Deserializer + + +def test_htlc_claim_deserializer(): + serialized = 'ff02170100000009000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192000000000000000000943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb46d792073656372657420746861742073686f756c642062652033326279746573381188e6a3c0da8823ab37cf7562724b3920f4fc8a40cb259ae297bd7237b511cbfdbcb46b7afa319ad1c2d8cc3d8cdc33a437c8b17867777b891d03c036dfb9' # noqa + + deserializer = Deserializer(serialized) + actual = deserializer.deserialize() + + assert actual.version == 2 + assert actual.network == 23 + assert actual.typeGroup == 1 + assert actual.type == 9 + assert actual.nonce == 1 + assert actual.senderPublicKey == '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' + assert actual.fee == 0 + assert actual.signature == '381188e6a3c0da8823ab37cf7562724b3920f4fc8a40cb259ae297bd7237b511cbfdbcb46b7afa319ad1c2d8cc3d8cdc33a437c8b17867777b891d03c036dfb9' + assert actual.amount == 0 + assert actual.id == '846c5ee8a328376416735da43056d154d41e264564def42fb28b373c0d895c46' + assert actual.asset['claim']['lockTransactionId'] == '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' # noqa + assert actual.asset['claim']['unlockSecret'] == 'my secret that should be 32bytes' # noqa + + actual.verify_schnorr() diff --git a/tests/transactions/deserializers/test_htlc_lock.py b/tests/transactions/deserializers/test_htlc_lock.py new file mode 100644 index 00000000..1304748b --- /dev/null +++ b/tests/transactions/deserializers/test_htlc_lock.py @@ -0,0 +1,25 @@ +from crypto.transactions.deserializer import Deserializer + + +def test_htlc_lock_deserializer(): + serialized = 'ff02170100000008000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19280969800000000000000c2eb0b000000000f128d401958b1b30ad0d10406f47f9489321017b4614e6cb993fc63913c545401ce07c95d170995750207ecaf0ccf251c1265b92ad84f5536627fe939b22a1da166b6ea58e3964651236fb4e0739f9716dedf92986f37df71ea7993e9a97b4a1686c0ad08028dcae08b7cb4a54b8a4db57e72b839a611e86358' # noqa + + deserializer = Deserializer(serialized) + actual = deserializer.deserialize() + + assert actual.version == 2 + assert actual.network == 23 + assert actual.typeGroup == 1 + assert actual.type == 8 + assert actual.nonce == 1 + assert actual.senderPublicKey == '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' + assert actual.fee == 10000000 + assert actual.signature == '7fe939b22a1da166b6ea58e3964651236fb4e0739f9716dedf92986f37df71ea7993e9a97b4a1686c0ad08028dcae08b7cb4a54b8a4db57e72b839a611e86358' + assert actual.amount == 200000000 + assert actual.id == 'e1b34afa54bbf34de5c00716b92246c5248c2135221ece169db877ca60a14007' + assert actual.recipientId == 'AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC' + assert actual.asset['lock']['secretHash'] == '0f128d401958b1b30ad0d10406f47f9489321017b4614e6cb993fc63913c5454' # noqa + assert actual.asset['lock']['expiration']['type'] == 1 # noqa + assert actual.asset['lock']['expiration']['value'] == 1573455822 # noqa + + actual.verify_schnorr() diff --git a/tests/transactions/deserializers/test_htlc_refund.py b/tests/transactions/deserializers/test_htlc_refund.py new file mode 100644 index 00000000..f04868d5 --- /dev/null +++ b/tests/transactions/deserializers/test_htlc_refund.py @@ -0,0 +1,22 @@ +from crypto.transactions.deserializer import Deserializer + + +def test_htlc_refund_deserializer(): + serialized = 'ff0217010000000a000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192000000000000000000943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb416d9ef1dceb0cbb105a45af6bdde9439055f07197643f9e2837312463330fd02ec7b13d1242becfe333c1b8ab2ea91c0c8240390d86f0fb0f6cdc22ec6ac64f1' # noqa + + deserializer = Deserializer(serialized) + actual = deserializer.deserialize() + + assert actual.version == 2 + assert actual.network == 23 + assert actual.typeGroup == 1 + assert actual.type == 10 + assert actual.nonce == 1 + assert actual.senderPublicKey == '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' + assert actual.fee == 0 + assert actual.signature == '16d9ef1dceb0cbb105a45af6bdde9439055f07197643f9e2837312463330fd02ec7b13d1242becfe333c1b8ab2ea91c0c8240390d86f0fb0f6cdc22ec6ac64f1' + assert actual.amount == 0 + assert actual.id == '9356aa730990a2ea8e9871ffa65800f34ef1a4bec3215d89c950e72d82a34e91' + assert actual.asset['refund']['lockTransactionId'] == '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' # noqa + + actual.verify_schnorr() diff --git a/tests/transactions/deserializers/test_ipfs.py b/tests/transactions/deserializers/test_ipfs.py new file mode 100644 index 00000000..966a1f3c --- /dev/null +++ b/tests/transactions/deserializers/test_ipfs.py @@ -0,0 +1,22 @@ +from crypto.transactions.deserializer import Deserializer + + +def test_ipfs_deserializer(): + serialized = 'ff02170100000005000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed1920065cd1d000000000012202853f0f11ab91d73b73a2a86606103f45dd469ad2e89ec6f9a25febe8758d3fe0b6e81b123de99e953d3073a8760d3213ab5f5cf512e65a2dd73aebb410966d8fbc59e775deb4f23c51be0847402b5e1d4ee68732b3e6d8e8914d259d7e373eb' # noqa + + deserializer = Deserializer(serialized) + actual = deserializer.deserialize() + + assert actual.version == 2 + assert actual.network == 23 + assert actual.typeGroup == 1 + assert actual.type == 5 + assert actual.nonce == 1 + assert actual.senderPublicKey == '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' + assert actual.fee == 500000000 + assert actual.signature == '0b6e81b123de99e953d3073a8760d3213ab5f5cf512e65a2dd73aebb410966d8fbc59e775deb4f23c51be0847402b5e1d4ee68732b3e6d8e8914d259d7e373eb' + assert actual.amount == 0 + assert actual.id == '818228ce634b46c488f3b2df8fd02bd50331ebdedb44df5b9b11b97b01e9fb36' + assert actual.asset['ipfs'] == 'QmR45FmbVVrixReBwJkhEKde2qwHYaQzGxu4ZoDeswuF9w' # noqa + + actual.verify_schnorr() diff --git a/tests/transactions/deserializers/test_multi_payment.py b/tests/transactions/deserializers/test_multi_payment.py new file mode 100644 index 00000000..0ed714f3 --- /dev/null +++ b/tests/transactions/deserializers/test_multi_payment.py @@ -0,0 +1,25 @@ +from crypto.transactions.deserializer import Deserializer + + +def test_multi_payment_deserializer(): + serialized = 'ff02170100000006000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed1928096980000000000000200010000000000000017134b5be4b327ddf9c2bb47fec8a1a44189e90f74020000000000000017bfa6aec83cf1bd03a0cab9f35c85ff51a3e9f041672e89e66a9c5d7d95c21ccd07a89a111f02823146c06f14689d2cf1efd645fb648258fcf2280486d2cae19f391796d72145d2a8e6f261e887e34cd1998bdb65' # noqa + + deserializer = Deserializer(serialized) + actual = deserializer.deserialize() + + assert actual.version == 2 + assert actual.network == 23 + assert actual.typeGroup == 1 + assert actual.type == 6 + assert actual.nonce == 1 + assert actual.senderPublicKey == '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' + assert actual.fee == 10000000 + assert actual.signature == '672e89e66a9c5d7d95c21ccd07a89a111f02823146c06f14689d2cf1efd645fb648258fcf2280486d2cae19f391796d72145d2a8e6f261e887e34cd1998bdb65' + assert actual.amount == 0 + assert actual.id == 'e8c7293d428048f8678dc6c88cb8b32bd49c8ae9b02018297c1889d9bd33ba8d' + assert actual.asset['payments'][0]['amount'] == 1 # noqa + assert actual.asset['payments'][0]['recipientId'] == 'AHXtmB84sTZ9Zd35h9Y1vfFvPE2Xzqj8ri' # noqa + assert actual.asset['payments'][1]['amount'] == 2 # noqa + assert actual.asset['payments'][1]['recipientId'] == 'AZFEPTWnn2Sn8wDZgCRF8ohwKkrmk2AZi1' # noqa + + actual.verify_schnorr() diff --git a/tests/transactions/deserializers/test_multi_signature_registration.py b/tests/transactions/deserializers/test_multi_signature_registration.py index 983307ae..7cae8f73 100644 --- a/tests/transactions/deserializers/test_multi_signature_registration.py +++ b/tests/transactions/deserializers/test_multi_signature_registration.py @@ -2,15 +2,31 @@ def test_multi_signature_registration_deserializer(): - serialized = 'ff011704724c9a00036928c98ee53a1f52ed01dd87db10ffe1980eb47cd7c0a7d688321f47b5d7d76000943577000000000002031803543c6cc3545be6bac09c82721973a052c690658283472e88f24d14739f75acc80276dc5b8706a85ca9fdc46e571ac84e52fbb48e13ec7a165a80731b44ae89f1fc02e8d5d17eb17bbc8d7bf1001d29a2d25d1249b7bb7a5b7ad8b7422063091f4b3130440220324d89c5792e4a54ae70b4f1e27e2f87a8b7169cc6f2f7b2c83dba894960f987022053b8d0ae23ff9d1769364db7b6fd03216d93753c82a711c3558045e787bc01a5304402201fcd54a9ac9c0269b8cec213566ddf43207798e2cf9ca1ce3c5d315d66321c6902201aa94c4ed3e5e479a12220aa886b259e488eb89b697c711f91e8c03b9620e0b1ff304502210097f17c8eecf36f86a967cc52a83fa661e4ffc70cc4ea08df58673669406d424c0220798f5710897b75dda42f6548f841afbe4ed1fa262097112cf5a1b3f7dade60e4304402201a4a4c718bfdc699bbb891b2e89be018027d2dcd10640b5ddf07802424dab78e02204ec7c7d505d2158c3b51fdd3843d16aecd2eaaa4c6c7a555ef123c5e59fd41fb304402207e660489bced5ce80c33d45c86781b63898775ab4a231bb48780f97b40073a63022026f0cefd0d83022d822522ab4366a82e3b89085c328817919939f2efeabd913d' # noqa + serialized = 'ff021701000000040001000000000000000205d9bbe71c343ac9a6a83a4344fd404c3534fc7349827097d0835d160bc2b89600943577000000000002030205d9bbe71c343ac9a6a83a4344fd404c3534fc7349827097d0835d160bc2b89603df0a1eb42d99b5de395cead145ba1ec2ea837be308c7ce3a4e8018b7efc7fdb803860d76b1df09659ac282cea3da5bd84fc45729f348a4a8e5f802186be72dc17ff5e9859c955bf8917b308ea21c88daf58661686c2017e476dcf735ad7f00aebf8e6effda3fe99e5f33f6007db7db9c9155796d9b5d31c53bd6156364a6a765d00064900cb2cc3db6ca9c7e3bd363b322cdc4a39e051f655e9867935e1bb856b6dcce52845c031c690808f40340bc827bbaacd7b04bceff866cb0d386ab8471517401dd363ccc101a958bded1a5db1c08f13283fc7cee53da93dfe00785eb406512467ff8e445f8ad843744ac4179f30f942645dfd5bdf5f2bfc344ad02393053880a02d0012f035dc3fd54173c83d40217914653488fe9ce592dca34234163181d255281f2be7033725cfc4a6786509e7fabbaf0be8cf50882fc7b66fe94f259fd004e' # noqa + deserializer = Deserializer(serialized) actual = deserializer.deserialize() data = actual.to_dict() - assert data['asset']['multisignature']['min'] == 2 - assert data['asset']['multisignature']['lifetime'] == 24 - assert data['asset']['multisignature']['keysgroup'] == [ - '+03543c6cc3545be6bac09c82721973a052c690658283472e88f24d14739f75acc8', - '+0276dc5b8706a85ca9fdc46e571ac84e52fbb48e13ec7a165a80731b44ae89f1fc', - '+02e8d5d17eb17bbc8d7bf1001d29a2d25d1249b7bb7a5b7ad8b7422063091f4b31' + + assert data['amount'] == 0 + assert data['nonce'] == 1 + assert data['version'] == 2 + assert data['network'] == 23 + assert data['typeGroup'] == 1 + assert data['fee'] == 2000000000 + assert data['senderPublicKey'] == '0205d9bbe71c343ac9a6a83a4344fd404c3534fc7349827097d0835d160bc2b896' + assert data['signature'] == 'f5e9859c955bf8917b308ea21c88daf58661686c2017e476dcf735ad7f00aebf8e6effda3fe99e5f33f6007db7db9c9155796d9b5d31c53bd6156364a6a765d0' + assert data['id'] == 'c868aad20165a336c35e324378f0c12008d18af4c1025291efcb7539c7c917f0' + assert data['asset']['multiSignature']['min'] == 2 + assert data['asset']['multiSignature']['publicKeys'] == [ + '0205d9bbe71c343ac9a6a83a4344fd404c3534fc7349827097d0835d160bc2b896', + '03df0a1eb42d99b5de395cead145ba1ec2ea837be308c7ce3a4e8018b7efc7fdb8', + '03860d76b1df09659ac282cea3da5bd84fc45729f348a4a8e5f802186be72dc17f' ] - actual.verify() + assert data['signatures'] == [ + '0064900cb2cc3db6ca9c7e3bd363b322cdc4a39e051f655e9867935e1bb856b6dcce52845c031c690808f40340bc827bbaacd7b04bceff866cb0d386ab84715174', # noqa + '01dd363ccc101a958bded1a5db1c08f13283fc7cee53da93dfe00785eb406512467ff8e445f8ad843744ac4179f30f942645dfd5bdf5f2bfc344ad02393053880a', # noqa + '02d0012f035dc3fd54173c83d40217914653488fe9ce592dca34234163181d255281f2be7033725cfc4a6786509e7fabbaf0be8cf50882fc7b66fe94f259fd004e' # noqa + ] + + actual.verify_schnorr_multisig() diff --git a/tests/transactions/deserializers/test_second_signature_registration.py b/tests/transactions/deserializers/test_second_signature_registration.py index 7a54bd61..8bf28211 100644 --- a/tests/transactions/deserializers/test_second_signature_registration.py +++ b/tests/transactions/deserializers/test_second_signature_registration.py @@ -1,9 +1,22 @@ from crypto.transactions.deserializer import Deserializer -def test_second_signature_registration(): - serialized = 'ff011e013bc27502034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed1920065cd1d000000000003699e966b2525f9088a6941d8d94f7869964a000efe65783d78ac82e1199fe609304402202aab49477dd3531e4473196d08fbd7c00ebb79223d5eaaeaf02c52c4041a86cf02201a7d82655f9b1d22af3ea94e6f183649bb4610cdeca3b9e20d6c8773f869831c' # noqa +def test_second_signature_registration_deserializer(): + serialized = 'ff02170100000001000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed1920065cd1d000000000003699e966b2525f9088a6941d8d94f7869964a000efe65783d78ac82e1199fe609f9a1e2244c8318e8be85482fc02659e5c1775d246d73d5d0699ae4a1d5e3a3e84f9dcf68ee015f943d2a82eb829f35abd7901279761d96f6b43431520e955c67' # noqa + deserializer = Deserializer(serialized) actual = deserializer.deserialize() + + assert actual.version == 2 + assert actual.network == 23 + assert actual.typeGroup == 1 + assert actual.type == 1 + assert actual.nonce == 1 + assert actual.senderPublicKey == '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' + assert actual.fee == 500000000 + assert actual.signature == 'f9a1e2244c8318e8be85482fc02659e5c1775d246d73d5d0699ae4a1d5e3a3e84f9dcf68ee015f943d2a82eb829f35abd7901279761d96f6b43431520e955c67' + assert actual.amount == 0 + assert actual.id == '173a3230159b45d772b2e0348f42af53913bf3e376397f29b8e0bda290badbe4' assert actual.asset['signature']['publicKey'] == '03699e966b2525f9088a6941d8d94f7869964a000efe65783d78ac82e1199fe609' # noqa - actual.verify() + + actual.verify_schnorr() diff --git a/tests/transactions/deserializers/test_transfer.py b/tests/transactions/deserializers/test_transfer.py index b697e980..f3b9e9bc 100644 --- a/tests/transactions/deserializers/test_transfer.py +++ b/tests/transactions/deserializers/test_transfer.py @@ -1,16 +1,48 @@ +import pytest from crypto.transactions.deserializer import Deserializer def test_transfer_deserializer(): - serialized = 'ff011e0066b47502034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19280969800000000000000c2eb0b00000000000000001e0995750207ecaf0ccf251c1265b92ad84f5536623044022002994b30e08b58825c8c16ebf2cc693cfe706fb26571674784ead098accc89d702205b79dedc752a84504ecfe4b9e1292997f22260ee4daa102d2d9a61432d93b286' # noqa + serialized = 'ff02170100000000000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19280969800000000000000c2eb0b0000000000000000170995750207ecaf0ccf251c1265b92ad84f553662136c29d921b58ae3194020b82e9808f9cd54f7178cb34678f570f28226b8e56ba0ad318297a3bacbb37ab22ddaa5dbf1901cda3ec2d2bca5ce98d6407839ab9b' # noqa + deserializer = Deserializer(serialized) actual = deserializer.deserialize() + + assert actual.version == 2 + assert actual.network == 23 + assert actual.typeGroup == 1 assert actual.expiration == 0 assert actual.type == 0 assert actual.amount == 200000000 assert actual.fee == 10000000 - assert actual.timestamp == 41268326 - assert actual.recipientId == 'D61mfSggzbvQgTUe6JhYKH2doHaqJ3Dyib' + assert actual.nonce == 1 + assert actual.recipientId == 'AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC' assert actual.senderPublicKey == '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' # noqa - assert actual.id == 'da61c6cba363cc39baa0ca3f9ba2c5db81b9805045bd0b9fc58af07ad4206856' - actual.verify() + assert actual.id == '129517023bd895b682bbb38b1d1f99e9222bd487899c843da22d8572b0fb52a8' + assert actual.signature == '136c29d921b58ae3194020b82e9808f9cd54f7178cb34678f570f28226b8e56ba0ad318297a3bacbb37ab22ddaa5dbf1901cda3ec2d2bca5ce98d6407839ab9b' + + actual.verify_schnorr() + + +@pytest.mark.skip() +def test_transfer_second_signature_deserializer(): + serialized = 'ff02170100000000000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19280969800000000000000c2eb0b0000000000000000170995750207ecaf0ccf251c1265b92ad84f553662136c29d921b58ae3194020b82e9808f9cd54f7178cb34678f570f28226b8e56ba0ad318297a3bacbb37ab22ddaa5dbf1901cda3ec2d2bca5ce98d6407839ab9b02dd94f611e300ad77147d808a34e942b379c5468760d8605adc0304400a2578a2039468b844f30ad1f0515f9cce33855791296117bfe8ef3caa664152644fd6' + + deserializer = Deserializer(serialized) + actual = deserializer.deserialize() + + assert actual.version == 2 + assert actual.network == 23 + assert actual.typeGroup == 1 + assert actual.expiration == 0 + assert actual.type == 0 + assert actual.amount == 200000000 + assert actual.fee == 10000000 + assert actual.nonce == 1 + assert actual.recipientId == 'AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC' + assert actual.senderPublicKey == '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' # noqa + assert actual.id == 'be148ab83b75c199f9778f8963814a641a6ee937b6dd5b294082fbf7def94a45' + assert actual.signature == '136c29d921b58ae3194020b82e9808f9cd54f7178cb34678f570f28226b8e56ba0ad318297a3bacbb37ab22ddaa5dbf1901cda3ec2d2bca5ce98d6407839ab9b' + assert actual.signSignature == '02dd94f611e300ad77147d808a34e942b379c5468760d8605adc0304400a2578a2039468b844f30ad1f0515f9cce33855791296117bfe8ef3caa664152644fd6' + + actual.verify_schnorr() \ No newline at end of file diff --git a/tests/transactions/deserializers/test_vote.py b/tests/transactions/deserializers/test_vote.py index e82600b0..14f8834c 100644 --- a/tests/transactions/deserializers/test_vote.py +++ b/tests/transactions/deserializers/test_vote.py @@ -2,8 +2,22 @@ def test_vote_deserializer(): - serialized = 'ff011e0365b87502034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19200e1f50500000000000101022cca9529ec97a772156c152a00aad155ee6708243e65c9d211a589cb5d43234d3045022100bb39554e077c0cd23ef8376731f6b0457edea0aa04c92a9ef07c84228aa5542c0220648365448a0b19c49ff0bab5cde0bee7999a9cfd5eaefc4a7f03b6f93a2efb51' # noqa + serialized = 'ff02170100000003000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19200e1f50500000000000101022cca9529ec97a772156c152a00aad155ee6708243e65c9d211a589cb5d43234d86007f8e6a982bc271ec063c20f158734f0bc1e23e0e1abf9edeaa208b4810fa1d466171bba79a5c00b0a4c698728f68aa0748d98613cac247c014ee84a6fc41' # noqa + deserializer = Deserializer(serialized) actual = deserializer.deserialize() + + assert actual.version == 2 + assert actual.network == 23 + assert actual.typeGroup == 1 + assert actual.type == 3 + assert actual.amount == 0 + assert actual.fee == 100000000 + assert actual.nonce == 1 + assert actual.senderPublicKey == '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' # noqa + assert actual.id == '2c5d71028607674411c8e37e316a015eccbeb9ba486fddfbd393dc421540a90a' + assert actual.signature == '86007f8e6a982bc271ec063c20f158734f0bc1e23e0e1abf9edeaa208b4810fa1d466171bba79a5c00b0a4c698728f68aa0748d98613cac247c014ee84a6fc41' + assert actual.asset['votes'] == ['+022cca9529ec97a772156c152a00aad155ee6708243e65c9d211a589cb5d43234d'] # noqa - actual.verify() + + actual.verify_schnorr() diff --git a/tests/transactions/serializers/test_delegate_registration.py b/tests/transactions/serializers/test_delegate_registration.py index ecd045ab..ba6e03e1 100644 --- a/tests/transactions/serializers/test_delegate_registration.py +++ b/tests/transactions/serializers/test_delegate_registration.py @@ -2,5 +2,5 @@ def test_serializer(transaction_type_2): - result = Serializer(transaction_type_2).serialize() + result = Serializer(transaction_type_2).serialize(False, True) assert result == transaction_type_2['serialized'] diff --git a/tests/transactions/serializers/test_delegate_resignation.py b/tests/transactions/serializers/test_delegate_resignation.py index af227687..6172982a 100644 --- a/tests/transactions/serializers/test_delegate_resignation.py +++ b/tests/transactions/serializers/test_delegate_resignation.py @@ -1,9 +1,6 @@ -import pytest - from crypto.transactions.serializer import Serializer -@pytest.mark.skip(reason='not implemented - missing fixture') -def test_serializer(transaction_type_8): - result = Serializer(transaction_type_8).serialize() - assert result == transaction_type_8['serialized'] +def test_serializer(transaction_type_7): + result = Serializer(transaction_type_7).serialize(False, True) + assert result == transaction_type_7['serialized'] diff --git a/tests/transactions/serializers/test_htlc_claim.py b/tests/transactions/serializers/test_htlc_claim.py new file mode 100644 index 00000000..5592096a --- /dev/null +++ b/tests/transactions/serializers/test_htlc_claim.py @@ -0,0 +1,6 @@ +from crypto.transactions.serializer import Serializer + + +def test_serializer(transaction_type_9): + result = Serializer(transaction_type_9).serialize(False, True) + assert result == transaction_type_9['serialized'] diff --git a/tests/transactions/serializers/test_htlc_lock.py b/tests/transactions/serializers/test_htlc_lock.py new file mode 100644 index 00000000..f0e17b9b --- /dev/null +++ b/tests/transactions/serializers/test_htlc_lock.py @@ -0,0 +1,6 @@ +from crypto.transactions.serializer import Serializer + + +def test_serializer(transaction_type_8): + result = Serializer(transaction_type_8).serialize(False, True) + assert result == transaction_type_8['serialized'] diff --git a/tests/transactions/serializers/test_htlc_refund.py b/tests/transactions/serializers/test_htlc_refund.py new file mode 100644 index 00000000..e658a9d8 --- /dev/null +++ b/tests/transactions/serializers/test_htlc_refund.py @@ -0,0 +1,6 @@ +from crypto.transactions.serializer import Serializer + + +def test_serializer(transaction_type_10): + result = Serializer(transaction_type_10).serialize(False, True) + assert result == transaction_type_10['serialized'] diff --git a/tests/transactions/serializers/test_ipfs.py b/tests/transactions/serializers/test_ipfs.py index 89df73ea..38dab861 100644 --- a/tests/transactions/serializers/test_ipfs.py +++ b/tests/transactions/serializers/test_ipfs.py @@ -1,9 +1,6 @@ -import pytest - from crypto.transactions.serializer import Serializer -@pytest.mark.skip(reason='not implemented - missing fixture') def test_serializer(transaction_type_5): - result = Serializer(transaction_type_5).serialize() + result = Serializer(transaction_type_5).serialize(False, True) assert result == transaction_type_5['serialized'] diff --git a/tests/transactions/serializers/test_multi_payment.py b/tests/transactions/serializers/test_multi_payment.py index b98c6958..08a58d14 100644 --- a/tests/transactions/serializers/test_multi_payment.py +++ b/tests/transactions/serializers/test_multi_payment.py @@ -1,9 +1,6 @@ -import pytest - from crypto.transactions.serializer import Serializer -@pytest.mark.skip(reason='not implemented - missing fixture') -def test_serializer(transaction_type_7): - result = Serializer(transaction_type_7).serialize() - assert result == transaction_type_7['serialized'] +def test_serializer(transaction_type_6): + result = Serializer(transaction_type_6).serialize(False, True) + assert result == transaction_type_6['serialized'] diff --git a/tests/transactions/serializers/test_multi_signature_registration.py b/tests/transactions/serializers/test_multi_signature_registration.py index 20eef999..5ac1f860 100644 --- a/tests/transactions/serializers/test_multi_signature_registration.py +++ b/tests/transactions/serializers/test_multi_signature_registration.py @@ -2,5 +2,5 @@ def test_serializer(transaction_type_4): - result = Serializer(transaction_type_4).serialize() + result = Serializer(transaction_type_4).serialize(False, True, False) assert result == transaction_type_4['serialized'] diff --git a/tests/transactions/serializers/test_second_signature_registration.py b/tests/transactions/serializers/test_second_signature_registration.py index 373f934c..f494e737 100644 --- a/tests/transactions/serializers/test_second_signature_registration.py +++ b/tests/transactions/serializers/test_second_signature_registration.py @@ -2,5 +2,5 @@ def test_serializer(transaction_type_1): - result = Serializer(transaction_type_1).serialize() + result = Serializer(transaction_type_1).serialize(False, False) assert result == transaction_type_1['serialized'] diff --git a/tests/transactions/serializers/test_timelock_transfer.py b/tests/transactions/serializers/test_timelock_transfer.py deleted file mode 100644 index 38b040a7..00000000 --- a/tests/transactions/serializers/test_timelock_transfer.py +++ /dev/null @@ -1,9 +0,0 @@ -import pytest - -from crypto.transactions.serializer import Serializer - - -@pytest.mark.skip(reason='not implemented - missing fixture') -def test_serializer(transaction_type_6): - result = Serializer(transaction_type_6).serialize() - assert result == transaction_type_6['serialized'] diff --git a/tests/transactions/serializers/test_transfer.py b/tests/transactions/serializers/test_transfer.py index 2ddac5b1..a7128532 100644 --- a/tests/transactions/serializers/test_transfer.py +++ b/tests/transactions/serializers/test_transfer.py @@ -2,5 +2,5 @@ def test_serializer(transaction_type_0): - result = Serializer(transaction_type_0).serialize() + result = Serializer(transaction_type_0).serialize(False, True) assert result == transaction_type_0['serialized'] diff --git a/tests/transactions/serializers/test_vote.py b/tests/transactions/serializers/test_vote.py index ab7db55a..ed83e62d 100644 --- a/tests/transactions/serializers/test_vote.py +++ b/tests/transactions/serializers/test_vote.py @@ -2,5 +2,5 @@ def test_serializer(transaction_type_3): - result = Serializer(transaction_type_3).serialize() + result = Serializer(transaction_type_3).serialize(False, True) assert result == transaction_type_3['serialized'] diff --git a/tests/transactions/test_transaction.py b/tests/transactions/test_transaction.py index 6d38a56a..76bca5f6 100644 --- a/tests/transactions/test_transaction.py +++ b/tests/transactions/test_transaction.py @@ -3,7 +3,7 @@ def test_transaction_serialize(transaction_type_0): transaction = Transaction(**transaction_type_0) - serialized = transaction.serialize() + serialized = transaction.serialize(False, True, False) assert serialized == transaction_type_0['serialized'] @@ -20,4 +20,5 @@ def test_transaction_deserialize(transaction_type_0): assert data['version'] == transaction_type_0['version'] assert data['network'] == transaction_type_0['network'] assert data['type'] == transaction_type_0['type'] - assert data['timestamp'] == transaction_type_0['timestamp'] + assert data['typeGroup'] == transaction_type_0['typeGroup'] + assert data['nonce'] == transaction_type_0['nonce'] diff --git a/tests/utils/test_message.py b/tests/utils/test_message.py index a80e330c..8f02d5e2 100644 --- a/tests/utils/test_message.py +++ b/tests/utils/test_message.py @@ -23,19 +23,10 @@ def test_to_dict(message): assert result.to_dict() == message['camelCase_pk'] -def test_to_json_with_publicKey(message): +def test_to_json(message): result = Message(**message['camelCase_pk']) json_data = result.to_json() data = json.loads(json_data) assert data['signature'] == message['camelCase_pk']['signature'] assert data['publicKey'] == message['camelCase_pk']['publicKey'] assert data['message'] == message['camelCase_pk']['message'] - - -def test_to_json_with_publickey(message): - result = Message(**message['snake_case_pk']) - json_data = result.to_json() - data = json.loads(json_data) - assert data['signature'] == message['snake_case_pk']['signature'] - assert data['publickey'] == message['snake_case_pk']['publickey'] - assert data['message'] == message['snake_case_pk']['message'] From 738c3b8210bfc2f02a55b734e0a3b25245a93ab7 Mon Sep 17 00:00:00 2001 From: Edgar Goetzendorff Date: Thu, 30 Jan 2020 22:44:25 +0100 Subject: [PATCH 34/68] fix: enable vendorfield for multipayment (#103) --- crypto/transactions/builder/multi_payment.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crypto/transactions/builder/multi_payment.py b/crypto/transactions/builder/multi_payment.py index a95b98eb..8b768510 100644 --- a/crypto/transactions/builder/multi_payment.py +++ b/crypto/transactions/builder/multi_payment.py @@ -6,10 +6,11 @@ class MultiPayment(BaseTransactionBuilder): transaction_type = TRANSACTION_MULTI_PAYMENT - def __init__(self, fee=None): + def __init__(self, vendorField=None, fee=None): """Create a multi payment transaction Args: + vendorField (str): value for the vendor field aka smartbridge fee (int, optional): fee used for the transaction (default is already set) """ super().__init__() @@ -18,6 +19,8 @@ def __init__(self, fee=None): self.transaction.asset['payments'] = [] + self.transaction.vendorField = vendorField.encode() if vendorField else None + if fee: self.transaction.fee = fee From 7f45b7fe8448e2cf66799d30fd8c0adbcb07b3ca Mon Sep 17 00:00:00 2001 From: Edgar Goetzendorff Date: Thu, 30 Jan 2020 22:46:02 +0100 Subject: [PATCH 35/68] fix: enable vendorfield for htlc lock (#104) --- crypto/transactions/builder/htlc_lock.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crypto/transactions/builder/htlc_lock.py b/crypto/transactions/builder/htlc_lock.py index 5f825eb6..baa361d3 100644 --- a/crypto/transactions/builder/htlc_lock.py +++ b/crypto/transactions/builder/htlc_lock.py @@ -6,7 +6,7 @@ class HtlcLock(BaseTransactionBuilder): transaction_type = TRANSACTION_HTLC_LOCK - def __init__(self, recipient_id, secret_hash, expiration_type, expiration_value, fee=None): + def __init__(self, recipient_id, secret_hash, expiration_type, expiration_value, vendorField=None, fee=None): """Create a timelock transaction Args: @@ -14,6 +14,7 @@ def __init__(self, recipient_id, secret_hash, expiration_type, expiration_value, secret_hash (str): a hash of the secret. The SAME hash must be used in the corresponding “claim” transaction expiration_type (int): type of the expiration. Either block height or network epoch timestamp based expiration_value (int): Expiration of transaction in seconds or height depending on expiration_type + vendorField (str): value for the vendor field aka smartbridge fee (int, optional): fee used for the transaction (default is already set) """ super().__init__() @@ -30,6 +31,8 @@ def __init__(self, recipient_id, secret_hash, expiration_type, expiration_value, } } + self.transaction.vendorField = vendorField.encode() if vendorField else None + if fee: self.transaction.fee = fee From 69233f9930850a7bd95ab31848af4d018e4b592c Mon Sep 17 00:00:00 2001 From: galperins4 Date: Wed, 5 Feb 2020 08:36:22 -0600 Subject: [PATCH 36/68] fix: update second_sign method to use schnorr (#106) * fix - update secondsign to use schnorr * feat - add schnorr second verify * feat - add second verify function to base.py * Update base.py * Update transaction.py * Update serializer.py * Update base.py * Update test_transfer.py * Update base.py * Update test_transfer.py --- crypto/transactions/builder/base.py | 8 +++++-- crypto/transactions/serializer.py | 3 +-- crypto/transactions/transaction.py | 9 ++++++++ tests/transactions/builder/test_transfer.py | 25 +++++++++++++++++++++ 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/crypto/transactions/builder/base.py b/crypto/transactions/builder/base.py index 473cc22f..7d4f5b51 100644 --- a/crypto/transactions/builder/base.py +++ b/crypto/transactions/builder/base.py @@ -47,8 +47,9 @@ def second_sign(self, passphrase): Args: passphrase (str): 2nd passphrase associated with the account sending this transaction """ - message = Message.sign(self.transaction.to_bytes(skip_signature=False), passphrase) - self.transaction.signSignature = message.signature + msg = hashlib.sha256(self.transaction.to_bytes(False, True, False)).digest() + secret = unhexlify(PrivateKey.from_passphrase(passphrase).to_hex()) + self.transaction.signSignature = hexlify(schnorr.bcrypto410_sign(msg, secret)) self.transaction.id = self.transaction.get_id() def multi_sign(self, passphrase, index): @@ -66,6 +67,9 @@ def multi_sign(self, passphrase, index): def schnorr_verify(self): return self.transaction.verify_schnorr() + + def schnorr_verify_second(self, secondPublicKey): + return self.transaction.verify_schnorr_secondsig(secondPublicKey) def schnorr_verify_multisig(self): return self.transaction.verify_schnorr_multisig() diff --git a/crypto/transactions/serializer.py b/crypto/transactions/serializer.py index b0e8100d..f95e9c93 100644 --- a/crypto/transactions/serializer.py +++ b/crypto/transactions/serializer.py @@ -94,9 +94,8 @@ def _handle_signature(self, bytes_data, skip_signature, skip_second_signature, s if not skip_second_signature and self.transaction.get('secondSignature'): bytes_data += unhexlify(self.transaction['secondSignature']) - elif self.transaction.get('signSignature'): + if not skip_second_signature and self.transaction.get('signSignature'): bytes_data += unhexlify(self.transaction['signSignature']) - if not skip_multi_signature and self.transaction.get('signatures'): bytes_data += unhexlify(''.join(self.transaction['signatures'])) diff --git a/crypto/transactions/transaction.py b/crypto/transactions/transaction.py index e2ce9369..b3a2a653 100644 --- a/crypto/transactions/transaction.py +++ b/crypto/transactions/transaction.py @@ -162,6 +162,15 @@ def verify_schnorr(self): return is_valid + def verify_schnorr_secondsig(self, secondPublicKey): + """Verify the transaction. Method will raise an exception if invalid, if it's valid it will + returns True + """ + is_valid = schnorr.b410_schnorr_verify(self.to_bytes(False, True), secondPublicKey, self.signSignature) + + if not is_valid: + raise ArkInvalidTransaction('Transaction could not be verified') + def verify_schnorr_multisig(self): """Verify the multisignatures transaction. Method will raise an exception if invalid, it will returns True diff --git a/tests/transactions/builder/test_transfer.py b/tests/transactions/builder/test_transfer.py index 1f5282db..8c4650f2 100644 --- a/tests/transactions/builder/test_transfer.py +++ b/tests/transactions/builder/test_transfer.py @@ -1,5 +1,6 @@ from crypto.configuration.network import set_network from crypto.constants import TRANSACTION_TRANSFER, TRANSACTION_TYPE_GROUP +from crypto.identity.public_key import PublicKey from crypto.networks.devnet import Devnet from crypto.transactions.builder.transfer import Transfer @@ -27,6 +28,30 @@ def test_transfer_transaction(): transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid +def test_transfer_secondsig_transaction(): + """Test if a transfer transaction with second signature gets built + """ + transaction = Transfer( + recipientId='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', + amount=200000000, + ) + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.set_nonce(1) + transaction.schnorr_sign('this is a top secret passphrase') + transaction.second_sign('second top secret passphrase') + transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 + assert transaction_dict['signature'] + assert transaction_dict['signSignature'] + assert transaction_dict['type'] is TRANSACTION_TRANSFER + assert transaction_dict['typeGroup'] == 1 + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid + transaction.schnorr_verify_second(PublicKey.from_passphrase('second top secret passphrase')) # if no exception is raised, it means the transaction is valid + + def test_parse_signatures(transaction_type_0): """Test if parse signature works when parsing serialized data """ From 4cfc57651dbc4978bead30ea199a936ec366eb60 Mon Sep 17 00:00:00 2001 From: Brian Faust Date: Mon, 10 Feb 2020 06:12:41 +0200 Subject: [PATCH 37/68] release: 1.0.0 (#111) --- CHANGELOG.md | 7 +++++++ setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26fce230..02012821 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## Unreleased +## 1.0.0 - 2019-02-11 + +### Added + +- Implement AIP11 +- Implement AIP18 + ## 0.1.1 - 2018-08-29 ### Fixed diff --git a/setup.py b/setup.py index a7a0c5c3..313d15d6 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ setuptools.setup( name='arkecosystem-crypto', description='A simple Cryptography Implementation in Python for the Ark Blockchain.', - version='0.1.1', + version='1.0.0', author='Ark Ecosystem', author_email='info@ark.io', url='https://github.com/ArkEcosystem/python-crypto', From 6b23b771696cca0c227168fe2595c3cd92564cca Mon Sep 17 00:00:00 2001 From: Brian Faust Date: Mon, 10 Feb 2020 06:21:28 +0200 Subject: [PATCH 38/68] chore: fix wrong release year (#112) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02012821..dee63869 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## Unreleased -## 1.0.0 - 2019-02-11 +## 1.0.0 - 2020-02-11 ### Added From 0e38682a890461dcacd9ecc1705cb2bba4e33fc1 Mon Sep 17 00:00:00 2001 From: Chris Johnson Date: Mon, 15 Feb 2021 21:37:47 -0600 Subject: [PATCH 39/68] ci: test against python 3.8 and 3.9 (#117) --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 030dfd99..dc62fc35 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: [3.5, 3.6, 3.7] + python-version: [3.5, 3.6, 3.7, 3.8, 3.9] steps: - uses: actions/checkout@v1 From 5d84769eee62281a3a202650c2ee8cf6f9396369 Mon Sep 17 00:00:00 2001 From: Chris Johnson Date: Mon, 15 Feb 2021 21:38:25 -0600 Subject: [PATCH 40/68] feat: add amount to HTLC lock constructor (#116) --- crypto/transactions/builder/htlc_lock.py | 8 +++- tests/transactions/builder/test_htlc_lock.py | 42 ++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/crypto/transactions/builder/htlc_lock.py b/crypto/transactions/builder/htlc_lock.py index baa361d3..0ee2c7a5 100644 --- a/crypto/transactions/builder/htlc_lock.py +++ b/crypto/transactions/builder/htlc_lock.py @@ -6,11 +6,12 @@ class HtlcLock(BaseTransactionBuilder): transaction_type = TRANSACTION_HTLC_LOCK - def __init__(self, recipient_id, secret_hash, expiration_type, expiration_value, vendorField=None, fee=None): + def __init__(self, recipient_id, amount, secret_hash, expiration_type, expiration_value, vendorField=None, fee=None): """Create a timelock transaction Args: recipient_id (str): recipient identifier + amount (int): amount of coins you want to transfer secret_hash (str): a hash of the secret. The SAME hash must be used in the corresponding “claim” transaction expiration_type (int): type of the expiration. Either block height or network epoch timestamp based expiration_value (int): Expiration of transaction in seconds or height depending on expiration_type @@ -20,6 +21,11 @@ def __init__(self, recipient_id, secret_hash, expiration_type, expiration_value, super().__init__() self.transaction.recipientId = recipient_id + + if type(amount) == int and amount > 0: + self.transaction.amount = amount + else: + raise ValueError('Amount is not valid') self.transaction.typeGroup = self.get_type_group() diff --git a/tests/transactions/builder/test_htlc_lock.py b/tests/transactions/builder/test_htlc_lock.py index 92a39d50..45a088fe 100644 --- a/tests/transactions/builder/test_htlc_lock.py +++ b/tests/transactions/builder/test_htlc_lock.py @@ -2,15 +2,56 @@ from crypto.constants import TRANSACTION_HTLC_LOCK, TRANSACTION_TYPE_GROUP from crypto.networks.devnet import Devnet from crypto.transactions.builder.htlc_lock import HtlcLock +import pytest set_network(Devnet) +def test_htlc_lock_transation_amount_not_int(): + with pytest.raises(ValueError): + """Test error handling in constructor for non-integer amount + """ + HtlcLock( + recipient_id='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', + amount='bad amount number', + secret_hash='0f128d401958b1b30ad0d10406f47f9489321017b4614e6cb993fc63913c5454', + expiration_type=1, + expiration_value=1573455822 + ) + + +def test_htlc_lock_transation_amount_zero(): + with pytest.raises(ValueError): + """Test error handling in constructor for non-integer amount + """ + HtlcLock( + recipient_id='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', + amount=0, + secret_hash='0f128d401958b1b30ad0d10406f47f9489321017b4614e6cb993fc63913c5454', + expiration_type=1, + expiration_value=1573455822 + ) + + +def test_htlc_lock_transation_amount_negative(): + with pytest.raises(ValueError): + """Test error handling in constructor for non-integer amount + """ + HtlcLock( + recipient_id='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', + amount=-5, + secret_hash='0f128d401958b1b30ad0d10406f47f9489321017b4614e6cb993fc63913c5454', + expiration_type=1, + expiration_value=1573455822 + ) + + def test_htlc_lock_transaction(): """Test if timelock transaction gets built """ transaction = HtlcLock( recipient_id='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', + amount=200000000, secret_hash='0f128d401958b1b30ad0d10406f47f9489321017b4614e6cb993fc63913c5454', expiration_type=1, expiration_value=1573455822 @@ -22,6 +63,7 @@ def test_htlc_lock_transaction(): transaction_dict = transaction.to_dict() assert transaction_dict['recipientId'] == 'AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC' + assert transaction_dict['amount'] == 200000000 assert transaction_dict['nonce'] == 1 assert transaction_dict['signature'] assert transaction_dict['type'] is TRANSACTION_HTLC_LOCK From c2dd3d2592065e7b83cda635d840190a3a61b735 Mon Sep 17 00:00:00 2001 From: Chris Johnson Date: Tue, 16 Feb 2021 21:58:37 -0600 Subject: [PATCH 41/68] chore: Increase test coverage for transactions (#119) --- tests/configuration/test_network.py | 8 ++ .../builder/test_delegate_registration.py | 23 ++++++ .../builder/test_delegate_resignation.py | 20 +++++ tests/transactions/builder/test_htlc_claim.py | 27 +++++++ tests/transactions/builder/test_htlc_lock.py | 36 ++++++++- .../transactions/builder/test_htlc_refund.py | 25 ++++++ tests/transactions/builder/test_ipfs.py | 23 ++++++ .../builder/test_multi_payment.py | 26 +++++++ .../test_second_signature_registration.py | 20 +++++ tests/transactions/builder/test_transfer.py | 76 ++++++++++++++++++- tests/transactions/builder/test_vote.py | 23 ++++++ 11 files changed, 303 insertions(+), 4 deletions(-) diff --git a/tests/configuration/test_network.py b/tests/configuration/test_network.py index e33f52d6..3b608e45 100644 --- a/tests/configuration/test_network.py +++ b/tests/configuration/test_network.py @@ -3,6 +3,7 @@ from crypto.configuration.network import get_network, set_custom_network, set_network from crypto.networks.devnet import Devnet from crypto.networks.mainnet import Mainnet +from crypto.networks.testnet import Testnet def test_get_network(): @@ -11,9 +12,16 @@ def test_get_network(): def test_set_network(): + # test main net set_network(Mainnet) result = get_network() assert result['version'] == 23 + assert result['wif'] == 170 + # test test net + set_network(Testnet) + result = get_network() + assert result['version'] == 23 + assert result['wif'] == 186 set_network(Devnet) # set back to devnet so other tests don't fail diff --git a/tests/transactions/builder/test_delegate_registration.py b/tests/transactions/builder/test_delegate_registration.py index 43e042e7..f5d08cff 100644 --- a/tests/transactions/builder/test_delegate_registration.py +++ b/tests/transactions/builder/test_delegate_registration.py @@ -23,5 +23,28 @@ def test_delegate_registration_transaction(): assert transaction_dict['type'] is TRANSACTION_DELEGATE_REGISTRATION assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['fee'] == 2500000000 + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid + + +def test_delegate_registration_transaction_custom_fee(): + """Test if a delegate registration transaction gets built with a custom fee + """ + delegate_name = 'mr.delegate' + + transaction = DelegateRegistration(delegate_name, 5) + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.set_nonce(1) + transaction.schnorr_sign('testing') + transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 + assert transaction_dict['signature'] + assert transaction_dict['asset']['delegate']['username'] == delegate_name + assert transaction_dict['type'] is TRANSACTION_DELEGATE_REGISTRATION + assert transaction_dict['typeGroup'] == 1 + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['fee'] == 5 transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_delegate_resignation.py b/tests/transactions/builder/test_delegate_resignation.py index 27305f7b..c760b837 100644 --- a/tests/transactions/builder/test_delegate_resignation.py +++ b/tests/transactions/builder/test_delegate_resignation.py @@ -20,5 +20,25 @@ def test_delegate_resignation_transaction(): assert transaction_dict['type'] is TRANSACTION_DELEGATE_RESIGNATION assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['fee'] == 2500000000 + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid + + +def test_delegate_resignation_transaction_custom_fee(): + """Test if delegate resignation transaction gets built with a custom fee + """ + transaction = DelegateResignation(5) + transaction.set_nonce(1) + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.schnorr_sign('testing') + transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 + assert transaction_dict['signature'] + assert transaction_dict['type'] is TRANSACTION_DELEGATE_RESIGNATION + assert transaction_dict['typeGroup'] == 1 + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['fee'] == 5 transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_htlc_claim.py b/tests/transactions/builder/test_htlc_claim.py index 891a8110..3237bee3 100644 --- a/tests/transactions/builder/test_htlc_claim.py +++ b/tests/transactions/builder/test_htlc_claim.py @@ -25,6 +25,33 @@ def test_htlc_claim_transaction(): assert transaction_dict['type'] is TRANSACTION_HTLC_CLAIM assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['fee'] == 0 + assert transaction_dict['asset']['claim']['lockTransactionId'] == '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' + assert transaction_dict['asset']['claim']['unlockSecret'] == 'my secret that should be 32bytes' + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid + + +def test_htlc_claim_transaction_custom_fee(): + """Test if timelock transaction gets built with a custom fee + """ + lock_transaction_id = '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' + + # This should be the hashed secret used for HTLC Lock transaction + unlock_secret = 'my secret that should be 32bytes' + + transaction = HtlcClaim(lock_transaction_id, unlock_secret, 5) + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.set_nonce(1) + transaction.schnorr_sign('testing') + transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 + assert transaction_dict['signature'] + assert transaction_dict['type'] is TRANSACTION_HTLC_CLAIM + assert transaction_dict['typeGroup'] == 1 + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['fee'] == 5 assert transaction_dict['asset']['claim']['lockTransactionId'] == '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' assert transaction_dict['asset']['claim']['unlockSecret'] == 'my secret that should be 32bytes' diff --git a/tests/transactions/builder/test_htlc_lock.py b/tests/transactions/builder/test_htlc_lock.py index 45a088fe..9b103f65 100644 --- a/tests/transactions/builder/test_htlc_lock.py +++ b/tests/transactions/builder/test_htlc_lock.py @@ -1,8 +1,9 @@ +import pytest + from crypto.configuration.network import set_network from crypto.constants import TRANSACTION_HTLC_LOCK, TRANSACTION_TYPE_GROUP from crypto.networks.devnet import Devnet from crypto.transactions.builder.htlc_lock import HtlcLock -import pytest set_network(Devnet) @@ -69,6 +70,39 @@ def test_htlc_lock_transaction(): assert transaction_dict['type'] is TRANSACTION_HTLC_LOCK assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['fee'] == 10000000 + assert transaction_dict['asset']['lock']['secretHash'] == '0f128d401958b1b30ad0d10406f47f9489321017b4614e6cb993fc63913c5454' + assert transaction_dict['asset']['lock']['expiration']['type'] == 1 + assert transaction_dict['asset']['lock']['expiration']['value'] == 1573455822 + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid + + +def test_htlc_lock_transaction_custom_fee(): + """Test if timelock transaction gets built with a custom fee + """ + transaction = HtlcLock( + recipient_id='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', + amount=200000000, + secret_hash='0f128d401958b1b30ad0d10406f47f9489321017b4614e6cb993fc63913c5454', + expiration_type=1, + expiration_value=1573455822, + fee=5 + ) + + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.set_nonce(1) + transaction.schnorr_sign('testing') + transaction_dict = transaction.to_dict() + + assert transaction_dict['recipientId'] == 'AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC' + assert transaction_dict['amount'] == 200000000 + assert transaction_dict['nonce'] == 1 + assert transaction_dict['signature'] + assert transaction_dict['type'] is TRANSACTION_HTLC_LOCK + assert transaction_dict['typeGroup'] == 1 + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['fee'] == 5 assert transaction_dict['asset']['lock']['secretHash'] == '0f128d401958b1b30ad0d10406f47f9489321017b4614e6cb993fc63913c5454' assert transaction_dict['asset']['lock']['expiration']['type'] == 1 assert transaction_dict['asset']['lock']['expiration']['value'] == 1573455822 diff --git a/tests/transactions/builder/test_htlc_refund.py b/tests/transactions/builder/test_htlc_refund.py index 116e1bdd..d5100ef5 100644 --- a/tests/transactions/builder/test_htlc_refund.py +++ b/tests/transactions/builder/test_htlc_refund.py @@ -23,6 +23,31 @@ def test_timelock_refund_transaction(): assert transaction_dict['type'] is TRANSACTION_HTLC_REFUND assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['fee'] == 0 + assert transaction_dict['asset']['refund']['lockTransactionId'] == '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid + + +def test_timelock_refund_transaction_custom_fee(): + """Test if timelock transaction gets built with custom fee + """ + transaction = HtlcRefund( + lock_transaction_id='943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4', + fee=5 + ) + + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.set_nonce(1) + transaction.schnorr_sign('testing') + transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 + assert transaction_dict['signature'] + assert transaction_dict['type'] is TRANSACTION_HTLC_REFUND + assert transaction_dict['typeGroup'] == 1 + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['fee'] == 5 assert transaction_dict['asset']['refund']['lockTransactionId'] == '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_ipfs.py b/tests/transactions/builder/test_ipfs.py index ecca2e83..02b5521b 100644 --- a/tests/transactions/builder/test_ipfs.py +++ b/tests/transactions/builder/test_ipfs.py @@ -24,6 +24,29 @@ def test_ipfs_transaction(): assert transaction_dict['type'] is TRANSACTION_IPFS assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['fee'] == 500000000 + assert transaction_dict['asset']['ipfs'] == ipfs_id + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid + + +def test_ipfs_transaction_custom_fee(): + """Test if ipfs transaction gets built with custom fee + """ + ipfs_id = b58encode('hello') + + transaction = IPFS(ipfs_id, 5) + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.set_nonce(1) + transaction.schnorr_sign('testing') + transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 + assert transaction_dict['signature'] + assert transaction_dict['type'] is TRANSACTION_IPFS + assert transaction_dict['typeGroup'] == 1 + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['fee'] == 5 assert transaction_dict['asset']['ipfs'] == ipfs_id transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_multi_payment.py b/tests/transactions/builder/test_multi_payment.py index 9314efd0..6cca3173 100644 --- a/tests/transactions/builder/test_multi_payment.py +++ b/tests/transactions/builder/test_multi_payment.py @@ -22,6 +22,32 @@ def test_multi_payment_transaction(): assert transaction_dict['type'] is TRANSACTION_MULTI_PAYMENT assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['fee'] == 10000000 + assert transaction_dict['asset']['payments'][0]['amount'] == 1 + assert transaction_dict['asset']['payments'][0]['recipientId'] == 'AHXtmB84sTZ9Zd35h9Y1vfFvPE2Xzqj8ri' + assert transaction_dict['asset']['payments'][1]['amount'] == 2 + assert transaction_dict['asset']['payments'][1]['recipientId'] == 'ATK14wxyYxbELq2b91bAfBY8Vmh9J6MDej' + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid + + +def test_multi_payment_transaction_custom_fee(): + """Test if multi payment transaction gets built with a custom fee + """ + transaction = MultiPayment(fee=5) + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.set_nonce(1) + transaction.add_payment(1, 'AHXtmB84sTZ9Zd35h9Y1vfFvPE2Xzqj8ri') + transaction.add_payment(2, 'ATK14wxyYxbELq2b91bAfBY8Vmh9J6MDej') + transaction.schnorr_sign('testing') + transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 + assert transaction_dict['signature'] + assert transaction_dict['type'] is TRANSACTION_MULTI_PAYMENT + assert transaction_dict['typeGroup'] == 1 + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['fee'] == 5 assert transaction_dict['asset']['payments'][0]['amount'] == 1 assert transaction_dict['asset']['payments'][0]['recipientId'] == 'AHXtmB84sTZ9Zd35h9Y1vfFvPE2Xzqj8ri' assert transaction_dict['asset']['payments'][1]['amount'] == 2 diff --git a/tests/transactions/builder/test_second_signature_registration.py b/tests/transactions/builder/test_second_signature_registration.py index 1ed58134..8da33638 100644 --- a/tests/transactions/builder/test_second_signature_registration.py +++ b/tests/transactions/builder/test_second_signature_registration.py @@ -20,5 +20,25 @@ def test_second_signature_registration_transaction(): assert transaction_dict['type'] is TRANSACTION_SECOND_SIGNATURE_REGISTRATION assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['fee'] == 500000000 + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid + + +def test_second_signature_registration_transaction_custom_fee(): + """Test if a second signature registration transaction gets built with a custom fee + """ + transaction = SecondSignatureRegistration('this is a top secret passphrase', 5) + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.set_nonce(1) + transaction.schnorr_sign('testing') + transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 + assert transaction_dict['signature'] + assert transaction_dict['type'] is TRANSACTION_SECOND_SIGNATURE_REGISTRATION + assert transaction_dict['typeGroup'] == 1 + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['fee'] == 5 transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_transfer.py b/tests/transactions/builder/test_transfer.py index 8c4650f2..1c6c81d9 100644 --- a/tests/transactions/builder/test_transfer.py +++ b/tests/transactions/builder/test_transfer.py @@ -1,9 +1,12 @@ +import pytest + from crypto.configuration.network import set_network from crypto.constants import TRANSACTION_TRANSFER, TRANSACTION_TYPE_GROUP from crypto.identity.public_key import PublicKey from crypto.networks.devnet import Devnet from crypto.transactions.builder.transfer import Transfer + set_network(Devnet) @@ -24,6 +27,53 @@ def test_transfer_transaction(): assert transaction_dict['type'] is TRANSACTION_TRANSFER assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['fee'] == 10000000 + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid + + +def test_transfer_transaction_update_amount(): + """Test if a transfer transaction can update an amount + """ + transaction = Transfer( + recipientId='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', + amount=200000000 + ) + transaction.set_amount(10) + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.set_nonce(1) + transaction.schnorr_sign('this is a top secret passphrase') + transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 + assert transaction_dict['signature'] + assert transaction_dict['type'] is TRANSACTION_TRANSFER + assert transaction_dict['typeGroup'] == 1 + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['amount'] == 10 + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid + + +def test_transfer_transaction_custom_fee(): + """Test if a transfer transaction gets built with a custom fee + """ + transaction = Transfer( + recipientId='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', + amount=200000000, + fee=5 + ) + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.set_nonce(1) + transaction.schnorr_sign('this is a top secret passphrase') + transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 + assert transaction_dict['signature'] + assert transaction_dict['type'] is TRANSACTION_TRANSFER + assert transaction_dict['typeGroup'] == 1 + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['fee'] == 5 transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid @@ -49,9 +99,9 @@ def test_transfer_secondsig_transaction(): assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid - transaction.schnorr_verify_second(PublicKey.from_passphrase('second top secret passphrase')) # if no exception is raised, it means the transaction is valid - - + transaction.schnorr_verify_second(PublicKey.from_passphrase('second top secret passphrase')) # if no exception is raised, it means the transaction is valid + + def test_parse_signatures(transaction_type_0): """Test if parse signature works when parsing serialized data """ @@ -62,3 +112,23 @@ def test_parse_signatures(transaction_type_0): assert transfer.transaction.signature is None transfer.transaction.parse_signatures(transaction_type_0['serialized'], 166) assert transfer.transaction.signature + + +def test_transfer_transaction_amount_not_int(): + with pytest.raises(ValueError): + """Test error handling in constructor for non-integer amount + """ + Transfer( + recipientId='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', + amount='bad amount' + ) + + +def test_transfer_transaction_amount_zero(): + with pytest.raises(ValueError): + """Test error handling in constructor for non-integer amount + """ + Transfer( + recipientId='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', + amount=0 + ) diff --git a/tests/transactions/builder/test_vote.py b/tests/transactions/builder/test_vote.py index 2d7e862c..3f87333d 100644 --- a/tests/transactions/builder/test_vote.py +++ b/tests/transactions/builder/test_vote.py @@ -23,5 +23,28 @@ def test_vote_transaction(): assert transaction_dict['type'] is TRANSACTION_VOTE assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['fee'] == 100000000 + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid + + +def test_vote_transaction_custom_fee(): + """Test if a vote transaction gets built with a custom fee + """ + vote = '+034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' + + transaction = Vote(vote, 5) + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.set_nonce(1) + transaction.schnorr_sign('testing') + transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 + assert transaction_dict['signature'] + assert transaction_dict['asset']['votes'] + assert transaction_dict['type'] is TRANSACTION_VOTE + assert transaction_dict['typeGroup'] == 1 + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['fee'] == 5 transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid From 818a349e2a0194f1e5a708567ece0de3f99c42a7 Mon Sep 17 00:00:00 2001 From: Jolan Date: Wed, 8 Sep 2021 15:43:17 +0200 Subject: [PATCH 42/68] chore: update README links (#121) --- .github/workflows/test.yml | 2 +- README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dc62fc35..ffd42f4d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: [3.5, 3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9] steps: - uses: actions/checkout@v1 diff --git a/README.md b/README.md index c7f3d9be..bc78598a 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,11 @@ Before you start contributing please take some time and check our official [Pyth 5. Run the entire test suite again, confirming that all tests pass including the ones you just added. 6. Send a GitHub Pull Request. GitHub Pull Requests are the expected method of code collaboration on this project. -If you have any questions, requests or ideas open an issue or ask us in #python on the [ArkEcosystem Slack](https://ark.io/slack). +If you have any questions, requests or ideas open an issue or ask us in #developers channel on the [ArkEcosystem Discord](https://discord.ark.io/). ## Documentation -You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://docs.ark.io/sdk/cryptography/python.html). +You can find installation instructions and detailed instructions on how to use this package at the [dedicated documentation site](https://ark.dev/docs/sdk/python/crypto/intro). ## Security From 928771cf99b7c6f58f057ae1e9323db870887b80 Mon Sep 17 00:00:00 2001 From: ItsANameToo Date: Tue, 21 Sep 2021 10:03:59 +0200 Subject: [PATCH 43/68] release: 2.0.0 --- CHANGELOG.md | 6 ++++++ README.md | 2 -- setup.py | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dee63869..384c278f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## Unreleased +## 2.0.0 - 2021-09-21 + +## Fixed + +- Add amount option to HTLC transactions + ## 1.0.0 - 2020-02-11 ### Added diff --git a/README.md b/README.md index bc78598a..dca05c1f 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,6 @@ ## Guide for contributing -Before you start contributing please take some time and check our official [Python Development Guidelines](https://github.com/ArkEcosystem/development-guidelines/blob/master/Python/README.md) which follow the widely accepted PEP8 Python Style Guide. 🖋 - 1. Fork the repository on GitHub. 2. Run the tests to confirm they all pass on your system. If they don’t, you’ll need to investigate why they fail. If you’re unable to diagnose this yourself raise it as a bug report. 3. Make your change. diff --git a/setup.py b/setup.py index 313d15d6..0a5c77db 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ setuptools.setup( name='arkecosystem-crypto', description='A simple Cryptography Implementation in Python for the Ark Blockchain.', - version='1.0.0', + version='2.0.0', author='Ark Ecosystem', author_email='info@ark.io', url='https://github.com/ArkEcosystem/python-crypto', From 676c942de05e0955cceb88cff3f4e3d0b61dd169 Mon Sep 17 00:00:00 2001 From: console <60887596+c0nsol3@users.noreply.github.com> Date: Wed, 20 Apr 2022 02:46:27 +0100 Subject: [PATCH 44/68] test: improve HTLC tests, add docs, raise exception on invalid unlock_secret format (#123) --- crypto/transactions/builder/htlc_claim.py | 3 + .../transactions/deserializers/htlc_claim.py | 2 +- crypto/transactions/serializers/htlc_claim.py | 9 ++- tests/conftest.py | 24 ++++---- tests/transactions/builder/test_htlc_claim.py | 57 +++++++++++++++---- tests/transactions/builder/test_htlc_lock.py | 32 ++++++++--- .../deserializers/test_htlc_claim.py | 12 ++-- 7 files changed, 97 insertions(+), 42 deletions(-) diff --git a/crypto/transactions/builder/htlc_claim.py b/crypto/transactions/builder/htlc_claim.py index 4f245ad9..9f153982 100644 --- a/crypto/transactions/builder/htlc_claim.py +++ b/crypto/transactions/builder/htlc_claim.py @@ -1,4 +1,5 @@ from crypto.constants import TRANSACTION_HTLC_CLAIM, TRANSACTION_TYPE_GROUP +from crypto.exceptions import ArkInvalidTransaction from crypto.transactions.builder.base import BaseTransactionBuilder @@ -10,6 +11,8 @@ def __init__(self, lock_transaction_id, unlock_secret, fee=None): """Create a timelock transaction Args: + lock_transaction_id (str): HTLC lock transaction ID we wish to claim + unlock_secret (str): unlock secret required to claim the transaction, must be 64 chars long fee (int, optional): fee used for the transaction (default is already set) """ super().__init__() diff --git a/crypto/transactions/deserializers/htlc_claim.py b/crypto/transactions/deserializers/htlc_claim.py index 865b0259..13243520 100644 --- a/crypto/transactions/deserializers/htlc_claim.py +++ b/crypto/transactions/deserializers/htlc_claim.py @@ -9,7 +9,7 @@ def deserialize(self): starting_position = int(self.asset_offset / 2) lock_transaction_id = hexlify(self.serialized[starting_position:starting_position + 32]) - unlock_secret = self.serialized[starting_position + 32:starting_position + 64] + unlock_secret = hexlify(self.serialized[starting_position + 32:starting_position + 64]) self.transaction.asset['claim'] = { 'lockTransactionId': lock_transaction_id.decode(), diff --git a/crypto/transactions/serializers/htlc_claim.py b/crypto/transactions/serializers/htlc_claim.py index 6ab1804a..68ed6330 100644 --- a/crypto/transactions/serializers/htlc_claim.py +++ b/crypto/transactions/serializers/htlc_claim.py @@ -1,6 +1,7 @@ -from binascii import hexlify, unhexlify +from binascii import unhexlify from crypto.transactions.serializers.base import BaseSerializer +from crypto.exceptions import ArkSerializerException class HtlcClaimSerializer(BaseSerializer): @@ -9,7 +10,9 @@ class HtlcClaimSerializer(BaseSerializer): def serialize(self): self.bytes_data += unhexlify(self.transaction['asset']['claim']['lockTransactionId']) - unlock_secret = hexlify(self.transaction['asset']['claim']['unlockSecret'].encode()) - self.bytes_data += unhexlify(unlock_secret) + unlock_secret_bytes = unhexlify(self.transaction['asset']['claim']['unlockSecret'].encode()) + if len(unlock_secret_bytes) != 32: + raise ArkSerializerException("Unlock secret must be 32 bytes long") + self.bytes_data += unlock_secret_bytes return self.bytes_data diff --git a/tests/conftest.py b/tests/conftest.py index 151b1e94..18de0c83 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -241,24 +241,22 @@ def transaction_type_9(): """Transaction of type "HTLC claim" """ data = { - 'version': 2, - 'network': 23, - 'typeGroup': 1, - 'type': 9, - 'nonce': 1, - 'senderPublicKey': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', - 'fee': 0, 'amount': 0, 'asset': { 'claim': { 'lockTransactionId': '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4', - 'unlockSecret': 'my secret that should be 32bytes' - }, + 'unlockSecret': '6434323233626639336532303235303561366135303134323161383864396661' + } }, - 'signature': '381188e6a3c0da8823ab37cf7562724b3920f4fc8a40cb259ae297bd7237b511cbfdbcb46b7afa319ad1c2d8cc3d8cdc33a437c8b17867777b891d03c036dfb9', - 'id': '846c5ee8a328376416735da43056d154d41e264564def42fb28b373c0d895c46', - 'serialized': 'ff02170100000009000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192000000000000000000943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb46d792073656372657420746861742073686f756c642062652033326279746573381188e6a3c0da8823ab37cf7562724b3920f4fc8a40cb259ae297bd7237b511cbfdbcb46b7afa319ad1c2d8cc3d8cdc33a437c8b17867777b891d03c036dfb9' - + 'fee': 0, + 'id': 'aad3fdb321e2543af1dd9d2d4d155473a8b49cacbc116fa1f3f1b95154b336d5', + 'senderPublicKey': '037fde73baaa48eb75c013fe9ff52a74a096d48b9978351bdcb5b72331ca37487c', + 'signature': 'dcd867411d20c7aa891e44cd92e916ea1d1e64ef1518dfcdfa227e4415d846a66c60718dc9d4bfc354afa69c2f8fa6e68f57e6eaf53c51b7a209ead5702ffd71', + 'nonce': 1, + 'type': 9, + 'typeGroup': 1, + 'version': 2, + 'serialized': 'ff021e0100000009000100000000000000037fde73baaa48eb75c013fe9ff52a74a096d48b9978351bdcb5b72331ca37487c000000000000000000943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb46434323233626639336532303235303561366135303134323161383864396661dcd867411d20c7aa891e44cd92e916ea1d1e64ef1518dfcdfa227e4415d846a66c60718dc9d4bfc354afa69c2f8fa6e68f57e6eaf53c51b7a209ead5702ffd71', } return data diff --git a/tests/transactions/builder/test_htlc_claim.py b/tests/transactions/builder/test_htlc_claim.py index 3237bee3..757145eb 100644 --- a/tests/transactions/builder/test_htlc_claim.py +++ b/tests/transactions/builder/test_htlc_claim.py @@ -1,21 +1,24 @@ +import binascii +import pytest from crypto.configuration.network import set_network from crypto.constants import TRANSACTION_HTLC_CLAIM, TRANSACTION_TYPE_GROUP from crypto.networks.devnet import Devnet +from crypto.exceptions import ArkSerializerException from crypto.transactions.builder.htlc_claim import HtlcClaim set_network(Devnet) -def test_htlc_claim_transaction(): +def test_htlc_claim_transaction_ok(): """Test if timelock transaction gets built """ lock_transaction_id = '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' - # This should be the hashed secret used for HTLC Lock transaction - unlock_secret = 'my secret that should be 32bytes' + # Secret required to unlock and claim funds from an HTLC. This secret should is revealed to you + # on the blockchain which you can then use to claim funds. + unlock_secret = '6434323233626639336532303235303561366135303134323161383864396661' transaction = HtlcClaim(lock_transaction_id, unlock_secret) - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) transaction.set_nonce(1) transaction.schnorr_sign('testing') transaction_dict = transaction.to_dict() @@ -23,25 +26,56 @@ def test_htlc_claim_transaction(): assert transaction_dict['nonce'] == 1 assert transaction_dict['signature'] assert transaction_dict['type'] is TRANSACTION_HTLC_CLAIM - assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 0 assert transaction_dict['asset']['claim']['lockTransactionId'] == '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' - assert transaction_dict['asset']['claim']['unlockSecret'] == 'my secret that should be 32bytes' + assert transaction_dict['asset']['claim']['unlockSecret'] == unlock_secret transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid -def test_htlc_claim_transaction_custom_fee(): +def test_htlc_claim_transaction_unlock_secret_not_hex(): + """Test if timelock transaction errors if an invalid hex unlock_secret is given + """ + lock_transaction_id = '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' + + # Secret required to unlock and claim funds from an HTLC. This secret should is revealed to you + # on the blockchain which you can then use to claim funds. + unlock_secret = '643432323362663933653230323530356136613530313432316138a84a3966x1' + + transaction = HtlcClaim(lock_transaction_id, unlock_secret) + transaction.set_nonce(1) + with pytest.raises(binascii.Error) as e: + transaction.schnorr_sign('testing') + assert str(e.value) == 'Non-hexadecimal digit found' + + +def test_htlc_claim_transaction_unlock_secret_bad_length(): + """Test if timelock transaction fails if the unlock_secret is too big + """ + lock_transaction_id = '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' + + # Secret required to unlock and claim funds from an HTLC. This secret should is revealed to you + # on the blockchain which you can then use to claim funds. + unlock_secret = '326632383634643861626534336530363236363132643461636235623535666462' + + transaction = HtlcClaim(lock_transaction_id, unlock_secret) + transaction.set_nonce(1) + with pytest.raises(ArkSerializerException) as e: + transaction.schnorr_sign('testing') + assert str(e.value) == 'Unlock secret must be 32 bytes long' + + +def test_htlc_claim_transaction_custom_fee_ok(): """Test if timelock transaction gets built with a custom fee """ lock_transaction_id = '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' - # This should be the hashed secret used for HTLC Lock transaction - unlock_secret = 'my secret that should be 32bytes' + # Secret required to unlock and claim funds from an HTLC. This secret should is revealed to you + # on the blockchain which you can then use to claim funds. + unlock_secret = '6434323233626639336532303235303561366135303134323161383864396661' transaction = HtlcClaim(lock_transaction_id, unlock_secret, 5) - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) transaction.set_nonce(1) transaction.schnorr_sign('testing') transaction_dict = transaction.to_dict() @@ -49,10 +83,9 @@ def test_htlc_claim_transaction_custom_fee(): assert transaction_dict['nonce'] == 1 assert transaction_dict['signature'] assert transaction_dict['type'] is TRANSACTION_HTLC_CLAIM - assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 5 assert transaction_dict['asset']['claim']['lockTransactionId'] == '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' - assert transaction_dict['asset']['claim']['unlockSecret'] == 'my secret that should be 32bytes' + assert transaction_dict['asset']['claim']['unlockSecret'] == unlock_secret transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_htlc_lock.py b/tests/transactions/builder/test_htlc_lock.py index 9b103f65..a7aa79ef 100644 --- a/tests/transactions/builder/test_htlc_lock.py +++ b/tests/transactions/builder/test_htlc_lock.py @@ -1,3 +1,4 @@ +from hashlib import sha256 import pytest from crypto.configuration.network import set_network @@ -12,10 +13,11 @@ def test_htlc_lock_transation_amount_not_int(): with pytest.raises(ValueError): """Test error handling in constructor for non-integer amount """ + secret_hash = _generate_secret_hash() HtlcLock( recipient_id='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', amount='bad amount number', - secret_hash='0f128d401958b1b30ad0d10406f47f9489321017b4614e6cb993fc63913c5454', + secret_hash=secret_hash, expiration_type=1, expiration_value=1573455822 ) @@ -25,10 +27,11 @@ def test_htlc_lock_transation_amount_zero(): with pytest.raises(ValueError): """Test error handling in constructor for non-integer amount """ + secret_hash = _generate_secret_hash() HtlcLock( recipient_id='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', amount=0, - secret_hash='0f128d401958b1b30ad0d10406f47f9489321017b4614e6cb993fc63913c5454', + secret_hash=secret_hash, expiration_type=1, expiration_value=1573455822 ) @@ -38,10 +41,11 @@ def test_htlc_lock_transation_amount_negative(): with pytest.raises(ValueError): """Test error handling in constructor for non-integer amount """ + secret_hash = _generate_secret_hash() HtlcLock( recipient_id='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', amount=-5, - secret_hash='0f128d401958b1b30ad0d10406f47f9489321017b4614e6cb993fc63913c5454', + secret_hash=secret_hash, expiration_type=1, expiration_value=1573455822 ) @@ -50,10 +54,11 @@ def test_htlc_lock_transation_amount_negative(): def test_htlc_lock_transaction(): """Test if timelock transaction gets built """ + secret_hash = _generate_secret_hash() transaction = HtlcLock( recipient_id='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', amount=200000000, - secret_hash='0f128d401958b1b30ad0d10406f47f9489321017b4614e6cb993fc63913c5454', + secret_hash=secret_hash, expiration_type=1, expiration_value=1573455822 ) @@ -71,7 +76,7 @@ def test_htlc_lock_transaction(): assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 10000000 - assert transaction_dict['asset']['lock']['secretHash'] == '0f128d401958b1b30ad0d10406f47f9489321017b4614e6cb993fc63913c5454' + assert transaction_dict['asset']['lock']['secretHash'] == secret_hash assert transaction_dict['asset']['lock']['expiration']['type'] == 1 assert transaction_dict['asset']['lock']['expiration']['value'] == 1573455822 @@ -81,10 +86,11 @@ def test_htlc_lock_transaction(): def test_htlc_lock_transaction_custom_fee(): """Test if timelock transaction gets built with a custom fee """ + secret_hash = _generate_secret_hash() transaction = HtlcLock( recipient_id='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', amount=200000000, - secret_hash='0f128d401958b1b30ad0d10406f47f9489321017b4614e6cb993fc63913c5454', + secret_hash=secret_hash, expiration_type=1, expiration_value=1573455822, fee=5 @@ -103,8 +109,20 @@ def test_htlc_lock_transaction_custom_fee(): assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 5 - assert transaction_dict['asset']['lock']['secretHash'] == '0f128d401958b1b30ad0d10406f47f9489321017b4614e6cb993fc63913c5454' + assert transaction_dict['asset']['lock']['secretHash'] == secret_hash assert transaction_dict['asset']['lock']['expiration']['type'] == 1 assert transaction_dict['asset']['lock']['expiration']['value'] == 1573455822 transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid + + +def _generate_secret_hash(): + secret = "super secret code that must be unique and entirely random" + + # secret code is a key ingredient used to unlock the htlc lock. To generate an unlock code one + # must "hexify" it: `unlock_secret = hexlify(secret_h.encode()).decode()`` + secret_code = sha256(secret.encode()).hexdigest()[:32] + + # secret_hash is the hashed secret code + secret_hash = sha256(secret_code.encode()).hexdigest() + return secret_hash diff --git a/tests/transactions/deserializers/test_htlc_claim.py b/tests/transactions/deserializers/test_htlc_claim.py index 3929c164..ca0ad97f 100644 --- a/tests/transactions/deserializers/test_htlc_claim.py +++ b/tests/transactions/deserializers/test_htlc_claim.py @@ -2,22 +2,22 @@ def test_htlc_claim_deserializer(): - serialized = 'ff02170100000009000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192000000000000000000943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb46d792073656372657420746861742073686f756c642062652033326279746573381188e6a3c0da8823ab37cf7562724b3920f4fc8a40cb259ae297bd7237b511cbfdbcb46b7afa319ad1c2d8cc3d8cdc33a437c8b17867777b891d03c036dfb9' # noqa + serialized = 'ff021e0100000009000100000000000000037fde73baaa48eb75c013fe9ff52a74a096d48b9978351bdcb5b72331ca37487c000000000000000000943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb46434323233626639336532303235303561366135303134323161383864396661dcd867411d20c7aa891e44cd92e916ea1d1e64ef1518dfcdfa227e4415d846a66c60718dc9d4bfc354afa69c2f8fa6e68f57e6eaf53c51b7a209ead5702ffd71' # noqa deserializer = Deserializer(serialized) actual = deserializer.deserialize() assert actual.version == 2 - assert actual.network == 23 + assert actual.network == 30 assert actual.typeGroup == 1 assert actual.type == 9 assert actual.nonce == 1 - assert actual.senderPublicKey == '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' + assert actual.senderPublicKey == '037fde73baaa48eb75c013fe9ff52a74a096d48b9978351bdcb5b72331ca37487c' assert actual.fee == 0 - assert actual.signature == '381188e6a3c0da8823ab37cf7562724b3920f4fc8a40cb259ae297bd7237b511cbfdbcb46b7afa319ad1c2d8cc3d8cdc33a437c8b17867777b891d03c036dfb9' + assert actual.signature == 'dcd867411d20c7aa891e44cd92e916ea1d1e64ef1518dfcdfa227e4415d846a66c60718dc9d4bfc354afa69c2f8fa6e68f57e6eaf53c51b7a209ead5702ffd71' assert actual.amount == 0 - assert actual.id == '846c5ee8a328376416735da43056d154d41e264564def42fb28b373c0d895c46' + assert actual.id == 'aad3fdb321e2543af1dd9d2d4d155473a8b49cacbc116fa1f3f1b95154b336d5' assert actual.asset['claim']['lockTransactionId'] == '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' # noqa - assert actual.asset['claim']['unlockSecret'] == 'my secret that should be 32bytes' # noqa + assert actual.asset['claim']['unlockSecret'] == '6434323233626639336532303235303561366135303134323161383864396661' # noqa actual.verify_schnorr() From f5eb570d8d792263b4d40be1d675cb34ef8d3458 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Wed, 10 Jul 2024 14:00:13 +0100 Subject: [PATCH 45/68] chore: initial requirements.txt (#125) --- .github/workflows/test.yml | 2 +- requirements.txt | 36 ++++++++++++++++++++++++++++++++++ tests/identity/test_address.py | 5 ++++- 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 requirements.txt diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ffd42f4d..0e601c4f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v1 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..297e5142 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,36 @@ +arkecosystem-crypto==2.0.0 +asn1crypto==1.5.1 +base58==2.1.1 +beautifulsoup4==4.11.1 +binary-helpers==0.0.4 +certifi==2021.10.8 +cffi==1.15.0 +charset-normalizer==2.0.12 +coincurve==17.0.0 +coverage==6.3.2 +exceptiongroup==1.0.0rc3 +flake8==4.0.1 +flake8-import-order==0.18.1 +flake8-print==4.0.0 +flake8-quotes==3.3.1 +idna==3.3 +iniconfig==1.1.1 +markdown-it-py==2.1.0 +mccabe==0.6.1 +mdurl==0.1.1 +packaging==21.3 +pluggy==1.0.0 +pycodestyle==2.8.0 +pycparser==2.21 +pyflakes==2.4.0 +Pygments==2.11.2 +pytest==7.1.1 +pytest-cov==3.0.0 +PyYAML==6.0.0 +requests==2.27.1 +rich==12.2.0 +soupsieve==2.3.2.post1 +toml==0.10.2 +tomli==2.0.1 +tqdm==4.64.0 +urllib3==1.26.9 diff --git a/tests/identity/test_address.py b/tests/identity/test_address.py index 512a9f2b..fc19a67e 100644 --- a/tests/identity/test_address.py +++ b/tests/identity/test_address.py @@ -1,19 +1,22 @@ +import pytest from crypto.identity.address import ( address_from_passphrase, address_from_private_key, address_from_public_key, validate_address ) - def test_address_from_public_key(identity): + pytest.skip(reason="ripemd160 is a legacy hash function") address = address_from_public_key(identity['data']['public_key']) assert address == identity['data']['address'] def test_address_from_private_key(identity): + pytest.skip(reason="ripemd160 is a legacy hash function") address = address_from_private_key(identity['data']['private_key']) assert address == identity['data']['address'] def test_address_from_passphrase(identity): + pytest.skip(reason="ripemd160 is a legacy hash function") address = address_from_passphrase(identity['passphrase']) assert address == identity['data']['address'] From dd22fcbae18fb279d9b51cd1637ed2677e548476 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Wed, 10 Jul 2024 14:32:47 +0100 Subject: [PATCH 46/68] chore: update minor dependencies (#126) --- requirements.txt | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/requirements.txt b/requirements.txt index 297e5142..bd9179d8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,36 +1,36 @@ arkecosystem-crypto==2.0.0 asn1crypto==1.5.1 base58==2.1.1 -beautifulsoup4==4.11.1 +beautifulsoup4==4.12.3 binary-helpers==0.0.4 certifi==2021.10.8 -cffi==1.15.0 +cffi==1.16.0 charset-normalizer==2.0.12 coincurve==17.0.0 coverage==6.3.2 -exceptiongroup==1.0.0rc3 +exceptiongroup==1.2.1 flake8==4.0.1 -flake8-import-order==0.18.1 +flake8-import-order==0.18.2 flake8-print==4.0.0 -flake8-quotes==3.3.1 -idna==3.3 +flake8-quotes==3.4.0 +idna==3.7 iniconfig==1.1.1 markdown-it-py==2.1.0 -mccabe==0.6.1 -mdurl==0.1.1 +mccabe==0.7.0 +mdurl==0.1.2 packaging==21.3 -pluggy==1.0.0 -pycodestyle==2.8.0 -pycparser==2.21 +pluggy==1.5.0 +pycodestyle==2.12.0 +pycparser==2.22 pyflakes==2.4.0 -Pygments==2.11.2 +Pygments==2.18.0 pytest==7.1.1 pytest-cov==3.0.0 -PyYAML==6.0.0 -requests==2.27.1 +PyYAML==6.0.1 +requests==2.32.3 rich==12.2.0 -soupsieve==2.3.2.post1 +soupsieve==2.5 toml==0.10.2 tomli==2.0.1 -tqdm==4.64.0 +tqdm==4.66.4 urllib3==1.26.9 From d642809446c21e8a5278b76d55e0881d18df6361 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Wed, 10 Jul 2024 14:51:56 +0100 Subject: [PATCH 47/68] chore: update major dependencies (#127) --- requirements.txt | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/requirements.txt b/requirements.txt index bd9179d8..11a8cc33 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,34 +3,34 @@ asn1crypto==1.5.1 base58==2.1.1 beautifulsoup4==4.12.3 binary-helpers==0.0.4 -certifi==2021.10.8 +certifi==2024.7.4 cffi==1.16.0 -charset-normalizer==2.0.12 -coincurve==17.0.0 -coverage==6.3.2 +charset-normalizer==3.3.2 +coincurve==20.0.0 +coverage==7.5.4 exceptiongroup==1.2.1 -flake8==4.0.1 +flake8==7.1.0 flake8-import-order==0.18.2 -flake8-print==4.0.0 +flake8-print==5.0.0 flake8-quotes==3.4.0 idna==3.7 -iniconfig==1.1.1 -markdown-it-py==2.1.0 +iniconfig==2.0.0 +markdown-it-py==3.0.0 mccabe==0.7.0 mdurl==0.1.2 -packaging==21.3 +packaging==24.1 pluggy==1.5.0 pycodestyle==2.12.0 pycparser==2.22 -pyflakes==2.4.0 +pyflakes==3.2.0 Pygments==2.18.0 -pytest==7.1.1 -pytest-cov==3.0.0 +pytest==8.2.2 +pytest-cov==5.0.0 PyYAML==6.0.1 requests==2.32.3 -rich==12.2.0 +rich==13.7.1 soupsieve==2.5 toml==0.10.2 tomli==2.0.1 tqdm==4.66.4 -urllib3==1.26.9 +urllib3==2.2.2 From 969df9962b8b6948722a2efacd5a2a588e43d2b1 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Wed, 17 Jul 2024 17:36:19 +0100 Subject: [PATCH 48/68] fix: ripemd160 usage (#128) --- .gitignore | 3 ++- crypto/identity/address.py | 5 +++-- requirements.txt | 6 ++++++ setup.py | 3 ++- tests/identity/test_address.py | 3 --- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 5176d6e9..5a0d6043 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ dist/ .vscode .vs venv -venv/ \ No newline at end of file +venv/ +.venv/ diff --git a/crypto/identity/address.py b/crypto/identity/address.py index b87a8a19..9805c06d 100644 --- a/crypto/identity/address.py +++ b/crypto/identity/address.py @@ -8,6 +8,7 @@ from crypto.configuration.network import get_network from crypto.identity.private_key import PrivateKey +from Cryptodome.Hash import RIPEMD160 def address_from_public_key(public_key, network_version=None): """Get an address from a public key @@ -23,7 +24,7 @@ def address_from_public_key(public_key, network_version=None): network = get_network() network_version = network['version'] - ripemd160 = hashlib.new('ripemd160', unhexlify(public_key.encode())) + ripemd160 = RIPEMD160.new(data=unhexlify(public_key.encode())) seed = write_bit8(network_version) + ripemd160.digest() return b58encode_check(seed).decode() @@ -43,7 +44,7 @@ def address_from_private_key(private_key, network_version=None): network_version = network['version'] private_key = PrivateKey.from_hex(private_key) - ripemd160 = hashlib.new('ripemd160', unhexlify(private_key.public_key)) + ripemd160 = RIPEMD160.new(data=unhexlify(private_key.public_key)) seed = write_bit8(network_version) + ripemd160.digest() return b58encode_check(seed).decode() diff --git a/requirements.txt b/requirements.txt index 11a8cc33..3a79d918 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ arkecosystem-crypto==2.0.0 asn1crypto==1.5.1 +attrs==23.2.0 base58==2.1.1 beautifulsoup4==4.12.3 binary-helpers==0.0.4 @@ -7,6 +8,7 @@ certifi==2024.7.4 cffi==1.16.0 charset-normalizer==3.3.2 coincurve==20.0.0 +commonmark==0.9.1 coverage==7.5.4 exceptiongroup==1.2.1 flake8==7.1.0 @@ -20,15 +22,19 @@ mccabe==0.7.0 mdurl==0.1.2 packaging==24.1 pluggy==1.5.0 +py==1.11.0 pycodestyle==2.12.0 pycparser==2.22 +pycryptodomex==3.20.0 pyflakes==3.2.0 Pygments==2.18.0 +pyparsing==3.1.2 pytest==8.2.2 pytest-cov==5.0.0 PyYAML==6.0.1 requests==2.32.3 rich==13.7.1 +six==1.16.0 soupsieve==2.5 toml==0.10.2 tomli==2.0.1 diff --git a/setup.py b/setup.py index 0a5c77db..1305888c 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,8 @@ requires = [ 'base58', 'binary-helpers', - 'coincurve' + 'coincurve', + 'pycryptodomex', ] tests_require = [ diff --git a/tests/identity/test_address.py b/tests/identity/test_address.py index fc19a67e..f29814cd 100644 --- a/tests/identity/test_address.py +++ b/tests/identity/test_address.py @@ -4,19 +4,16 @@ ) def test_address_from_public_key(identity): - pytest.skip(reason="ripemd160 is a legacy hash function") address = address_from_public_key(identity['data']['public_key']) assert address == identity['data']['address'] def test_address_from_private_key(identity): - pytest.skip(reason="ripemd160 is a legacy hash function") address = address_from_private_key(identity['data']['private_key']) assert address == identity['data']['address'] def test_address_from_passphrase(identity): - pytest.skip(reason="ripemd160 is a legacy hash function") address = address_from_passphrase(identity['passphrase']) assert address == identity['data']['address'] From fb8e8d50e03ee43b84b22c3a7458eaaa29c79418 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Fri, 19 Jul 2024 14:02:27 +0100 Subject: [PATCH 49/68] feat: keccak addresses (#130) --- crypto/identity/address.py | 89 ++++++++++++++++------------------ tests/identity/conftest.py | 3 +- tests/identity/test_address.py | 6 +-- 3 files changed, 45 insertions(+), 53 deletions(-) diff --git a/crypto/identity/address.py b/crypto/identity/address.py index 9805c06d..6b7a11a0 100644 --- a/crypto/identity/address.py +++ b/crypto/identity/address.py @@ -1,55 +1,73 @@ import hashlib from binascii import unhexlify -from base58 import b58decode_check, b58encode_check +from crypto.identity.private_key import PrivateKey -from binary.unsigned_integer.writer import write_bit8 +from Cryptodome.Hash import RIPEMD160, keccak +from coincurve import PrivateKey, PublicKey -from crypto.configuration.network import get_network -from crypto.identity.private_key import PrivateKey +def get_checksum_address(address): + """Get checksum address + + Args: + address (str): address to get checksum + + Returns: + str: checksum address + """ + address = address.lower() -from Cryptodome.Hash import RIPEMD160 + chars = list(address[2:]) -def address_from_public_key(public_key, network_version=None): + expanded = bytearray(40) + for i in range(40): + expanded[i] = ord(chars[i]) + + hashed = keccak.new(data=bytes(expanded), digest_bits=256).digest() + + for i in range(0, 40, 2): + if (hashed[i >> 1] >> 4) >= 8: + chars[i] = chars[i].upper() + if (hashed[i >> 1] & 0x0F) >= 8: + chars[i + 1] = chars[i + 1].upper() + + return "0x" + ''.join(chars) + +def address_from_public_key(public_key): """Get an address from a public key Args: public_key (str): - network_version (int, optional): Returns: - bytes: + str: address """ - if not network_version: - network = get_network() - network_version = network['version'] - ripemd160 = RIPEMD160.new(data=unhexlify(public_key.encode())) - seed = write_bit8(network_version) + ripemd160.digest() - return b58encode_check(seed).decode() + public_key = PublicKey(bytes.fromhex(public_key)).format(compressed=False)[1:] + + keccak_hash = keccak.new( + data=bytearray.fromhex(public_key.hex()), + digest_bits=256, + ) + return get_checksum_address(unhexlify(keccak_hash.hexdigest()[22:]).hex()) -def address_from_private_key(private_key, network_version=None): + +def address_from_private_key(private_key): """Get an address from private key Args: private_key (string): - network_version (int, optional): Returns: TYPE: Description """ - if not network_version: - network = get_network() - network_version = network['version'] - private_key = PrivateKey.from_hex(private_key) - ripemd160 = RIPEMD160.new(data=unhexlify(private_key.public_key)) - seed = write_bit8(network_version) + ripemd160.digest() - return b58encode_check(seed).decode() + + return address_from_public_key(private_key.public_key.format(compressed=False).hex()) -def address_from_passphrase(passphrase, network_version=None): +def address_from_passphrase(passphrase): """Get an address from passphrase Args: @@ -59,27 +77,6 @@ def address_from_passphrase(passphrase, network_version=None): Returns: string: address """ - if not network_version: - network = get_network() - network_version = network['version'] - private_key = hashlib.sha256(passphrase.encode()).hexdigest() - address = address_from_private_key(private_key, network_version) + address = address_from_private_key(private_key) return address - - -def validate_address(address, network_version=None): - """Validate a given address - - Args: - address (str): address you wish to validate - network_version (None, optional): integer, version of the network - - Returns: - bool: - """ - if not network_version: - network = get_network() - network_version = network['version'] - - return network_version == b58decode_check(address)[0] diff --git a/tests/identity/conftest.py b/tests/identity/conftest.py index fae17f10..90e09c52 100644 --- a/tests/identity/conftest.py +++ b/tests/identity/conftest.py @@ -1,6 +1,5 @@ import pytest - @pytest.fixture def identity(): """Identity fixture @@ -9,7 +8,7 @@ def identity(): 'data': { 'private_key': 'd8839c2432bfd0a67ef10a804ba991eabba19f154a3d707917681d45822a5712', 'public_key': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', - 'address': 'D61mfSggzbvQgTUe6JhYKH2doHaqJ3Dyib', + 'address': '0xb0FF9213f7226bBB72b84dE16af86e56f1f38B01', 'wif': 'SGq4xLgZKCGxs7bjmwnBrWcT4C1ADFEermj846KC97FSv1WFD1dA' }, 'passphrase': 'this is a top secret passphrase' diff --git a/tests/identity/test_address.py b/tests/identity/test_address.py index f29814cd..95070d54 100644 --- a/tests/identity/test_address.py +++ b/tests/identity/test_address.py @@ -1,6 +1,6 @@ import pytest from crypto.identity.address import ( - address_from_passphrase, address_from_private_key, address_from_public_key, validate_address + address_from_passphrase, address_from_private_key, address_from_public_key ) def test_address_from_public_key(identity): @@ -16,7 +16,3 @@ def test_address_from_private_key(identity): def test_address_from_passphrase(identity): address = address_from_passphrase(identity['passphrase']) assert address == identity['data']['address'] - - -def test_validate_address(identity): - assert validate_address(identity['data']['address']) is True From 5ffb58e9484cc7cc4e20420a921a6f32e2eb4cc5 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Mon, 29 Jul 2024 09:01:38 +0100 Subject: [PATCH 50/68] refactor: update schnorr implementation (#129) --- crypto/identity/address.py | 2 +- crypto/identity/private_key.py | 11 +- crypto/identity/public_key.py | 2 - crypto/transactions/builder/base.py | 25 +- .../builder/delegate_registration.py | 8 +- crypto/transactions/builder/vote.py | 12 +- crypto/transactions/deserializer.py | 20 +- .../deserializers/delegate_registration.py | 14 +- .../deserializers/multi_payment.py | 14 +- crypto/transactions/deserializers/transfer.py | 12 +- crypto/transactions/deserializers/vote.py | 26 +- crypto/transactions/serializer.py | 2 +- .../serializers/delegate_registration.py | 7 +- .../transactions/serializers/multi_payment.py | 12 +- crypto/transactions/serializers/transfer.py | 9 +- crypto/transactions/serializers/vote.py | 12 +- crypto/transactions/signature.py | 19 ++ crypto/transactions/transaction.py | 11 +- crypto/utils/message.py | 63 ++-- requirements.txt | 2 + setup.py | 3 +- tests/conftest.py | 282 +++++------------- tests/identity/conftest.py | 10 +- tests/identity/test_public_key.py | 4 +- tests/transactions/builder/conftest.py | 7 + .../builder/test_delegate_registration.py | 24 +- .../builder/test_delegate_resignation.py | 12 +- tests/transactions/builder/test_htlc_claim.py | 91 ------ tests/transactions/builder/test_htlc_lock.py | 128 -------- .../transactions/builder/test_htlc_refund.py | 53 ---- tests/transactions/builder/test_ipfs.py | 52 ---- .../builder/test_multi_payment.py | 30 +- .../test_multi_signature_registration.py | 10 +- .../test_second_signature_registration.py | 44 --- tests/transactions/builder/test_transfer.py | 50 ++-- tests/transactions/builder/test_vote.py | 74 ++++- .../test_delegate_registration.py | 26 +- .../test_delegate_resignation.py | 24 +- .../deserializers/test_htlc_claim.py | 23 -- .../deserializers/test_htlc_lock.py | 25 -- .../deserializers/test_htlc_refund.py | 22 -- tests/transactions/deserializers/test_ipfs.py | 22 -- .../deserializers/test_multi_payment.py | 22 +- .../test_multi_signature_registration.py | 28 +- .../test_second_signature_registration.py | 22 -- .../deserializers/test_transfer.py | 22 +- tests/transactions/deserializers/test_vote.py | 27 +- .../serializers/test_htlc_claim.py | 6 - .../serializers/test_htlc_lock.py | 6 - .../serializers/test_htlc_refund.py | 6 - tests/transactions/serializers/test_ipfs.py | 6 - .../test_second_signature_registration.py | 6 - tests/transactions/test_signature.py | 76 +++++ tests/utils/test_message.py | 48 ++- 54 files changed, 572 insertions(+), 1002 deletions(-) create mode 100644 crypto/transactions/signature.py create mode 100644 tests/transactions/builder/conftest.py delete mode 100644 tests/transactions/builder/test_htlc_claim.py delete mode 100644 tests/transactions/builder/test_htlc_lock.py delete mode 100644 tests/transactions/builder/test_htlc_refund.py delete mode 100644 tests/transactions/builder/test_ipfs.py delete mode 100644 tests/transactions/builder/test_second_signature_registration.py delete mode 100644 tests/transactions/deserializers/test_htlc_claim.py delete mode 100644 tests/transactions/deserializers/test_htlc_lock.py delete mode 100644 tests/transactions/deserializers/test_htlc_refund.py delete mode 100644 tests/transactions/deserializers/test_ipfs.py delete mode 100644 tests/transactions/deserializers/test_second_signature_registration.py delete mode 100644 tests/transactions/serializers/test_htlc_claim.py delete mode 100644 tests/transactions/serializers/test_htlc_lock.py delete mode 100644 tests/transactions/serializers/test_htlc_refund.py delete mode 100644 tests/transactions/serializers/test_ipfs.py delete mode 100644 tests/transactions/serializers/test_second_signature_registration.py create mode 100644 tests/transactions/test_signature.py diff --git a/crypto/identity/address.py b/crypto/identity/address.py index 6b7a11a0..514eb7d2 100644 --- a/crypto/identity/address.py +++ b/crypto/identity/address.py @@ -6,7 +6,7 @@ from Cryptodome.Hash import RIPEMD160, keccak from coincurve import PrivateKey, PublicKey -def get_checksum_address(address): +def get_checksum_address(address: bytes) -> str: """Get checksum address Args: diff --git a/crypto/identity/private_key.py b/crypto/identity/private_key.py index 72a5e62d..3183e90b 100644 --- a/crypto/identity/private_key.py +++ b/crypto/identity/private_key.py @@ -5,7 +5,6 @@ class PrivateKey(object): - def __init__(self, private_key): self.private_key = PvtKey.from_hex(private_key) self.public_key = hexlify(self.private_key.public_key.format()).decode() @@ -19,8 +18,14 @@ def sign(self, message): Returns: str: signature of the signed message """ - signature = self.private_key.sign(message) - return hexlify(signature).decode() + from crypto.transactions.signature import Signature + + signature = Signature.sign( + hexlify(message), + self.private_key.to_hex() + ) + + return signature.encode() def to_hex(self): """Returns a private key in hex format diff --git a/crypto/identity/public_key.py b/crypto/identity/public_key.py index 4a7e7c9e..f2313580 100644 --- a/crypto/identity/public_key.py +++ b/crypto/identity/public_key.py @@ -4,9 +4,7 @@ from crypto.identity.private_key import PrivateKey - class PublicKey(object): - def __init__(self, public_key): public_key = unhexlify(public_key.encode()) self.public_key = PubKey(public_key) diff --git a/crypto/transactions/builder/base.py b/crypto/transactions/builder/base.py index 7d4f5b51..efa68ce6 100644 --- a/crypto/transactions/builder/base.py +++ b/crypto/transactions/builder/base.py @@ -1,14 +1,11 @@ -import hashlib from binascii import hexlify, unhexlify from crypto.configuration.fee import get_fee from crypto.constants import HTLC_LOCK_EXPIRATION_TYPE, TRANSACTION_TYPE_GROUP from crypto.identity.private_key import PrivateKey from crypto.identity.public_key import PublicKey -from crypto.schnorr import schnorr +from crypto.transactions.signature import Signature from crypto.transactions.transaction import Transaction -from crypto.utils.message import Message - class BaseTransactionBuilder(object): @@ -19,7 +16,8 @@ def __init__(self): self.transaction.nonce = getattr(self, 'nonce', None) self.transaction.typeGroup = getattr(self, 'typeGroup', 1) self.transaction.signatures = getattr(self, 'signatures', None) - self.transaction.version = getattr(self, 'version', 2) + self.transaction.version = getattr(self, 'version', 1) + self.transaction.expiration = getattr(self, 'expiration', 0) if self.transaction.type != 0: self.transaction.amount = getattr(self, 'amount', 0) @@ -29,16 +27,17 @@ def to_dict(self): def to_json(self): return self.transaction.to_json() - def schnorr_sign(self, passphrase): + def sign(self, passphrase): """Sign the transaction using the given passphrase Args: passphrase (str): passphrase associated with the account sending this transaction """ self.transaction.senderPublicKey = PublicKey.from_passphrase(passphrase) - msg = hashlib.sha256(self.transaction.to_bytes(False, True, False)).digest() + + msg = self.transaction.to_bytes(False, True, False) secret = unhexlify(PrivateKey.from_passphrase(passphrase).to_hex()) - self.transaction.signature = hexlify(schnorr.bcrypto410_sign(msg, secret)) + self.transaction.signature = Signature.sign(msg, secret) self.transaction.id = self.transaction.get_id() def second_sign(self, passphrase): @@ -47,9 +46,9 @@ def second_sign(self, passphrase): Args: passphrase (str): 2nd passphrase associated with the account sending this transaction """ - msg = hashlib.sha256(self.transaction.to_bytes(False, True, False)).digest() + msg = self.transaction.to_bytes(False, True, False) secret = unhexlify(PrivateKey.from_passphrase(passphrase).to_hex()) - self.transaction.signSignature = hexlify(schnorr.bcrypto410_sign(msg, secret)) + self.transaction.signSignature = Signature.sign(msg, secret) self.transaction.id = self.transaction.get_id() def multi_sign(self, passphrase, index): @@ -58,16 +57,16 @@ def multi_sign(self, passphrase, index): index = len(self.transaction.signatures) if index == -1 else index - msg = hashlib.sha256(self.transaction.to_bytes()).digest() + msg = self.transaction.to_bytes() secret = unhexlify(PrivateKey.from_passphrase(passphrase).to_hex()) - signature = hexlify(schnorr.bcrypto410_sign(msg, secret)) + signature = hexlify(Signature.sign(msg, secret).encode()) index_formatted = hex(index).replace('x', '') self.transaction.signatures.append(index_formatted + signature.decode()) def schnorr_verify(self): return self.transaction.verify_schnorr() - + def schnorr_verify_second(self, secondPublicKey): return self.transaction.verify_schnorr_secondsig(secondPublicKey) diff --git a/crypto/transactions/builder/delegate_registration.py b/crypto/transactions/builder/delegate_registration.py index 04a0a0f7..4dd17dbb 100644 --- a/crypto/transactions/builder/delegate_registration.py +++ b/crypto/transactions/builder/delegate_registration.py @@ -7,21 +7,21 @@ class DelegateRegistration(BaseTransactionBuilder): transaction_type = TRANSACTION_DELEGATE_REGISTRATION - def __init__(self, username, fee=None): + def __init__(self, public_key, fee=None): """Create a delegate registration transaction Args: - username (str): username of a delegate you want to register + public_key (str): BLS public key of a validator you want to register fee (int, optional): fee used for the transaction (default is already set) """ super().__init__() - self.transaction.asset['delegate'] = {'username': username} + self.transaction.asset['validatorPublicKey'] = public_key if fee: self.transaction.fee = fee def sign(self, passphrase): public_key = PublicKey.from_passphrase(passphrase) - self.transaction.asset['delegate']['publicKey'] = public_key + #self.transaction.asset['delegate']['publicKey'] = public_key super().sign(passphrase) diff --git a/crypto/transactions/builder/vote.py b/crypto/transactions/builder/vote.py index baf9be9f..8086d3e5 100644 --- a/crypto/transactions/builder/vote.py +++ b/crypto/transactions/builder/vote.py @@ -1,22 +1,24 @@ from crypto.constants import TRANSACTION_VOTE from crypto.identity.address import address_from_passphrase from crypto.transactions.builder.base import BaseTransactionBuilder +from typing import Optional class Vote(BaseTransactionBuilder): - transaction_type = TRANSACTION_VOTE - def __init__(self, vote, fee=None): - """Create a second signature registration transaction + def __init__(self, votes: Optional[list[str]] = None, unvotes: Optional[list[str]] = None, fee: Optional[int] = None): + """Create a vote transaction Args: - vote (str): address of a delegate you want to vote + vote (str, optional): address of a delegate you want to vote + unvote (str, optional): address of a delegate you want to unvote fee (int, optional): fee used for the transaction (default is already set) """ super().__init__() - self.transaction.asset['votes'] = [vote] + self.transaction.asset['votes'] = votes if votes else [] + self.transaction.asset['unvotes'] = unvotes if unvotes else [] if fee: self.transaction.fee = fee diff --git a/crypto/transactions/deserializer.py b/crypto/transactions/deserializer.py index c0d9f73f..fa28aae9 100644 --- a/crypto/transactions/deserializer.py +++ b/crypto/transactions/deserializer.py @@ -49,10 +49,7 @@ def deserialize(self): handled_transaction = self._handle_transaction_type(asset_offset, transaction) transaction.amount = handled_transaction.amount transaction.version = handled_transaction.version - if transaction.version == 2: - transaction = self._handle_version_two(transaction) - else: - raise Exception('should this ever happen?') + transaction.id = transaction.get_id() return transaction @@ -79,18 +76,5 @@ def _handle_transaction_type(self, asset_offset, transaction): # this attribute is actually a specific deserializer that we want to use deserializer = attribute break - return deserializer(self.serialized, asset_offset, transaction).deserialize() - - def _handle_version_two(self, transaction): - """Handle deserialization for version two - - Args: - transaction (object): Transaction resource object - - Returns: - object: Transaction resource object of currently deserialized data - """ - transaction.id = sha256(unhexlify(transaction.serialize(False, True, False))).hexdigest() - - return transaction + return deserializer(self.serialized, asset_offset, transaction).deserialize() diff --git a/crypto/transactions/deserializers/delegate_registration.py b/crypto/transactions/deserializers/delegate_registration.py index 763641be..d3594716 100644 --- a/crypto/transactions/deserializers/delegate_registration.py +++ b/crypto/transactions/deserializers/delegate_registration.py @@ -1,6 +1,4 @@ -from binascii import hexlify, unhexlify - -from binary.unsigned_integer.reader import read_bit8 +from binascii import hexlify from crypto.transactions.deserializers.base import BaseDeserializer @@ -10,17 +8,13 @@ class DelegateRegistrationDeserializer(BaseDeserializer): def deserialize(self): starting_position = int(self.asset_offset / 2) - username_length = read_bit8(self.serialized, starting_position) & 0xff - start_index = self.asset_offset + 2 - end_index = start_index + (username_length * 2) - username = hexlify(self.serialized)[start_index:end_index] - username = unhexlify(username) + validator_public_key = self.serialized[starting_position:starting_position + 48] - self.transaction.asset['delegate'] = {'username': username.decode()} + self.transaction.asset['validatorPublicKey'] = validator_public_key.hex() self.transaction.parse_signatures( hexlify(self.serialized).decode(), - self.asset_offset + (username_length + 1) * 2 + self.asset_offset + (48 * 2) ) return self.transaction diff --git a/crypto/transactions/deserializers/multi_payment.py b/crypto/transactions/deserializers/multi_payment.py index f981f241..e1c63f1e 100644 --- a/crypto/transactions/deserializers/multi_payment.py +++ b/crypto/transactions/deserializers/multi_payment.py @@ -1,9 +1,8 @@ from binascii import hexlify, unhexlify -from base58 import b58encode_check - from binary.unsigned_integer.reader import read_bit16, read_bit64 +from crypto.identity.address import get_checksum_address from crypto.transactions.deserializers.base import BaseDeserializer @@ -18,20 +17,19 @@ def deserialize(self): index = 0 - for payment in range(payment_length): + for _ in range(payment_length): amount = read_bit64(self.serialized, offset=starting_position + 2 + index) recipient_start_index = (starting_position + 10 + index) * 2 - recipientId = hexlify(self.serialized)[recipient_start_index:recipient_start_index + 42] - recipientId = b58encode_check(unhexlify(recipientId)).decode() + recipientId = hexlify(self.serialized)[recipient_start_index:recipient_start_index + 40] - self.transaction.asset['payments'].append({'amount': amount, 'recipientId': recipientId}) + self.transaction.asset['payments'].append({'amount': amount, 'recipientId': get_checksum_address('0x'+ unhexlify(recipientId).hex())}) - index += 21 + 8 + index += 20 + 8 self.transaction.parse_signatures( hexlify(self.serialized).decode(), - self.asset_offset + 4 + (payment_length * (21 + 8)) * 2 + self.asset_offset + 4 + (payment_length * (20 + 8)) * 2 ) return self.transaction diff --git a/crypto/transactions/deserializers/transfer.py b/crypto/transactions/deserializers/transfer.py index e77566fe..cfe98d91 100644 --- a/crypto/transactions/deserializers/transfer.py +++ b/crypto/transactions/deserializers/transfer.py @@ -1,27 +1,25 @@ from binascii import hexlify, unhexlify -from base58 import b58encode_check - from binary.unsigned_integer.reader import read_bit32, read_bit64 +from crypto.identity.address import get_checksum_address from crypto.transactions.deserializers.base import BaseDeserializer - class TransferDeserializer(BaseDeserializer): - def deserialize(self): starting_position = int(self.asset_offset / 2) self.transaction.amount = read_bit64(self.serialized, offset=starting_position) self.transaction.expiration = read_bit32(self.serialized, offset=starting_position + 8) - recipient_start_index = (int(self.asset_offset / 2) + 12) * 2 + recipient_start_index = (int(self.asset_offset / 2) + 11) * 2 recipientId = hexlify(self.serialized)[recipient_start_index:recipient_start_index + 42] - self.transaction.recipientId = b58encode_check(unhexlify(recipientId)).decode() + + self.transaction.recipientId = get_checksum_address(unhexlify(recipientId).hex()) self.transaction.parse_signatures( hexlify(self.serialized).decode(), - self.asset_offset + (8 + 4 + 21) * 2 + self.asset_offset + (8 + 4 + 20) * 2 ) return self.transaction diff --git a/crypto/transactions/deserializers/vote.py b/crypto/transactions/deserializers/vote.py index eb32d919..2c087b6a 100644 --- a/crypto/transactions/deserializers/vote.py +++ b/crypto/transactions/deserializers/vote.py @@ -4,30 +4,36 @@ from crypto.transactions.deserializers.base import BaseDeserializer - class VoteDeserializer(BaseDeserializer): - def deserialize(self): starting_position = int(self.asset_offset / 2) vote_length = read_bit8(self.serialized, starting_position) & 0xff self.transaction.asset['votes'] = [] + self.transaction.asset['unvotes'] = [] + + vote_position = starting_position + 1 for index in range(vote_length): - starting_position = self.asset_offset + 2 + (index * 2 * 34) - vote = hexlify(self.serialized)[starting_position:starting_position + 2 * 34].decode() - if vote[1] == '1': - operator = '+' - else: - operator = '-' - vote = '{}{}'.format(operator, vote[2:]) + vote = self.serialized[vote_position + (index * 33):vote_position + (index * 33) + 33].hex() self.transaction.asset['votes'].append(vote) + unvote_position = vote_position + (vote_length * 33) + + unvote_length = read_bit8(self.serialized, unvote_position) & 0xff + + unvote_position += 1 + + for index in range(unvote_length): + unvote = self.serialized[unvote_position + (index * 33):unvote_position + (index * 33) + 33].hex() + + self.transaction.asset['unvotes'].append(unvote) + self.transaction.parse_signatures( hexlify(self.serialized).decode(), - self.asset_offset + 2 + (vote_length * 34 * 2) + self.asset_offset + 2 + (vote_length * 66) + 2 + (unvote_length * 66) ) return self.transaction diff --git a/crypto/transactions/serializer.py b/crypto/transactions/serializer.py index f95e9c93..20b6e4c5 100644 --- a/crypto/transactions/serializer.py +++ b/crypto/transactions/serializer.py @@ -31,7 +31,7 @@ def serialize(self, skip_signature=True, skip_second_signature=True, skip_multi_ bytes_data += write_bit8(0xff) - bytes_data += write_bit8(self.transaction.get('version') or 0x02) + bytes_data += write_bit8(self.transaction.get('version') or 0x01) bytes_data += write_bit8(self.transaction.get('network') or network_config['version']) bytes_data += write_bit32(self.transaction.get('typeGroup') or 0x01) bytes_data += write_bit16(self.transaction.get('type')) diff --git a/crypto/transactions/serializers/delegate_registration.py b/crypto/transactions/serializers/delegate_registration.py index 72a12b57..7db5c32e 100644 --- a/crypto/transactions/serializers/delegate_registration.py +++ b/crypto/transactions/serializers/delegate_registration.py @@ -3,16 +3,15 @@ from binary.unsigned_integer.writer import write_bit8 from crypto.transactions.serializers.base import BaseSerializer - +from binary.hex.writer import write_high class DelegateRegistrationSerializer(BaseSerializer): """Serializer handling delegate registration data """ def serialize(self): - delegate_bytes = hexlify(self.transaction['asset']['delegate']['username'].encode()) + delegate_bytes = self.transaction['asset']['validatorPublicKey'].encode() - self.bytes_data += write_bit8(len(delegate_bytes) // 2) - self.bytes_data += unhexlify(delegate_bytes) + self.bytes_data += write_high(delegate_bytes) return self.bytes_data diff --git a/crypto/transactions/serializers/multi_payment.py b/crypto/transactions/serializers/multi_payment.py index f8a53f5e..a328b7c4 100644 --- a/crypto/transactions/serializers/multi_payment.py +++ b/crypto/transactions/serializers/multi_payment.py @@ -1,7 +1,3 @@ -from binascii import hexlify - -from base58 import b58decode_check - from binary.hex.writer import write_high from binary.unsigned_integer.writer import write_bit16, write_bit64 @@ -16,8 +12,12 @@ def serialize(self): self.bytes_data += write_bit16(len(self.transaction['asset']['payments'])) for payment in self.transaction['asset']['payments']: + recipient = payment['recipientId'][2:] + + if type(recipient) is str: + recipient = recipient.encode() + self.bytes_data += write_bit64(payment['amount']) - recipientId = hexlify(b58decode_check(payment['recipientId'])) - self.bytes_data += write_high(recipientId) + self.bytes_data += write_high(recipient) return self.bytes_data diff --git a/crypto/transactions/serializers/transfer.py b/crypto/transactions/serializers/transfer.py index c6c66ba8..d7d068c7 100644 --- a/crypto/transactions/serializers/transfer.py +++ b/crypto/transactions/serializers/transfer.py @@ -15,7 +15,12 @@ class TransferSerializer(BaseSerializer): def serialize(self): self.bytes_data += write_bit64(self.transaction['amount']) self.bytes_data += write_bit32(self.transaction.get('expiration', 0)) - recipientId = hexlify(b58decode_check(self.transaction['recipientId'])) - self.bytes_data += write_high(recipientId) + + recipient = self.transaction['recipientId'][2:] + + if type(recipient) is str: + recipient = recipient.encode() + + self.bytes_data += write_high(recipient) return self.bytes_data diff --git a/crypto/transactions/serializers/vote.py b/crypto/transactions/serializers/vote.py index 82574794..4adbe070 100644 --- a/crypto/transactions/serializers/vote.py +++ b/crypto/transactions/serializers/vote.py @@ -11,14 +11,18 @@ class VoteSerializer(BaseSerializer): def serialize(self): vote_bytes = [] + unvote_bytes = [] for vote in self.transaction['asset']['votes']: - if vote.startswith('+'): - vote_bytes.append('01{}'.format(vote[1::])) - else: - vote_bytes.append('00{}'.format(vote[1::])) + vote_bytes.append(vote) + + for unvote in self.transaction['asset']['unvotes']: + unvote_bytes.append(unvote) self.bytes_data += write_bit8(len(self.transaction['asset']['votes'])) self.bytes_data += unhexlify(''.join(vote_bytes)) + self.bytes_data += write_bit8(len(self.transaction['asset']['unvotes'])) + self.bytes_data += unhexlify(''.join(unvote_bytes)) + return self.bytes_data diff --git a/crypto/transactions/signature.py b/crypto/transactions/signature.py new file mode 100644 index 00000000..03ac4ac0 --- /dev/null +++ b/crypto/transactions/signature.py @@ -0,0 +1,19 @@ +from crypto.identity.private_key import PrivateKey +from btclib.ecc import ssa +from typing import Union + +class Signature: + @staticmethod + def verify(signature, message, publicKey: bytes): + # Remove leading byte ('02' / '03') from ECDSA key + if (len(publicKey) == 33): + publicKey = publicKey[1:] + + return ssa.verify(message, publicKey, signature) + + @staticmethod + def sign(message, privateKey: Union[bytes, PrivateKey]): + if isinstance(privateKey, PrivateKey): + privateKey = bytes.fromhex(privateKey.to_hex()) + + return ssa.sign(message, privateKey).serialize(False).hex() diff --git a/crypto/transactions/transaction.py b/crypto/transactions/transaction.py index b3a2a653..81b10a7f 100644 --- a/crypto/transactions/transaction.py +++ b/crypto/transactions/transaction.py @@ -13,6 +13,7 @@ from crypto.schnorr import schnorr from crypto.transactions.deserializer import Deserializer from crypto.transactions.serializer import Serializer +from crypto.transactions.signature import Signature TRANSACTION_ATTRIBUTES = { 'amount': 0, @@ -155,7 +156,7 @@ def verify_schnorr(self): """Verify the transaction. Method will raise an exception if invalid, if it's valid it will returns True """ - is_valid = schnorr.b410_schnorr_verify(self.to_bytes(), self.senderPublicKey, self.signature) + is_valid = Signature.verify(self.signature, self.to_bytes(), self.senderPublicKey) if not is_valid: raise ArkInvalidTransaction('Transaction could not be verified') @@ -166,16 +167,16 @@ def verify_schnorr_secondsig(self, secondPublicKey): """Verify the transaction. Method will raise an exception if invalid, if it's valid it will returns True """ - is_valid = schnorr.b410_schnorr_verify(self.to_bytes(False, True), secondPublicKey, self.signSignature) - + is_valid = Signature.verify(self.signSignature, self.to_bytes(False, True), secondPublicKey) + if not is_valid: raise ArkInvalidTransaction('Transaction could not be verified') - + def verify_schnorr_multisig(self): """Verify the multisignatures transaction. Method will raise an exception if invalid, it will returns True """ - is_valid = schnorr.b410_schnorr_verify(self.to_bytes(True, True, False), self.senderPublicKey, self.signature) + is_valid = Signature.verify(self.signature, self.to_bytes(True, True, False), self.senderPublicKey) if not is_valid: raise ArkInvalidTransaction('Transaction could not be verified') diff --git a/crypto/utils/message.py b/crypto/utils/message.py index fbdbf8ba..7862c667 100644 --- a/crypto/utils/message.py +++ b/crypto/utils/message.py @@ -2,19 +2,32 @@ from binascii import unhexlify from crypto.identity.private_key import PrivateKey -from crypto.identity.public_key import PublicKey - +from crypto.transactions.signature import Signature +from typing import Union class Message(object): - def __init__(self, **kwargs): - for k in kwargs.keys(): - if k in ['message', 'signature', 'publickey', 'publicKey']: - self.__setattr__(k, kwargs[k]) - else: - raise TypeError('Invalid keyword argument %s' % k) + public_key: bytes + message: bytes + signature: bytes + + def __init__(self, public_key: bytes, message: bytes, signature: bytes): + if type(public_key) is bytes: + self.public_key = public_key + else: + self.public_key = public_key.encode() + + if type(message) is bytes: + self.message = message + else: + self.message = message.encode() + + if type(signature) is bytes: + self.signature = signature + else: + self.signature = signature.encode() @classmethod - def sign(cls, message, passphrase): + def sign(cls, message: Union[bytes, str], passphrase: bytes): """Signs a message Args: @@ -24,11 +37,20 @@ def sign(cls, message, passphrase): Returns: Message: returns a message object """ - message_bytes = message if isinstance(message, bytes) else message.encode() - passphrase = passphrase.decode() if isinstance(passphrase, bytes) else passphrase + + if type(message) is str: + message = message.encode() + + private_key = PrivateKey.from_passphrase(passphrase) - signature = private_key.sign(message_bytes) - return cls(message=message, signature=signature, publicKey=private_key.public_key) + public_key = private_key.public_key + signature = Signature.sign(message, private_key) + + return cls( + message=message, + signature=signature, + public_key=public_key, + ) def verify(self): """Verify the Message object @@ -36,11 +58,11 @@ def verify(self): Returns: bool: returns a boolean - true if verified, false if not """ - message = self.message if isinstance(self.message, bytes) else self.message.encode() - key = PublicKey.from_hex(self.publickey) if hasattr(self, 'publickey') else PublicKey.from_hex(self.publicKey) + + public_key = unhexlify(self.public_key) signature = unhexlify(self.signature) - is_verified = key.public_key.verify(signature, message) - return is_verified + + return Signature.verify(signature, self.message, public_key) def to_dict(self): """Return a dictionary of the message @@ -49,9 +71,9 @@ def to_dict(self): dict: dictionary consiting of public_key, signature and message """ data = { - ('publicKey' if hasattr(self, 'publicKey') else 'publickey'): (self.publicKey if hasattr(self, 'publicKey') else self.publickey), - 'signature': self.signature, - 'message': self.message, + 'public_key': self.public_key.decode(), + 'signature': self.signature.decode(), + 'message': self.message.decode(), } return data @@ -62,4 +84,5 @@ def to_json(self): str: json string consisting of public_key, signature and message """ data = self.to_dict() + return json.dumps(data) diff --git a/requirements.txt b/requirements.txt index 3a79d918..bbfed554 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,8 @@ attrs==23.2.0 base58==2.1.1 beautifulsoup4==4.12.3 binary-helpers==0.0.4 +btclib==2023.7.12 +btclib_libsecp256k1==0.4.0 certifi==2024.7.4 cffi==1.16.0 charset-normalizer==3.3.2 diff --git a/setup.py b/setup.py index 1305888c..e68046aa 100644 --- a/setup.py +++ b/setup.py @@ -8,6 +8,7 @@ 'binary-helpers', 'coincurve', 'pycryptodomex', + 'btclib', ] tests_require = [ @@ -16,7 +17,7 @@ 'flake8-print>=3.1.0', 'flake8-quotes>=1.0.0', 'pytest>=3.6.1', - 'pytest-cov>=2.5.1' + 'pytest-cov>=2.5.1', ] extras_require = { diff --git a/tests/conftest.py b/tests/conftest.py index 18de0c83..e55c3429 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,69 +6,42 @@ def transaction_type_0(): """Transaction of type "transfer" """ data = { - 'version': 2, - 'network': 23, - 'typeGroup': 1, - 'type': 0, - 'nonce': 1, - 'senderPublicKey': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', - 'fee': 10000000, - 'amount': 200000000, - 'expiration': 0, - 'recipientId': 'AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', - 'signature': '136c29d921b58ae3194020b82e9808f9cd54f7178cb34678f570f28226b8e56ba0ad318297a3bacbb37ab22ddaa5dbf1901cda3ec2d2bca5ce98d6407839ab9b', # noqa - 'id': '129517023bd895b682bbb38b1d1f99e9222bd487899c843da22d8572b0fb52a8', - 'serialized': 'ff02170100000000000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19280969800000000000000c2eb0b0000000000000000170995750207ecaf0ccf251c1265b92ad84f553662136c29d921b58ae3194020b82e9808f9cd54f7178cb34678f570f28226b8e56ba0ad318297a3bacbb37ab22ddaa5dbf1901cda3ec2d2bca5ce98d6407839ab9b' # noqa - } - return data - - -@pytest.fixture -def transaction_type_1(): - """Transaction of type "second signature registration" - """ - data = { - 'version': 2, - 'network': 23, - 'typeGroup': 1, - 'type': 1, - 'nonce': 1, - 'senderPublicKey': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', - 'fee': 500000000, - 'asset': { - 'signature': { - 'publicKey': '03699e966b2525f9088a6941d8d94f7869964a000efe65783d78ac82e1199fe609' - } - }, - 'signature': 'f9a1e2244c8318e8be85482fc02659e5c1775d246d73d5d0699ae4a1d5e3a3e84f9dcf68ee015f943d2a82eb829f35abd7901279761d96f6b43431520e955c67', # noqa - 'amount': 0, - 'id': '173a3230159b45d772b2e0348f42af53913bf3e376397f29b8e0bda290badbe4', - 'serialized': 'ff02170100000001000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed1920065cd1d000000000003699e966b2525f9088a6941d8d94f7869964a000efe65783d78ac82e1199fe609f9a1e2244c8318e8be85482fc02659e5c1775d246d73d5d0699ae4a1d5e3a3e84f9dcf68ee015f943d2a82eb829f35abd7901279761d96f6b43431520e955c67' # noqa + "version": 1, + "network": 30, + "typeGroup": 1, + "type": 0, + "nonce": 5, + "senderPublicKey": '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3', + "fee": 10000000, + "amount": 1, + "expiration": 0, + "recipientId": '0xb693449AdDa7EFc015D87944EAE8b7C37EB1690A', + "signature": '95abbcadcfb4b8991392b1ce819777abd471d837ae8bfbf28f39cad4c7b2e815116622f3ceb099386ec19b781d7b38bdf008e1851a7f848bb88382f893ff85ea', + "id": '0ba2a3bf50747a89e5527235ec9beaab055ceadedfa347e81e95ba97e5166c6b', + "serialized": "ff011e0100000000000500000000000000023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3809698000000000000010000000000000000000000b693449adda7efc015d87944eae8b7c37eb1690a95abbcadcfb4b8991392b1ce819777abd471d837ae8bfbf28f39cad4c7b2e815116622f3ceb099386ec19b781d7b38bdf008e1851a7f848bb88382f893ff85ea", } return data @pytest.fixture def transaction_type_2(): - """Transaction of type "delegate registration" + """Transaction of type "validator registration" """ data = { - 'version': 2, - 'network': 23, + 'version': 1, + 'network': 30, 'typeGroup': 1, 'type': 2, - 'nonce': 1, - 'senderPublicKey': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', + 'nonce': 5, + 'senderPublicKey': '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3', 'fee': 2500000000, 'asset': { - 'delegate': { - 'username': 'boldninja' - } + 'validatorPublicKey': 'a08058db53e2665c84a40f5152e76dd2b652125a6079130d4c315e728bcf4dd1dfb44ac26e82302331d61977d3141118' }, - 'signature': 'eaf4b4dfd7903c32cf6c145ddf0744e86536719f5790b4286b08f1a10f0ad183bc601efc8a49a2a7b41758601a1793693afa1781cf0a63a8f72b08d5a1aaba1e', # noqa + 'signature': '5a1a0dba931a1b0a9055801578109a657fd4a2551fdb42dfd24a2c3a70835648d576f1d6b76b7a1cfedb4061d877c0a0f8adb3b47c7bbcb43b1b038da6b3ab19', # noqa 'amount': 0, - 'id': 'cfd113d8cd9fd46b07030c14fac38c1d3fc0eca991e999eab9d0152ea96ab0dc', - 'serialized': 'ff02170100000002000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19200f90295000000000009626f6c646e696e6a61eaf4b4dfd7903c32cf6c145ddf0744e86536719f5790b4286b08f1a10f0ad183bc601efc8a49a2a7b41758601a1793693afa1781cf0a63a8f72b08d5a1aaba1e' # noqa + 'id': '89b3df28b9069b9cba26ba8a4d2b970bb7e87a37271a96a0e9e0dd831e831f8d', + 'serialized': 'ff011e0100000002000500000000000000023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d300f902950000000000a08058db53e2665c84a40f5152e76dd2b652125a6079130d4c315e728bcf4dd1dfb44ac26e82302331d61977d31411185a1a0dba931a1b0a9055801578109a657fd4a2551fdb42dfd24a2c3a70835648d576f1d6b76b7a1cfedb4061d877c0a0f8adb3b47c7bbcb43b1b038da6b3ab19' # noqa } return data @@ -78,20 +51,22 @@ def transaction_type_3(): """Transaction of type "vote" """ data = { - 'version': 2, - 'network': 23, - 'typeGroup': 1, - 'type': 3, - 'nonce': 1, - 'senderPublicKey': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', - 'fee': 100000000, - 'asset': { - 'votes': ['+022cca9529ec97a772156c152a00aad155ee6708243e65c9d211a589cb5d43234d'] + "version": 1, + "network": 30, + "typeGroup": 1, + "type": 3, + "nonce": 5, + "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", + "fee": 100000000, + "asset": { + "unvotes": [], + "votes": ["03f25455408f9a7e6c6a056b121e68fbda98f3511d22e9ef27b0ebaf1ef9e4eabc"] }, - 'signature': '86007f8e6a982bc271ec063c20f158734f0bc1e23e0e1abf9edeaa208b4810fa1d466171bba79a5c00b0a4c698728f68aa0748d98613cac247c014ee84a6fc41', # noqa - 'amount': 0, - 'id': '2c5d71028607674411c8e37e316a015eccbeb9ba486fddfbd393dc421540a90a', - 'serialized': 'ff02170100000003000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19200e1f50500000000000101022cca9529ec97a772156c152a00aad155ee6708243e65c9d211a589cb5d43234d86007f8e6a982bc271ec063c20f158734f0bc1e23e0e1abf9edeaa208b4810fa1d466171bba79a5c00b0a4c698728f68aa0748d98613cac247c014ee84a6fc41' # noqa + "signature": "5946eea46e46026bd5feb5335ba9411fa30677c9a9a9a788065cf1e95bf2896659485fd26b78613640b1209827ce6d4587a05a4721c63e8af24d3351e1eeaa6c", # noqa + "amount": 0, + "id": "a2ff7837281cb978d6f293b46da7bb0342fe09923eede0bfbef9dd57ffee9158", + "serialized": + "ff011e0100000003000500000000000000023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d300e1f50500000000000103f25455408f9a7e6c6a056b121e68fbda98f3511d22e9ef27b0ebaf1ef9e4eabc005946eea46e46026bd5feb5335ba9411fa30677c9a9a9a788065cf1e95bf2896659485fd26b78613640b1209827ce6d4587a05a4721c63e8af24d3351e1eeaa6c" # noqa } return data @@ -101,55 +76,32 @@ def transaction_type_4(): """Transaction of type "multi signature registration" """ data = { - 'version': 2, - 'network': 23, + 'version': 1, + 'network': 30, 'typeGroup': 1, 'type': 4, - 'nonce': 1, - 'senderPublicKey': '0205d9bbe71c343ac9a6a83a4344fd404c3534fc7349827097d0835d160bc2b896', - 'id': 'c868aad20165a336c35e324378f0c12008d18af4c1025291efcb7539c7c917f0', + 'nonce': 5, + 'senderPublicKey': '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3', + 'id': 'f140e52f89c3b782da2fc47111abc85610e784ab3a55115712fe5fb2aa061f16', 'amount': 0, - 'fee': 2000000000, - 'signature': 'f5e9859c955bf8917b308ea21c88daf58661686c2017e476dcf735ad7f00aebf8e6effda3fe99e5f33f6007db7db9c9155796d9b5d31c53bd6156364a6a765d0', # noqa + 'fee': 500000000, + 'signature': '5cc5dc6ca87dd1b3bd24dba27e72cce6b32284821f217f1a0195ba319e3ad3b8c9bdae225da62cd52ee911e7ffecd5e4d1197421070a8c92ee57e00398cf3dcb', # noqa 'asset': { 'multiSignature': { 'publicKeys': [ - '0205d9bbe71c343ac9a6a83a4344fd404c3534fc7349827097d0835d160bc2b896', - '03df0a1eb42d99b5de395cead145ba1ec2ea837be308c7ce3a4e8018b7efc7fdb8', - '03860d76b1df09659ac282cea3da5bd84fc45729f348a4a8e5f802186be72dc17f' + "029fab3cb2f5e248ae7cbb4de646741da4d73c493b2a03ab5c71507fb2c0dcca92", + "03629f9dbf7f1e91cefa845126189816ceae357bdd1f41bd14787318a7d5b55d48", + "027941d2059f89a26d89e87d3385e261a0ede1234aaeaa487012b69d6b67962dc5", ], 'min': 2, } }, 'signatures': [ - '0064900cb2cc3db6ca9c7e3bd363b322cdc4a39e051f655e9867935e1bb856b6dcce52845c031c690808f40340bc827bbaacd7b04bceff866cb0d386ab84715174', # noqa - '01dd363ccc101a958bded1a5db1c08f13283fc7cee53da93dfe00785eb406512467ff8e445f8ad843744ac4179f30f942645dfd5bdf5f2bfc344ad02393053880a', # noqa - '02d0012f035dc3fd54173c83d40217914653488fe9ce592dca34234163181d255281f2be7033725cfc4a6786509e7fabbaf0be8cf50882fc7b66fe94f259fd004e' # noqa + '003eeb63ff599b2d2255127323522f0559dd9444873b08203f998515bfb107cee68edb9e393622d93913a1afdb28768d6accd2b725c75942a638719795de344156', # noqa + '017aeccd6d60ae1b6ab5121f59225fe8246d58f8cfae76deb3e492a87abe4e37f622cc07948f7c9b62c0447deef05f4960bae25354699f9d8f9ddfa4be4de04923', # noqa + '02e6df01f999919f1a6236c8ac8ce54e3e41042e14547b1aa86b44c4c05e02f1ac89a44d0ad8a5f0374b00b5285d0b9b1cb64e4212a792bf5e4775c4d761c44c9d' # noqa ], - 'serialized': 'ff021701000000040001000000000000000205d9bbe71c343ac9a6a83a4344fd404c3534fc7349827097d0835d160bc2b89600943577000000000002030205d9bbe71c343ac9a6a83a4344fd404c3534fc7349827097d0835d160bc2b89603df0a1eb42d99b5de395cead145ba1ec2ea837be308c7ce3a4e8018b7efc7fdb803860d76b1df09659ac282cea3da5bd84fc45729f348a4a8e5f802186be72dc17ff5e9859c955bf8917b308ea21c88daf58661686c2017e476dcf735ad7f00aebf8e6effda3fe99e5f33f6007db7db9c9155796d9b5d31c53bd6156364a6a765d00064900cb2cc3db6ca9c7e3bd363b322cdc4a39e051f655e9867935e1bb856b6dcce52845c031c690808f40340bc827bbaacd7b04bceff866cb0d386ab8471517401dd363ccc101a958bded1a5db1c08f13283fc7cee53da93dfe00785eb406512467ff8e445f8ad843744ac4179f30f942645dfd5bdf5f2bfc344ad02393053880a02d0012f035dc3fd54173c83d40217914653488fe9ce592dca34234163181d255281f2be7033725cfc4a6786509e7fabbaf0be8cf50882fc7b66fe94f259fd004e' # noqa - } - return data - - -@pytest.fixture -def transaction_type_5(): - """Transaction of type "ipfs" - """ - data = { - 'version': 2, - 'network': 23, - 'typeGroup': 1, - 'type': 5, - 'nonce': 1, - 'senderPublicKey': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', - 'fee': 500000000, - 'amount': 0, - 'asset': { - 'ipfs': 'QmR45FmbVVrixReBwJkhEKde2qwHYaQzGxu4ZoDeswuF9w' - }, - 'signature': '0b6e81b123de99e953d3073a8760d3213ab5f5cf512e65a2dd73aebb410966d8fbc59e775deb4f23c51be0847402b5e1d4ee68732b3e6d8e8914d259d7e373eb', - 'id': '818228ce634b46c488f3b2df8fd02bd50331ebdedb44df5b9b11b97b01e9fb36', - 'serialized': 'ff02170100000005000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed1920065cd1d000000000012202853f0f11ab91d73b73a2a86606103f45dd469ad2e89ec6f9a25febe8758d3fe0b6e81b123de99e953d3073a8760d3213ab5f5cf512e65a2dd73aebb410966d8fbc59e775deb4f23c51be0847402b5e1d4ee68732b3e6d8e8914d259d7e373eb' + 'serialized': 'ff011e0100000004000500000000000000023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d30065cd1d00000000000203029fab3cb2f5e248ae7cbb4de646741da4d73c493b2a03ab5c71507fb2c0dcca9203629f9dbf7f1e91cefa845126189816ceae357bdd1f41bd14787318a7d5b55d48027941d2059f89a26d89e87d3385e261a0ede1234aaeaa487012b69d6b67962dc55cc5dc6ca87dd1b3bd24dba27e72cce6b32284821f217f1a0195ba319e3ad3b8c9bdae225da62cd52ee911e7ffecd5e4d1197421070a8c92ee57e00398cf3dcb003eeb63ff599b2d2255127323522f0559dd9444873b08203f998515bfb107cee68edb9e393622d93913a1afdb28768d6accd2b725c75942a638719795de344156017aeccd6d60ae1b6ab5121f59225fe8246d58f8cfae76deb3e492a87abe4e37f622cc07948f7c9b62c0447deef05f4960bae25354699f9d8f9ddfa4be4de0492302e6df01f999919f1a6236c8ac8ce54e3e41042e14547b1aa86b44c4c05e02f1ac89a44d0ad8a5f0374b00b5285d0b9b1cb64e4212a792bf5e4775c4d761c44c9d' # noqa } return data @@ -159,29 +111,29 @@ def transaction_type_6(): """Transaction of type "multi payment" """ data = { - 'version': 2, - 'network': 23, + 'version': 1, + 'network': 30, 'typeGroup': 1, 'type': 6, - 'nonce': 1, - 'senderPublicKey': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', + 'nonce': 5, + 'senderPublicKey': '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3', 'fee': 10000000, 'amount': 0, 'asset': { 'payments': [ { - 'amount': 1, - 'recipientId': 'AHXtmB84sTZ9Zd35h9Y1vfFvPE2Xzqj8ri' + 'amount': 100000000, + 'recipientId': '0xb693449AdDa7EFc015D87944EAE8b7C37EB1690A' }, { - 'amount': 2, - 'recipientId': 'AZFEPTWnn2Sn8wDZgCRF8ohwKkrmk2AZi1' + 'amount': 200000000, + 'recipientId': '0xb693449AdDa7EFc015D87944EAE8b7C37EB1690A' }, ], }, - 'signature': '672e89e66a9c5d7d95c21ccd07a89a111f02823146c06f14689d2cf1efd645fb648258fcf2280486d2cae19f391796d72145d2a8e6f261e887e34cd1998bdb65', - 'id': 'e8c7293d428048f8678dc6c88cb8b32bd49c8ae9b02018297c1889d9bd33ba8d', - 'serialized': 'ff02170100000006000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed1928096980000000000000200010000000000000017134b5be4b327ddf9c2bb47fec8a1a44189e90f74020000000000000017bfa6aec83cf1bd03a0cab9f35c85ff51a3e9f041672e89e66a9c5d7d95c21ccd07a89a111f02823146c06f14689d2cf1efd645fb648258fcf2280486d2cae19f391796d72145d2a8e6f261e887e34cd1998bdb65' + 'signature': '220b5d1597716c63f7945da495793f6fcb9997481108e9c1e4e7b72c6ec31fe11b2015b4940938ca8979e84ec06550825b3c0a24e23fdae087173d4d74e4d8bc', + 'id': 'c42df7851d62e99ef52363b6344a7f81069e5a2e239144853990839aa4084fb3', + 'serialized': 'ff011e0100000006000500000000000000023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3809698000000000000020000e1f50500000000b693449adda7efc015d87944eae8b7c37eb1690a00c2eb0b00000000b693449adda7efc015d87944eae8b7c37eb1690a220b5d1597716c63f7945da495793f6fcb9997481108e9c1e4e7b72c6ec31fe11b2015b4940938ca8979e84ec06550825b3c0a24e23fdae087173d4d74e4d8bc' } return data @@ -191,97 +143,17 @@ def transaction_type_7(): """Transaction of type "delegate resignation" """ data = { - 'version': 2, - 'network': 23, + 'version': 1, + 'network': 30, 'typeGroup': 1, 'type': 7, - 'nonce': 1, - 'senderPublicKey': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', + 'nonce': 5, + 'senderPublicKey': '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3', 'fee': 2500000000, 'amount': 0, - 'signature': 'bdc048ca7eb5688cc01921aecf5914118cfc78eacc23825efa6d75094a683127cc02512dc59e1e0631fa8956f482eabc54933d23011a8337ea9cab99abed504d', - 'id': '707b4deb339e717dfef44c40db0692015ce9bbab015c007b016b8a46b341e859', - 'serialized': 'ff02170100000007000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19200f902950000000000bdc048ca7eb5688cc01921aecf5914118cfc78eacc23825efa6d75094a683127cc02512dc59e1e0631fa8956f482eabc54933d23011a8337ea9cab99abed504d' - } - return data - - -@pytest.fixture -def transaction_type_8(): - """Transaction of type "HTLC lock" - """ - data = { - 'version': 2, - 'network': 23, - 'typeGroup': 1, - 'type': 8, - 'nonce': 1, - 'senderPublicKey': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', - 'fee': 10000000, - 'amount': 200000000, - 'recipientId': 'AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', - 'asset': { - 'lock': { - 'secretHash': '0f128d401958b1b30ad0d10406f47f9489321017b4614e6cb993fc63913c5454', - 'expiration': { - 'type': 1, - 'value': 1573455822 - } - }, - }, - 'signature': '7fe939b22a1da166b6ea58e3964651236fb4e0739f9716dedf92986f37df71ea7993e9a97b4a1686c0ad08028dcae08b7cb4a54b8a4db57e72b839a611e86358', - 'id': 'e1b34afa54bbf34de5c00716b92246c5248c2135221ece169db877ca60a14007', - 'serialized': 'ff02170100000008000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19280969800000000000000c2eb0b000000000f128d401958b1b30ad0d10406f47f9489321017b4614e6cb993fc63913c545401ce07c95d170995750207ecaf0ccf251c1265b92ad84f5536627fe939b22a1da166b6ea58e3964651236fb4e0739f9716dedf92986f37df71ea7993e9a97b4a1686c0ad08028dcae08b7cb4a54b8a4db57e72b839a611e86358' - - } - return data - -@pytest.fixture -def transaction_type_9(): - """Transaction of type "HTLC claim" - """ - data = { - 'amount': 0, - 'asset': { - 'claim': { - 'lockTransactionId': '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4', - 'unlockSecret': '6434323233626639336532303235303561366135303134323161383864396661' - } - }, - 'fee': 0, - 'id': 'aad3fdb321e2543af1dd9d2d4d155473a8b49cacbc116fa1f3f1b95154b336d5', - 'senderPublicKey': '037fde73baaa48eb75c013fe9ff52a74a096d48b9978351bdcb5b72331ca37487c', - 'signature': 'dcd867411d20c7aa891e44cd92e916ea1d1e64ef1518dfcdfa227e4415d846a66c60718dc9d4bfc354afa69c2f8fa6e68f57e6eaf53c51b7a209ead5702ffd71', - 'nonce': 1, - 'type': 9, - 'typeGroup': 1, - 'version': 2, - 'serialized': 'ff021e0100000009000100000000000000037fde73baaa48eb75c013fe9ff52a74a096d48b9978351bdcb5b72331ca37487c000000000000000000943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb46434323233626639336532303235303561366135303134323161383864396661dcd867411d20c7aa891e44cd92e916ea1d1e64ef1518dfcdfa227e4415d846a66c60718dc9d4bfc354afa69c2f8fa6e68f57e6eaf53c51b7a209ead5702ffd71', - } - return data - -@pytest.fixture -def transaction_type_10(): - """Transaction of type "HTLC refund" - """ - data = { - 'version': 2, - 'network': 23, - 'typeGroup': 1, - 'type': 10, - 'nonce': 1, - 'senderPublicKey': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', - 'fee': 0, - 'amount': 0, - 'asset': { - 'refund': { - 'lockTransactionId': '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4', - }, - }, - 'signature': '16d9ef1dceb0cbb105a45af6bdde9439055f07197643f9e2837312463330fd02ec7b13d1242becfe333c1b8ab2ea91c0c8240390d86f0fb0f6cdc22ec6ac64f1', - 'id': '9356aa730990a2ea8e9871ffa65800f34ef1a4bec3215d89c950e72d82a34e91', - 'serialized': 'ff0217010000000a000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192000000000000000000943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb416d9ef1dceb0cbb105a45af6bdde9439055f07197643f9e2837312463330fd02ec7b13d1242becfe333c1b8ab2ea91c0c8240390d86f0fb0f6cdc22ec6ac64f1' - + 'signature': '34ec032ec41273043cf4f809e16e0892104743fac7da1d7ff33fb75b5569cc1c59e03558ceb2f28daf1cfaec8b88ce994c2114177f4bfeca03c0b0533d58dc1f', + 'id': '4579323b640090f5a43f1b22d29082348a6f0016b2b28ad3bc44f1838647c83d', + 'serialized': 'ff011e0100000007000500000000000000023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d300f90295000000000034ec032ec41273043cf4f809e16e0892104743fac7da1d7ff33fb75b5569cc1c59e03558ceb2f28daf1cfaec8b88ce994c2114177f4bfeca03c0b0533d58dc1f' } return data @@ -289,17 +161,9 @@ def transaction_type_10(): @pytest.fixture def message(): data = { - 'camelCase_pk': { - 'publicKey': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', - 'signature': '304402200fb4adddd1f1d652b544ea6ab62828a0a65b712ed447e2538db0caebfa68929e02205ecb2e1c63b29879c2ecf1255db506d671c8b3fa6017f67cfd1bf07e6edd1cc8', # noqa - 'message': 'Hello World' - }, - - 'snake_case_pk': { - 'publickey': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', - 'signature': '304402200fb4adddd1f1d652b544ea6ab62828a0a65b712ed447e2538db0caebfa68929e02205ecb2e1c63b29879c2ecf1255db506d671c8b3fa6017f67cfd1bf07e6edd1cc8', # noqa - 'message': 'Hello World' - }, - 'passphrase': 'this is a top secret passphrase' + 'publicKey': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', + 'signature': '22ff2371becb58439efdb95d116215f75bc41a380e1491605cc4874dd0d2c7d7958e4072da22fd307fe419c8864ba313a28bdb6fecbc6407839b6ba472395fe9', # noqa + 'message': 'Hello World', + 'passphrase': 'this is a top secret passphrase', } return data diff --git a/tests/identity/conftest.py b/tests/identity/conftest.py index 90e09c52..325e38cd 100644 --- a/tests/identity/conftest.py +++ b/tests/identity/conftest.py @@ -6,11 +6,11 @@ def identity(): """ data = { 'data': { - 'private_key': 'd8839c2432bfd0a67ef10a804ba991eabba19f154a3d707917681d45822a5712', - 'public_key': '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192', - 'address': '0xb0FF9213f7226bBB72b84dE16af86e56f1f38B01', - 'wif': 'SGq4xLgZKCGxs7bjmwnBrWcT4C1ADFEermj846KC97FSv1WFD1dA' + 'private_key': 'bef98d4c0e58d0e4695560594f91a349421b7cdc3e63a560470ccb259f99f087', + 'public_key': '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3', + 'address': '0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', + 'wif': 'SFyRYRYL1DddchRpuhp94hKN1tpYjzAEkLuUDAMjGJBkoAaz2RQk' }, - 'passphrase': 'this is a top secret passphrase' + 'passphrase': 'my super secret passphrase' } return data diff --git a/tests/identity/test_public_key.py b/tests/identity/test_public_key.py index 2311d532..af10b8a8 100644 --- a/tests/identity/test_public_key.py +++ b/tests/identity/test_public_key.py @@ -1,12 +1,12 @@ from crypto.identity.public_key import PublicKey -def test_private_key_from_passphrase(identity): +def test_public_key_from_passphrase(identity): public_key = PublicKey.from_passphrase(identity['passphrase']) assert public_key == identity['data']['public_key'] -def test_private_key_from_hex(identity): +def test_public_key_from_hex(identity): public_key = PublicKey.from_hex(identity['data']['public_key']) assert isinstance(public_key, PublicKey) assert public_key.to_hex() == identity['data']['public_key'] diff --git a/tests/transactions/builder/conftest.py b/tests/transactions/builder/conftest.py new file mode 100644 index 00000000..374172f8 --- /dev/null +++ b/tests/transactions/builder/conftest.py @@ -0,0 +1,7 @@ +import pytest + +@pytest.fixture +def passphrase(): + """Passphrase used for tests""" + + return 'my super secret passphrase' diff --git a/tests/transactions/builder/test_delegate_registration.py b/tests/transactions/builder/test_delegate_registration.py index f5d08cff..b9bf6bb5 100644 --- a/tests/transactions/builder/test_delegate_registration.py +++ b/tests/transactions/builder/test_delegate_registration.py @@ -6,45 +6,49 @@ set_network(Devnet) -def test_delegate_registration_transaction(): +def test_delegate_registration_transaction(passphrase): """Test if a delegate registration transaction gets built """ - delegate_name = 'mr.delegate' + bls_public_key = 'a227bf7c57eaa6e4f5de7b17495b4ea0be645d1204ce2fc9b54dbfabe23a59b6377e924c12aa4a831483af021fbc29ec' - transaction = DelegateRegistration(delegate_name) + transaction = DelegateRegistration(bls_public_key) transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) transaction.set_nonce(1) - transaction.schnorr_sign('testing') + transaction.sign(passphrase) transaction_dict = transaction.to_dict() assert transaction_dict['nonce'] == 1 assert transaction_dict['signature'] - assert transaction_dict['asset']['delegate']['username'] == delegate_name + assert transaction_dict['asset']['validatorPublicKey'] == bls_public_key assert transaction_dict['type'] is TRANSACTION_DELEGATE_REGISTRATION assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 2500000000 + assert transaction_dict['expiration'] == 0 + assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid -def test_delegate_registration_transaction_custom_fee(): +def test_delegate_registration_transaction_custom_fee(passphrase): """Test if a delegate registration transaction gets built with a custom fee """ - delegate_name = 'mr.delegate' + bls_public_key = 'a227bf7c57eaa6e4f5de7b17495b4ea0be645d1204ce2fc9b54dbfabe23a59b6377e924c12aa4a831483af021fbc29ec' - transaction = DelegateRegistration(delegate_name, 5) + transaction = DelegateRegistration(bls_public_key, 5) transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) transaction.set_nonce(1) - transaction.schnorr_sign('testing') + transaction.sign(passphrase) transaction_dict = transaction.to_dict() assert transaction_dict['nonce'] == 1 assert transaction_dict['signature'] - assert transaction_dict['asset']['delegate']['username'] == delegate_name + assert transaction_dict['asset']['validatorPublicKey'] == bls_public_key assert transaction_dict['type'] is TRANSACTION_DELEGATE_REGISTRATION assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 5 + assert transaction_dict['expiration'] == 0 + assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_delegate_resignation.py b/tests/transactions/builder/test_delegate_resignation.py index c760b837..fd3a957f 100644 --- a/tests/transactions/builder/test_delegate_resignation.py +++ b/tests/transactions/builder/test_delegate_resignation.py @@ -6,13 +6,13 @@ set_network(Devnet) -def test_delegate_resignation_transaction(): +def test_delegate_resignation_transaction(passphrase): """Test if delegate resignation transaction gets built """ transaction = DelegateResignation() transaction.set_nonce(1) transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.schnorr_sign('testing') + transaction.sign(passphrase) transaction_dict = transaction.to_dict() assert transaction_dict['nonce'] == 1 @@ -21,17 +21,19 @@ def test_delegate_resignation_transaction(): assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 2500000000 + assert transaction_dict['expiration'] == 0 + assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid -def test_delegate_resignation_transaction_custom_fee(): +def test_delegate_resignation_transaction_custom_fee(passphrase): """Test if delegate resignation transaction gets built with a custom fee """ transaction = DelegateResignation(5) transaction.set_nonce(1) transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.schnorr_sign('testing') + transaction.sign(passphrase) transaction_dict = transaction.to_dict() assert transaction_dict['nonce'] == 1 @@ -40,5 +42,7 @@ def test_delegate_resignation_transaction_custom_fee(): assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 5 + assert transaction_dict['expiration'] == 0 + assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_htlc_claim.py b/tests/transactions/builder/test_htlc_claim.py deleted file mode 100644 index 757145eb..00000000 --- a/tests/transactions/builder/test_htlc_claim.py +++ /dev/null @@ -1,91 +0,0 @@ -import binascii -import pytest -from crypto.configuration.network import set_network -from crypto.constants import TRANSACTION_HTLC_CLAIM, TRANSACTION_TYPE_GROUP -from crypto.networks.devnet import Devnet -from crypto.exceptions import ArkSerializerException -from crypto.transactions.builder.htlc_claim import HtlcClaim - -set_network(Devnet) - - -def test_htlc_claim_transaction_ok(): - """Test if timelock transaction gets built - """ - lock_transaction_id = '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' - - # Secret required to unlock and claim funds from an HTLC. This secret should is revealed to you - # on the blockchain which you can then use to claim funds. - unlock_secret = '6434323233626639336532303235303561366135303134323161383864396661' - - transaction = HtlcClaim(lock_transaction_id, unlock_secret) - transaction.set_nonce(1) - transaction.schnorr_sign('testing') - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['type'] is TRANSACTION_HTLC_CLAIM - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 0 - assert transaction_dict['asset']['claim']['lockTransactionId'] == '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' - assert transaction_dict['asset']['claim']['unlockSecret'] == unlock_secret - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid - - -def test_htlc_claim_transaction_unlock_secret_not_hex(): - """Test if timelock transaction errors if an invalid hex unlock_secret is given - """ - lock_transaction_id = '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' - - # Secret required to unlock and claim funds from an HTLC. This secret should is revealed to you - # on the blockchain which you can then use to claim funds. - unlock_secret = '643432323362663933653230323530356136613530313432316138a84a3966x1' - - transaction = HtlcClaim(lock_transaction_id, unlock_secret) - transaction.set_nonce(1) - with pytest.raises(binascii.Error) as e: - transaction.schnorr_sign('testing') - assert str(e.value) == 'Non-hexadecimal digit found' - - -def test_htlc_claim_transaction_unlock_secret_bad_length(): - """Test if timelock transaction fails if the unlock_secret is too big - """ - lock_transaction_id = '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' - - # Secret required to unlock and claim funds from an HTLC. This secret should is revealed to you - # on the blockchain which you can then use to claim funds. - unlock_secret = '326632383634643861626534336530363236363132643461636235623535666462' - - transaction = HtlcClaim(lock_transaction_id, unlock_secret) - transaction.set_nonce(1) - with pytest.raises(ArkSerializerException) as e: - transaction.schnorr_sign('testing') - assert str(e.value) == 'Unlock secret must be 32 bytes long' - - -def test_htlc_claim_transaction_custom_fee_ok(): - """Test if timelock transaction gets built with a custom fee - """ - lock_transaction_id = '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' - - # Secret required to unlock and claim funds from an HTLC. This secret should is revealed to you - # on the blockchain which you can then use to claim funds. - unlock_secret = '6434323233626639336532303235303561366135303134323161383864396661' - - transaction = HtlcClaim(lock_transaction_id, unlock_secret, 5) - transaction.set_nonce(1) - transaction.schnorr_sign('testing') - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['type'] is TRANSACTION_HTLC_CLAIM - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 5 - assert transaction_dict['asset']['claim']['lockTransactionId'] == '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' - assert transaction_dict['asset']['claim']['unlockSecret'] == unlock_secret - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_htlc_lock.py b/tests/transactions/builder/test_htlc_lock.py deleted file mode 100644 index a7aa79ef..00000000 --- a/tests/transactions/builder/test_htlc_lock.py +++ /dev/null @@ -1,128 +0,0 @@ -from hashlib import sha256 -import pytest - -from crypto.configuration.network import set_network -from crypto.constants import TRANSACTION_HTLC_LOCK, TRANSACTION_TYPE_GROUP -from crypto.networks.devnet import Devnet -from crypto.transactions.builder.htlc_lock import HtlcLock - -set_network(Devnet) - - -def test_htlc_lock_transation_amount_not_int(): - with pytest.raises(ValueError): - """Test error handling in constructor for non-integer amount - """ - secret_hash = _generate_secret_hash() - HtlcLock( - recipient_id='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', - amount='bad amount number', - secret_hash=secret_hash, - expiration_type=1, - expiration_value=1573455822 - ) - - -def test_htlc_lock_transation_amount_zero(): - with pytest.raises(ValueError): - """Test error handling in constructor for non-integer amount - """ - secret_hash = _generate_secret_hash() - HtlcLock( - recipient_id='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', - amount=0, - secret_hash=secret_hash, - expiration_type=1, - expiration_value=1573455822 - ) - - -def test_htlc_lock_transation_amount_negative(): - with pytest.raises(ValueError): - """Test error handling in constructor for non-integer amount - """ - secret_hash = _generate_secret_hash() - HtlcLock( - recipient_id='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', - amount=-5, - secret_hash=secret_hash, - expiration_type=1, - expiration_value=1573455822 - ) - - -def test_htlc_lock_transaction(): - """Test if timelock transaction gets built - """ - secret_hash = _generate_secret_hash() - transaction = HtlcLock( - recipient_id='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', - amount=200000000, - secret_hash=secret_hash, - expiration_type=1, - expiration_value=1573455822 - ) - - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(1) - transaction.schnorr_sign('testing') - transaction_dict = transaction.to_dict() - - assert transaction_dict['recipientId'] == 'AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC' - assert transaction_dict['amount'] == 200000000 - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['type'] is TRANSACTION_HTLC_LOCK - assert transaction_dict['typeGroup'] == 1 - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 10000000 - assert transaction_dict['asset']['lock']['secretHash'] == secret_hash - assert transaction_dict['asset']['lock']['expiration']['type'] == 1 - assert transaction_dict['asset']['lock']['expiration']['value'] == 1573455822 - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid - - -def test_htlc_lock_transaction_custom_fee(): - """Test if timelock transaction gets built with a custom fee - """ - secret_hash = _generate_secret_hash() - transaction = HtlcLock( - recipient_id='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', - amount=200000000, - secret_hash=secret_hash, - expiration_type=1, - expiration_value=1573455822, - fee=5 - ) - - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(1) - transaction.schnorr_sign('testing') - transaction_dict = transaction.to_dict() - - assert transaction_dict['recipientId'] == 'AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC' - assert transaction_dict['amount'] == 200000000 - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['type'] is TRANSACTION_HTLC_LOCK - assert transaction_dict['typeGroup'] == 1 - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 5 - assert transaction_dict['asset']['lock']['secretHash'] == secret_hash - assert transaction_dict['asset']['lock']['expiration']['type'] == 1 - assert transaction_dict['asset']['lock']['expiration']['value'] == 1573455822 - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid - - -def _generate_secret_hash(): - secret = "super secret code that must be unique and entirely random" - - # secret code is a key ingredient used to unlock the htlc lock. To generate an unlock code one - # must "hexify" it: `unlock_secret = hexlify(secret_h.encode()).decode()`` - secret_code = sha256(secret.encode()).hexdigest()[:32] - - # secret_hash is the hashed secret code - secret_hash = sha256(secret_code.encode()).hexdigest() - return secret_hash diff --git a/tests/transactions/builder/test_htlc_refund.py b/tests/transactions/builder/test_htlc_refund.py deleted file mode 100644 index d5100ef5..00000000 --- a/tests/transactions/builder/test_htlc_refund.py +++ /dev/null @@ -1,53 +0,0 @@ -from crypto.configuration.network import set_network -from crypto.constants import TRANSACTION_HTLC_REFUND, TRANSACTION_TYPE_GROUP -from crypto.networks.devnet import Devnet -from crypto.transactions.builder.htlc_refund import HtlcRefund - -set_network(Devnet) - - -def test_timelock_refund_transaction(): - """Test if timelock transaction gets built - """ - transaction = HtlcRefund( - lock_transaction_id='943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' - ) - - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(1) - transaction.schnorr_sign('testing') - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['type'] is TRANSACTION_HTLC_REFUND - assert transaction_dict['typeGroup'] == 1 - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 0 - assert transaction_dict['asset']['refund']['lockTransactionId'] == '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid - - -def test_timelock_refund_transaction_custom_fee(): - """Test if timelock transaction gets built with custom fee - """ - transaction = HtlcRefund( - lock_transaction_id='943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4', - fee=5 - ) - - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(1) - transaction.schnorr_sign('testing') - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['type'] is TRANSACTION_HTLC_REFUND - assert transaction_dict['typeGroup'] == 1 - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 5 - assert transaction_dict['asset']['refund']['lockTransactionId'] == '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_ipfs.py b/tests/transactions/builder/test_ipfs.py deleted file mode 100644 index 02b5521b..00000000 --- a/tests/transactions/builder/test_ipfs.py +++ /dev/null @@ -1,52 +0,0 @@ -from base58 import b58encode - -from crypto.configuration.network import set_network -from crypto.constants import TRANSACTION_IPFS, TRANSACTION_TYPE_GROUP -from crypto.networks.devnet import Devnet -from crypto.transactions.builder.ipfs import IPFS - -set_network(Devnet) - - -def test_ipfs_transaction(): - """Test if ipfs transaction gets built - """ - ipfs_id = b58encode('hello') - - transaction = IPFS(ipfs_id) - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(1) - transaction.schnorr_sign('testing') - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['type'] is TRANSACTION_IPFS - assert transaction_dict['typeGroup'] == 1 - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 500000000 - assert transaction_dict['asset']['ipfs'] == ipfs_id - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid - - -def test_ipfs_transaction_custom_fee(): - """Test if ipfs transaction gets built with custom fee - """ - ipfs_id = b58encode('hello') - - transaction = IPFS(ipfs_id, 5) - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(1) - transaction.schnorr_sign('testing') - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['type'] is TRANSACTION_IPFS - assert transaction_dict['typeGroup'] == 1 - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 5 - assert transaction_dict['asset']['ipfs'] == ipfs_id - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_multi_payment.py b/tests/transactions/builder/test_multi_payment.py index 6cca3173..ea363397 100644 --- a/tests/transactions/builder/test_multi_payment.py +++ b/tests/transactions/builder/test_multi_payment.py @@ -6,15 +6,15 @@ set_network(Devnet) -def test_multi_payment_transaction(): +def test_multi_payment_transaction(passphrase): """Test if multi payment transaction gets built """ transaction = MultiPayment() transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) transaction.set_nonce(1) - transaction.add_payment(1, 'AHXtmB84sTZ9Zd35h9Y1vfFvPE2Xzqj8ri') - transaction.add_payment(2, 'ATK14wxyYxbELq2b91bAfBY8Vmh9J6MDej') - transaction.schnorr_sign('testing') + transaction.add_payment(1, '0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22') + transaction.add_payment(2, '0xb693449AdDa7EFc015D87944EAE8b7C37EB1690A') + transaction.sign(passphrase) transaction_dict = transaction.to_dict() assert transaction_dict['nonce'] == 1 @@ -23,23 +23,26 @@ def test_multi_payment_transaction(): assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 10000000 + assert transaction_dict['expiration'] == 0 + assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' + assert transaction_dict['asset']['payments'][0]['amount'] == 1 - assert transaction_dict['asset']['payments'][0]['recipientId'] == 'AHXtmB84sTZ9Zd35h9Y1vfFvPE2Xzqj8ri' + assert transaction_dict['asset']['payments'][0]['recipientId'] == '0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22' assert transaction_dict['asset']['payments'][1]['amount'] == 2 - assert transaction_dict['asset']['payments'][1]['recipientId'] == 'ATK14wxyYxbELq2b91bAfBY8Vmh9J6MDej' + assert transaction_dict['asset']['payments'][1]['recipientId'] == '0xb693449AdDa7EFc015D87944EAE8b7C37EB1690A' transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid -def test_multi_payment_transaction_custom_fee(): +def test_multi_payment_transaction_custom_fee(passphrase): """Test if multi payment transaction gets built with a custom fee """ transaction = MultiPayment(fee=5) transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) transaction.set_nonce(1) - transaction.add_payment(1, 'AHXtmB84sTZ9Zd35h9Y1vfFvPE2Xzqj8ri') - transaction.add_payment(2, 'ATK14wxyYxbELq2b91bAfBY8Vmh9J6MDej') - transaction.schnorr_sign('testing') + transaction.add_payment(1, '0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22') + transaction.add_payment(2, '0xb693449AdDa7EFc015D87944EAE8b7C37EB1690A') + transaction.sign(passphrase) transaction_dict = transaction.to_dict() assert transaction_dict['nonce'] == 1 @@ -48,9 +51,12 @@ def test_multi_payment_transaction_custom_fee(): assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 5 + assert transaction_dict['expiration'] == 0 + assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' + assert transaction_dict['asset']['payments'][0]['amount'] == 1 - assert transaction_dict['asset']['payments'][0]['recipientId'] == 'AHXtmB84sTZ9Zd35h9Y1vfFvPE2Xzqj8ri' + assert transaction_dict['asset']['payments'][0]['recipientId'] == '0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22' assert transaction_dict['asset']['payments'][1]['amount'] == 2 - assert transaction_dict['asset']['payments'][1]['recipientId'] == 'ATK14wxyYxbELq2b91bAfBY8Vmh9J6MDej' + assert transaction_dict['asset']['payments'][1]['recipientId'] == '0xb693449AdDa7EFc015D87944EAE8b7C37EB1690A' transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_multi_signature_registration.py b/tests/transactions/builder/test_multi_signature_registration.py index bd471d65..0724fc22 100644 --- a/tests/transactions/builder/test_multi_signature_registration.py +++ b/tests/transactions/builder/test_multi_signature_registration.py @@ -6,7 +6,7 @@ set_network(Devnet) -def test_multi_signature_registration_transaction(): +def test_multi_signature_registration_transaction(passphrase): """Test if a second signature registration transaction gets built """ publicKeys = [ @@ -21,22 +21,24 @@ def test_multi_signature_registration_transaction(): transaction.set_min(2) transaction.set_public_keys(publicKeys) transaction.add_participant('03860d76b1df09659ac282cea3da5bd84fc45729f348a4a8e5f802186be72dc17f') - transaction.multi_sign('this is a top secret passphrase 1', 0) + transaction.multi_sign(passphrase, 0) transaction.multi_sign('this is a top secret passphrase 2', 1) transaction.multi_sign('this is a top secret passphrase 3', 2) - transaction.schnorr_sign('this is a top secret passphrase 1') + transaction.sign(passphrase) transaction_dict = transaction.to_dict() assert transaction_dict['nonce'] == 1 - assert transaction_dict['version'] == 2 + assert transaction_dict['version'] == 1 assert transaction_dict['fee'] == 2000000000 assert transaction_dict['signature'] assert transaction_dict['type'] is TRANSACTION_MULTI_SIGNATURE_REGISTRATION assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['asset']['multiSignature']['min'] == 2 + assert transaction_dict['expiration'] == 0 + assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' assert transaction_dict['asset']['multiSignature']['publicKeys'][0] == publicKeys[0] assert transaction_dict['asset']['multiSignature']['publicKeys'][1] == publicKeys[1] diff --git a/tests/transactions/builder/test_second_signature_registration.py b/tests/transactions/builder/test_second_signature_registration.py deleted file mode 100644 index 8da33638..00000000 --- a/tests/transactions/builder/test_second_signature_registration.py +++ /dev/null @@ -1,44 +0,0 @@ -from crypto.configuration.network import set_network -from crypto.constants import TRANSACTION_SECOND_SIGNATURE_REGISTRATION, TRANSACTION_TYPE_GROUP -from crypto.networks.devnet import Devnet -from crypto.transactions.builder.second_signature_registration import SecondSignatureRegistration - -set_network(Devnet) - - -def test_second_signature_registration_transaction(): - """Test if a second signature registration transaction gets built - """ - transaction = SecondSignatureRegistration('this is a top secret passphrase') - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(1) - transaction.schnorr_sign('testing') - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['type'] is TRANSACTION_SECOND_SIGNATURE_REGISTRATION - assert transaction_dict['typeGroup'] == 1 - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 500000000 - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid - - -def test_second_signature_registration_transaction_custom_fee(): - """Test if a second signature registration transaction gets built with a custom fee - """ - transaction = SecondSignatureRegistration('this is a top secret passphrase', 5) - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(1) - transaction.schnorr_sign('testing') - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['type'] is TRANSACTION_SECOND_SIGNATURE_REGISTRATION - assert transaction_dict['typeGroup'] == 1 - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 5 - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_transfer.py b/tests/transactions/builder/test_transfer.py index 1c6c81d9..4436e602 100644 --- a/tests/transactions/builder/test_transfer.py +++ b/tests/transactions/builder/test_transfer.py @@ -10,39 +10,47 @@ set_network(Devnet) -def test_transfer_transaction(): +def test_transfer_transaction(passphrase): """Test if a transfer transaction gets built """ transaction = Transfer( - recipientId='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', - amount=200000000, + recipientId='0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', + amount=1, + fee=10000000, + # timestamp=1720707047217, ) transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(1) - transaction.schnorr_sign('this is a top secret passphrase') + transaction.set_nonce(8) + # transaction.transaction.id = '495afb812cb0ecfe7ac4d383b54d6458b53bb9be5ab37e2207bbd7ce82fdbc94' + transaction.sign(passphrase) transaction_dict = transaction.to_dict() - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] + print(transaction_dict, transaction) + + assert transaction_dict['version'] == 1 + assert transaction_dict['nonce'] == 8 assert transaction_dict['type'] is TRANSACTION_TRANSFER assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 10000000 + assert transaction_dict['amount'] == 1 + assert transaction_dict['expiration'] == 0 + assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid -def test_transfer_transaction_update_amount(): +def test_transfer_transaction_update_amount(passphrase): """Test if a transfer transaction can update an amount """ transaction = Transfer( - recipientId='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', + recipientId='0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', amount=200000000 ) transaction.set_amount(10) transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) transaction.set_nonce(1) - transaction.schnorr_sign('this is a top secret passphrase') + transaction.sign(passphrase) transaction_dict = transaction.to_dict() assert transaction_dict['nonce'] == 1 @@ -51,21 +59,23 @@ def test_transfer_transaction_update_amount(): assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['amount'] == 10 + assert transaction_dict['expiration'] == 0 + assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid -def test_transfer_transaction_custom_fee(): +def test_transfer_transaction_custom_fee(passphrase): """Test if a transfer transaction gets built with a custom fee """ transaction = Transfer( - recipientId='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', + recipientId='0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', amount=200000000, fee=5 ) transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) transaction.set_nonce(1) - transaction.schnorr_sign('this is a top secret passphrase') + transaction.sign(passphrase) transaction_dict = transaction.to_dict() assert transaction_dict['nonce'] == 1 @@ -74,20 +84,22 @@ def test_transfer_transaction_custom_fee(): assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 5 + assert transaction_dict['expiration'] == 0 + assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid -def test_transfer_secondsig_transaction(): +def test_transfer_secondsig_transaction(passphrase): """Test if a transfer transaction with second signature gets built """ transaction = Transfer( - recipientId='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', + recipientId='0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', amount=200000000, ) transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) transaction.set_nonce(1) - transaction.schnorr_sign('this is a top secret passphrase') + transaction.sign(passphrase) transaction.second_sign('second top secret passphrase') transaction_dict = transaction.to_dict() @@ -97,6 +109,8 @@ def test_transfer_secondsig_transaction(): assert transaction_dict['type'] is TRANSACTION_TRANSFER assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['expiration'] == 0 + assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid transaction.schnorr_verify_second(PublicKey.from_passphrase('second top secret passphrase')) # if no exception is raised, it means the transaction is valid @@ -119,7 +133,7 @@ def test_transfer_transaction_amount_not_int(): """Test error handling in constructor for non-integer amount """ Transfer( - recipientId='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', + recipientId='0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', amount='bad amount' ) @@ -129,6 +143,6 @@ def test_transfer_transaction_amount_zero(): """Test error handling in constructor for non-integer amount """ Transfer( - recipientId='AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC', + recipientId='0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', amount=0 ) diff --git a/tests/transactions/builder/test_vote.py b/tests/transactions/builder/test_vote.py index 3f87333d..b7f78fd9 100644 --- a/tests/transactions/builder/test_vote.py +++ b/tests/transactions/builder/test_vote.py @@ -6,45 +6,99 @@ set_network(Devnet) -def test_vote_transaction(): +def test_vote_transaction(passphrase): """Test if a vote transaction gets built """ - vote = '+034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' + votes = ['034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192'] - transaction = Vote(vote) + transaction = Vote(votes, None) transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) transaction.set_nonce(1) - transaction.schnorr_sign('testing') + transaction.sign(passphrase) transaction_dict = transaction.to_dict() assert transaction_dict['nonce'] == 1 assert transaction_dict['signature'] - assert transaction_dict['asset']['votes'] + assert transaction_dict['asset']['votes'] == votes + assert transaction_dict['asset']['unvotes'] == [] assert transaction_dict['type'] is TRANSACTION_VOTE assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 100000000 + assert transaction_dict['expiration'] == 0 + assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid +def test_unvote_transaction(passphrase): + """Test if a vote transaction gets built + """ + unvotes = ['02dfc9a0684fe0744101b2398e9c86a81b5e46aceffff994ef9189083f01d0bebb'] + + transaction = Vote(None, unvotes) + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.set_nonce(1) + transaction.sign(passphrase) + transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 + assert transaction_dict['signature'] + assert transaction_dict['asset']['votes'] == [] + assert transaction_dict['asset']['unvotes'] == unvotes + assert transaction_dict['type'] is TRANSACTION_VOTE + assert transaction_dict['typeGroup'] == 1 + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['fee'] == 100000000 + assert transaction_dict['expiration'] == 0 + assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid + +def test_vote_swap_transaction(passphrase): + """Test if a vote transaction gets built + """ + votes = ['034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192'] + unvotes = ['02dfc9a0684fe0744101b2398e9c86a81b5e46aceffff994ef9189083f01d0bebb'] + + transaction = Vote(votes, unvotes) + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.set_nonce(1) + transaction.sign(passphrase) + transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 + assert transaction_dict['signature'] + assert transaction_dict['asset']['votes'] == votes + assert transaction_dict['asset']['unvotes'] == unvotes + assert transaction_dict['type'] is TRANSACTION_VOTE + assert transaction_dict['typeGroup'] == 1 + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['fee'] == 100000000 + assert transaction_dict['expiration'] == 0 + assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid -def test_vote_transaction_custom_fee(): +def test_vote_transaction_custom_fee(passphrase): """Test if a vote transaction gets built with a custom fee """ - vote = '+034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' + votes = ['034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192'] - transaction = Vote(vote, 5) + transaction = Vote(votes, None, 5) transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) transaction.set_nonce(1) - transaction.schnorr_sign('testing') + transaction.sign(passphrase) transaction_dict = transaction.to_dict() assert transaction_dict['nonce'] == 1 assert transaction_dict['signature'] - assert transaction_dict['asset']['votes'] + assert transaction_dict['asset']['votes'] == votes + assert transaction_dict['asset']['unvotes'] == [] assert transaction_dict['type'] is TRANSACTION_VOTE assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 5 + assert transaction_dict['expiration'] == 0 + assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/deserializers/test_delegate_registration.py b/tests/transactions/deserializers/test_delegate_registration.py index 66cad428..a521c0d5 100644 --- a/tests/transactions/deserializers/test_delegate_registration.py +++ b/tests/transactions/deserializers/test_delegate_registration.py @@ -1,22 +1,22 @@ from crypto.transactions.deserializer import Deserializer -def test_delegate_registration_deserializer(): - serialized = 'ff02170100000002000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19200f90295000000000009626f6c646e696e6a61eaf4b4dfd7903c32cf6c145ddf0744e86536719f5790b4286b08f1a10f0ad183bc601efc8a49a2a7b41758601a1793693afa1781cf0a63a8f72b08d5a1aaba1e' # noqa +def test_delegate_registration_deserializer(transaction_type_2): + serialized = transaction_type_2['serialized'] deserializer = Deserializer(serialized) actual = deserializer.deserialize() - assert actual.version == 2 - assert actual.network == 23 - assert actual.typeGroup == 1 - assert actual.type == 2 - assert actual.nonce == 1 - assert actual.senderPublicKey == '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' - assert actual.fee == 2500000000 - assert actual.asset['delegate'] == {'username': 'boldninja'} - assert actual.signature == 'eaf4b4dfd7903c32cf6c145ddf0744e86536719f5790b4286b08f1a10f0ad183bc601efc8a49a2a7b41758601a1793693afa1781cf0a63a8f72b08d5a1aaba1e' - assert actual.amount == 0 - assert actual.id == 'cfd113d8cd9fd46b07030c14fac38c1d3fc0eca991e999eab9d0152ea96ab0dc' + assert actual.version == transaction_type_2['version'] + assert actual.network == transaction_type_2['network'] + assert actual.typeGroup == transaction_type_2['typeGroup'] + assert actual.type == transaction_type_2['type'] + assert actual.nonce == transaction_type_2['nonce'] + assert actual.senderPublicKey == transaction_type_2['senderPublicKey'] + assert actual.fee == transaction_type_2['fee'] + assert actual.asset == transaction_type_2['asset'] + assert actual.signature == transaction_type_2['signature'] + assert actual.amount == transaction_type_2['amount'] + assert actual.id == transaction_type_2['id'] actual.verify_schnorr() diff --git a/tests/transactions/deserializers/test_delegate_resignation.py b/tests/transactions/deserializers/test_delegate_resignation.py index a54fd3a8..48b41459 100644 --- a/tests/transactions/deserializers/test_delegate_resignation.py +++ b/tests/transactions/deserializers/test_delegate_resignation.py @@ -1,21 +1,21 @@ from crypto.transactions.deserializer import Deserializer -def test_delegate_resignation_deserializer(): - serialized = 'ff02170100000007000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19200f902950000000000bdc048ca7eb5688cc01921aecf5914118cfc78eacc23825efa6d75094a683127cc02512dc59e1e0631fa8956f482eabc54933d23011a8337ea9cab99abed504d' # noqa +def test_delegate_resignation_deserializer(transaction_type_7): + serialized = transaction_type_7['serialized'] deserializer = Deserializer(serialized) actual = deserializer.deserialize() - assert actual.version == 2 - assert actual.network == 23 - assert actual.typeGroup == 1 - assert actual.type == 7 - assert actual.nonce == 1 - assert actual.senderPublicKey == '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' - assert actual.fee == 2500000000 - assert actual.signature == 'bdc048ca7eb5688cc01921aecf5914118cfc78eacc23825efa6d75094a683127cc02512dc59e1e0631fa8956f482eabc54933d23011a8337ea9cab99abed504d' - assert actual.amount == 0 - assert actual.id == '707b4deb339e717dfef44c40db0692015ce9bbab015c007b016b8a46b341e859' + assert actual.version == transaction_type_7['version'] + assert actual.network == transaction_type_7['network'] + assert actual.typeGroup == transaction_type_7['typeGroup'] + assert actual.type == transaction_type_7['type'] + assert actual.nonce == transaction_type_7['nonce'] + assert actual.senderPublicKey == transaction_type_7['senderPublicKey'] + assert actual.fee == transaction_type_7['fee'] + assert actual.signature == transaction_type_7['signature'] + assert actual.amount == transaction_type_7['amount'] + assert actual.id == transaction_type_7['id'] actual.verify_schnorr() diff --git a/tests/transactions/deserializers/test_htlc_claim.py b/tests/transactions/deserializers/test_htlc_claim.py deleted file mode 100644 index ca0ad97f..00000000 --- a/tests/transactions/deserializers/test_htlc_claim.py +++ /dev/null @@ -1,23 +0,0 @@ -from crypto.transactions.deserializer import Deserializer - - -def test_htlc_claim_deserializer(): - serialized = 'ff021e0100000009000100000000000000037fde73baaa48eb75c013fe9ff52a74a096d48b9978351bdcb5b72331ca37487c000000000000000000943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb46434323233626639336532303235303561366135303134323161383864396661dcd867411d20c7aa891e44cd92e916ea1d1e64ef1518dfcdfa227e4415d846a66c60718dc9d4bfc354afa69c2f8fa6e68f57e6eaf53c51b7a209ead5702ffd71' # noqa - - deserializer = Deserializer(serialized) - actual = deserializer.deserialize() - - assert actual.version == 2 - assert actual.network == 30 - assert actual.typeGroup == 1 - assert actual.type == 9 - assert actual.nonce == 1 - assert actual.senderPublicKey == '037fde73baaa48eb75c013fe9ff52a74a096d48b9978351bdcb5b72331ca37487c' - assert actual.fee == 0 - assert actual.signature == 'dcd867411d20c7aa891e44cd92e916ea1d1e64ef1518dfcdfa227e4415d846a66c60718dc9d4bfc354afa69c2f8fa6e68f57e6eaf53c51b7a209ead5702ffd71' - assert actual.amount == 0 - assert actual.id == 'aad3fdb321e2543af1dd9d2d4d155473a8b49cacbc116fa1f3f1b95154b336d5' - assert actual.asset['claim']['lockTransactionId'] == '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' # noqa - assert actual.asset['claim']['unlockSecret'] == '6434323233626639336532303235303561366135303134323161383864396661' # noqa - - actual.verify_schnorr() diff --git a/tests/transactions/deserializers/test_htlc_lock.py b/tests/transactions/deserializers/test_htlc_lock.py deleted file mode 100644 index 1304748b..00000000 --- a/tests/transactions/deserializers/test_htlc_lock.py +++ /dev/null @@ -1,25 +0,0 @@ -from crypto.transactions.deserializer import Deserializer - - -def test_htlc_lock_deserializer(): - serialized = 'ff02170100000008000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19280969800000000000000c2eb0b000000000f128d401958b1b30ad0d10406f47f9489321017b4614e6cb993fc63913c545401ce07c95d170995750207ecaf0ccf251c1265b92ad84f5536627fe939b22a1da166b6ea58e3964651236fb4e0739f9716dedf92986f37df71ea7993e9a97b4a1686c0ad08028dcae08b7cb4a54b8a4db57e72b839a611e86358' # noqa - - deserializer = Deserializer(serialized) - actual = deserializer.deserialize() - - assert actual.version == 2 - assert actual.network == 23 - assert actual.typeGroup == 1 - assert actual.type == 8 - assert actual.nonce == 1 - assert actual.senderPublicKey == '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' - assert actual.fee == 10000000 - assert actual.signature == '7fe939b22a1da166b6ea58e3964651236fb4e0739f9716dedf92986f37df71ea7993e9a97b4a1686c0ad08028dcae08b7cb4a54b8a4db57e72b839a611e86358' - assert actual.amount == 200000000 - assert actual.id == 'e1b34afa54bbf34de5c00716b92246c5248c2135221ece169db877ca60a14007' - assert actual.recipientId == 'AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC' - assert actual.asset['lock']['secretHash'] == '0f128d401958b1b30ad0d10406f47f9489321017b4614e6cb993fc63913c5454' # noqa - assert actual.asset['lock']['expiration']['type'] == 1 # noqa - assert actual.asset['lock']['expiration']['value'] == 1573455822 # noqa - - actual.verify_schnorr() diff --git a/tests/transactions/deserializers/test_htlc_refund.py b/tests/transactions/deserializers/test_htlc_refund.py deleted file mode 100644 index f04868d5..00000000 --- a/tests/transactions/deserializers/test_htlc_refund.py +++ /dev/null @@ -1,22 +0,0 @@ -from crypto.transactions.deserializer import Deserializer - - -def test_htlc_refund_deserializer(): - serialized = 'ff0217010000000a000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192000000000000000000943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb416d9ef1dceb0cbb105a45af6bdde9439055f07197643f9e2837312463330fd02ec7b13d1242becfe333c1b8ab2ea91c0c8240390d86f0fb0f6cdc22ec6ac64f1' # noqa - - deserializer = Deserializer(serialized) - actual = deserializer.deserialize() - - assert actual.version == 2 - assert actual.network == 23 - assert actual.typeGroup == 1 - assert actual.type == 10 - assert actual.nonce == 1 - assert actual.senderPublicKey == '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' - assert actual.fee == 0 - assert actual.signature == '16d9ef1dceb0cbb105a45af6bdde9439055f07197643f9e2837312463330fd02ec7b13d1242becfe333c1b8ab2ea91c0c8240390d86f0fb0f6cdc22ec6ac64f1' - assert actual.amount == 0 - assert actual.id == '9356aa730990a2ea8e9871ffa65800f34ef1a4bec3215d89c950e72d82a34e91' - assert actual.asset['refund']['lockTransactionId'] == '943c220691e711c39c79d437ce185748a0018940e1a4144293af9d05627d2eb4' # noqa - - actual.verify_schnorr() diff --git a/tests/transactions/deserializers/test_ipfs.py b/tests/transactions/deserializers/test_ipfs.py deleted file mode 100644 index 966a1f3c..00000000 --- a/tests/transactions/deserializers/test_ipfs.py +++ /dev/null @@ -1,22 +0,0 @@ -from crypto.transactions.deserializer import Deserializer - - -def test_ipfs_deserializer(): - serialized = 'ff02170100000005000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed1920065cd1d000000000012202853f0f11ab91d73b73a2a86606103f45dd469ad2e89ec6f9a25febe8758d3fe0b6e81b123de99e953d3073a8760d3213ab5f5cf512e65a2dd73aebb410966d8fbc59e775deb4f23c51be0847402b5e1d4ee68732b3e6d8e8914d259d7e373eb' # noqa - - deserializer = Deserializer(serialized) - actual = deserializer.deserialize() - - assert actual.version == 2 - assert actual.network == 23 - assert actual.typeGroup == 1 - assert actual.type == 5 - assert actual.nonce == 1 - assert actual.senderPublicKey == '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' - assert actual.fee == 500000000 - assert actual.signature == '0b6e81b123de99e953d3073a8760d3213ab5f5cf512e65a2dd73aebb410966d8fbc59e775deb4f23c51be0847402b5e1d4ee68732b3e6d8e8914d259d7e373eb' - assert actual.amount == 0 - assert actual.id == '818228ce634b46c488f3b2df8fd02bd50331ebdedb44df5b9b11b97b01e9fb36' - assert actual.asset['ipfs'] == 'QmR45FmbVVrixReBwJkhEKde2qwHYaQzGxu4ZoDeswuF9w' # noqa - - actual.verify_schnorr() diff --git a/tests/transactions/deserializers/test_multi_payment.py b/tests/transactions/deserializers/test_multi_payment.py index 0ed714f3..e9fd2e63 100644 --- a/tests/transactions/deserializers/test_multi_payment.py +++ b/tests/transactions/deserializers/test_multi_payment.py @@ -2,24 +2,24 @@ def test_multi_payment_deserializer(): - serialized = 'ff02170100000006000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed1928096980000000000000200010000000000000017134b5be4b327ddf9c2bb47fec8a1a44189e90f74020000000000000017bfa6aec83cf1bd03a0cab9f35c85ff51a3e9f041672e89e66a9c5d7d95c21ccd07a89a111f02823146c06f14689d2cf1efd645fb648258fcf2280486d2cae19f391796d72145d2a8e6f261e887e34cd1998bdb65' # noqa + serialized = 'ff011e0100000006000500000000000000023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3809698000000000000020000e1f505000000006f0182a0cc707b055322ccf6d4cb6a5aff1aeb2200c2eb0b00000000b693449adda7efc015d87944eae8b7c37eb1690a6031b2c73de394a12b4988f43f0fe13cd7782469857914b92e6c05561ff6d7634783a89157e487dc38f104bf40fc279c38602dbfba0cc679dfe631ba4c77929c' # noqa deserializer = Deserializer(serialized) actual = deserializer.deserialize() - assert actual.version == 2 - assert actual.network == 23 + assert actual.version == 1 + assert actual.network == 30 assert actual.typeGroup == 1 assert actual.type == 6 - assert actual.nonce == 1 - assert actual.senderPublicKey == '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' + assert actual.nonce == 5 + assert actual.senderPublicKey == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' assert actual.fee == 10000000 - assert actual.signature == '672e89e66a9c5d7d95c21ccd07a89a111f02823146c06f14689d2cf1efd645fb648258fcf2280486d2cae19f391796d72145d2a8e6f261e887e34cd1998bdb65' assert actual.amount == 0 - assert actual.id == 'e8c7293d428048f8678dc6c88cb8b32bd49c8ae9b02018297c1889d9bd33ba8d' - assert actual.asset['payments'][0]['amount'] == 1 # noqa - assert actual.asset['payments'][0]['recipientId'] == 'AHXtmB84sTZ9Zd35h9Y1vfFvPE2Xzqj8ri' # noqa - assert actual.asset['payments'][1]['amount'] == 2 # noqa - assert actual.asset['payments'][1]['recipientId'] == 'AZFEPTWnn2Sn8wDZgCRF8ohwKkrmk2AZi1' # noqa + assert actual.asset['payments'][0]['amount'] == 100000000 # noqa + assert actual.asset['payments'][0]['recipientId'] == '0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22' # noqa + assert actual.asset['payments'][1]['amount'] == 200000000 # noqa + assert actual.asset['payments'][1]['recipientId'] == '0xb693449AdDa7EFc015D87944EAE8b7C37EB1690A' # noqa + assert actual.signature == '6031b2c73de394a12b4988f43f0fe13cd7782469857914b92e6c05561ff6d7634783a89157e487dc38f104bf40fc279c38602dbfba0cc679dfe631ba4c77929c' + assert actual.id == '46799c223ff8f19d00c3e3bd617c6692f3cab1aed33dbc99ad9706d22f709c5e' actual.verify_schnorr() diff --git a/tests/transactions/deserializers/test_multi_signature_registration.py b/tests/transactions/deserializers/test_multi_signature_registration.py index 7cae8f73..5488ed21 100644 --- a/tests/transactions/deserializers/test_multi_signature_registration.py +++ b/tests/transactions/deserializers/test_multi_signature_registration.py @@ -2,31 +2,31 @@ def test_multi_signature_registration_deserializer(): - serialized = 'ff021701000000040001000000000000000205d9bbe71c343ac9a6a83a4344fd404c3534fc7349827097d0835d160bc2b89600943577000000000002030205d9bbe71c343ac9a6a83a4344fd404c3534fc7349827097d0835d160bc2b89603df0a1eb42d99b5de395cead145ba1ec2ea837be308c7ce3a4e8018b7efc7fdb803860d76b1df09659ac282cea3da5bd84fc45729f348a4a8e5f802186be72dc17ff5e9859c955bf8917b308ea21c88daf58661686c2017e476dcf735ad7f00aebf8e6effda3fe99e5f33f6007db7db9c9155796d9b5d31c53bd6156364a6a765d00064900cb2cc3db6ca9c7e3bd363b322cdc4a39e051f655e9867935e1bb856b6dcce52845c031c690808f40340bc827bbaacd7b04bceff866cb0d386ab8471517401dd363ccc101a958bded1a5db1c08f13283fc7cee53da93dfe00785eb406512467ff8e445f8ad843744ac4179f30f942645dfd5bdf5f2bfc344ad02393053880a02d0012f035dc3fd54173c83d40217914653488fe9ce592dca34234163181d255281f2be7033725cfc4a6786509e7fabbaf0be8cf50882fc7b66fe94f259fd004e' # noqa + serialized = 'ff011e0100000004000500000000000000023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d30065cd1d00000000000203029fab3cb2f5e248ae7cbb4de646741da4d73c493b2a03ab5c71507fb2c0dcca9203629f9dbf7f1e91cefa845126189816ceae357bdd1f41bd14787318a7d5b55d48027941d2059f89a26d89e87d3385e261a0ede1234aaeaa487012b69d6b67962dc504aee2652b4a41350b9821c261bb0a3c3492146a6e0ebda4e28a185d6c2e9eb7702b4889e8b9b5fadd989e71dbb7527b3be071e15cf3a93165e8f1b8c7b16974008c7c7018082d9afddd70652f559377a941240cf797819fe02f657d4509c8bcb133156741163cfb8e9b4db6ef398bdd5f94a24e1f0134d04783391bc13370201b015306973b045d83191ce57c56f097b79c0771a94310ee67aed8b96e1bb7f497e451fe569c297a857f840f46b801534179e87a5e07015fb03727746be450e925d6021d03e19f1e39ac9b985b867e8362002df387fd778a682218bc3e6ce4b5c7f59eaa6f893ffb8deec2e4c9b28287417e2ab7b427356ca74d790f7fb63b6f8dcbd5' # noqa deserializer = Deserializer(serialized) actual = deserializer.deserialize() data = actual.to_dict() assert data['amount'] == 0 - assert data['nonce'] == 1 - assert data['version'] == 2 - assert data['network'] == 23 + assert data['nonce'] == 5 + assert data['version'] == 1 + assert data['network'] == 30 assert data['typeGroup'] == 1 - assert data['fee'] == 2000000000 - assert data['senderPublicKey'] == '0205d9bbe71c343ac9a6a83a4344fd404c3534fc7349827097d0835d160bc2b896' - assert data['signature'] == 'f5e9859c955bf8917b308ea21c88daf58661686c2017e476dcf735ad7f00aebf8e6effda3fe99e5f33f6007db7db9c9155796d9b5d31c53bd6156364a6a765d0' - assert data['id'] == 'c868aad20165a336c35e324378f0c12008d18af4c1025291efcb7539c7c917f0' + assert data['fee'] == 500000000 + assert data['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' + assert data['signature'] == '04aee2652b4a41350b9821c261bb0a3c3492146a6e0ebda4e28a185d6c2e9eb7702b4889e8b9b5fadd989e71dbb7527b3be071e15cf3a93165e8f1b8c7b16974' + assert data['id'] == '75affed80fcad541787fd673fafa6b90561feac2d3f4a2dae857641109c44787' assert data['asset']['multiSignature']['min'] == 2 assert data['asset']['multiSignature']['publicKeys'] == [ - '0205d9bbe71c343ac9a6a83a4344fd404c3534fc7349827097d0835d160bc2b896', - '03df0a1eb42d99b5de395cead145ba1ec2ea837be308c7ce3a4e8018b7efc7fdb8', - '03860d76b1df09659ac282cea3da5bd84fc45729f348a4a8e5f802186be72dc17f' + '029fab3cb2f5e248ae7cbb4de646741da4d73c493b2a03ab5c71507fb2c0dcca92', + '03629f9dbf7f1e91cefa845126189816ceae357bdd1f41bd14787318a7d5b55d48', + '027941d2059f89a26d89e87d3385e261a0ede1234aaeaa487012b69d6b67962dc5' ] assert data['signatures'] == [ - '0064900cb2cc3db6ca9c7e3bd363b322cdc4a39e051f655e9867935e1bb856b6dcce52845c031c690808f40340bc827bbaacd7b04bceff866cb0d386ab84715174', # noqa - '01dd363ccc101a958bded1a5db1c08f13283fc7cee53da93dfe00785eb406512467ff8e445f8ad843744ac4179f30f942645dfd5bdf5f2bfc344ad02393053880a', # noqa - '02d0012f035dc3fd54173c83d40217914653488fe9ce592dca34234163181d255281f2be7033725cfc4a6786509e7fabbaf0be8cf50882fc7b66fe94f259fd004e' # noqa + '008c7c7018082d9afddd70652f559377a941240cf797819fe02f657d4509c8bcb133156741163cfb8e9b4db6ef398bdd5f94a24e1f0134d04783391bc13370201b', # noqa + '015306973b045d83191ce57c56f097b79c0771a94310ee67aed8b96e1bb7f497e451fe569c297a857f840f46b801534179e87a5e07015fb03727746be450e925d6', # noqa + '021d03e19f1e39ac9b985b867e8362002df387fd778a682218bc3e6ce4b5c7f59eaa6f893ffb8deec2e4c9b28287417e2ab7b427356ca74d790f7fb63b6f8dcbd5' # noqa ] actual.verify_schnorr_multisig() diff --git a/tests/transactions/deserializers/test_second_signature_registration.py b/tests/transactions/deserializers/test_second_signature_registration.py deleted file mode 100644 index 8bf28211..00000000 --- a/tests/transactions/deserializers/test_second_signature_registration.py +++ /dev/null @@ -1,22 +0,0 @@ -from crypto.transactions.deserializer import Deserializer - - -def test_second_signature_registration_deserializer(): - serialized = 'ff02170100000001000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed1920065cd1d000000000003699e966b2525f9088a6941d8d94f7869964a000efe65783d78ac82e1199fe609f9a1e2244c8318e8be85482fc02659e5c1775d246d73d5d0699ae4a1d5e3a3e84f9dcf68ee015f943d2a82eb829f35abd7901279761d96f6b43431520e955c67' # noqa - - deserializer = Deserializer(serialized) - actual = deserializer.deserialize() - - assert actual.version == 2 - assert actual.network == 23 - assert actual.typeGroup == 1 - assert actual.type == 1 - assert actual.nonce == 1 - assert actual.senderPublicKey == '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' - assert actual.fee == 500000000 - assert actual.signature == 'f9a1e2244c8318e8be85482fc02659e5c1775d246d73d5d0699ae4a1d5e3a3e84f9dcf68ee015f943d2a82eb829f35abd7901279761d96f6b43431520e955c67' - assert actual.amount == 0 - assert actual.id == '173a3230159b45d772b2e0348f42af53913bf3e376397f29b8e0bda290badbe4' - assert actual.asset['signature']['publicKey'] == '03699e966b2525f9088a6941d8d94f7869964a000efe65783d78ac82e1199fe609' # noqa - - actual.verify_schnorr() diff --git a/tests/transactions/deserializers/test_transfer.py b/tests/transactions/deserializers/test_transfer.py index f3b9e9bc..dc60f11d 100644 --- a/tests/transactions/deserializers/test_transfer.py +++ b/tests/transactions/deserializers/test_transfer.py @@ -3,23 +3,23 @@ def test_transfer_deserializer(): - serialized = 'ff02170100000000000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19280969800000000000000c2eb0b0000000000000000170995750207ecaf0ccf251c1265b92ad84f553662136c29d921b58ae3194020b82e9808f9cd54f7178cb34678f570f28226b8e56ba0ad318297a3bacbb37ab22ddaa5dbf1901cda3ec2d2bca5ce98d6407839ab9b' # noqa + serialized = 'ff011e0100000000000300000000000000023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d38096980000000000000100000000000000000000006f0182a0cc707b055322ccf6d4cb6a5aff1aeb221c9e1c2493b1e597d682f31700989f76eec068375d4ce5e1721cc9265b66c9b551ea1afa28c4f443120e43cff5cc7f137c4572749b0467561038127b4eafded9' deserializer = Deserializer(serialized) actual = deserializer.deserialize() - assert actual.version == 2 - assert actual.network == 23 + assert actual.version == 1 + assert actual.network == 30 assert actual.typeGroup == 1 - assert actual.expiration == 0 assert actual.type == 0 - assert actual.amount == 200000000 + assert actual.nonce == 3 + assert actual.senderPublicKey == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' # noqa assert actual.fee == 10000000 - assert actual.nonce == 1 - assert actual.recipientId == 'AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC' - assert actual.senderPublicKey == '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' # noqa - assert actual.id == '129517023bd895b682bbb38b1d1f99e9222bd487899c843da22d8572b0fb52a8' - assert actual.signature == '136c29d921b58ae3194020b82e9808f9cd54f7178cb34678f570f28226b8e56ba0ad318297a3bacbb37ab22ddaa5dbf1901cda3ec2d2bca5ce98d6407839ab9b' + assert actual.amount == 1 + assert actual.expiration == 0 + assert actual.recipientId == '0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22' + assert actual.signature == '1c9e1c2493b1e597d682f31700989f76eec068375d4ce5e1721cc9265b66c9b551ea1afa28c4f443120e43cff5cc7f137c4572749b0467561038127b4eafded9' + assert actual.id == '8d0584d1bbc8fa7a6e4e7904b884f69399df56dcfe6458c2bedc283c55604def' actual.verify_schnorr() @@ -45,4 +45,4 @@ def test_transfer_second_signature_deserializer(): assert actual.signature == '136c29d921b58ae3194020b82e9808f9cd54f7178cb34678f570f28226b8e56ba0ad318297a3bacbb37ab22ddaa5dbf1901cda3ec2d2bca5ce98d6407839ab9b' assert actual.signSignature == '02dd94f611e300ad77147d808a34e942b379c5468760d8605adc0304400a2578a2039468b844f30ad1f0515f9cce33855791296117bfe8ef3caa664152644fd6' - actual.verify_schnorr() \ No newline at end of file + actual.verify_schnorr() diff --git a/tests/transactions/deserializers/test_vote.py b/tests/transactions/deserializers/test_vote.py index 14f8834c..3ed39887 100644 --- a/tests/transactions/deserializers/test_vote.py +++ b/tests/transactions/deserializers/test_vote.py @@ -1,23 +1,20 @@ from crypto.transactions.deserializer import Deserializer -def test_vote_deserializer(): - serialized = 'ff02170100000003000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19200e1f50500000000000101022cca9529ec97a772156c152a00aad155ee6708243e65c9d211a589cb5d43234d86007f8e6a982bc271ec063c20f158734f0bc1e23e0e1abf9edeaa208b4810fa1d466171bba79a5c00b0a4c698728f68aa0748d98613cac247c014ee84a6fc41' # noqa - - deserializer = Deserializer(serialized) +def test_vote_deserializer(transaction_type_3): + deserializer = Deserializer(transaction_type_3['serialized']) actual = deserializer.deserialize() - assert actual.version == 2 - assert actual.network == 23 - assert actual.typeGroup == 1 - assert actual.type == 3 - assert actual.amount == 0 - assert actual.fee == 100000000 - assert actual.nonce == 1 - assert actual.senderPublicKey == '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' # noqa - assert actual.id == '2c5d71028607674411c8e37e316a015eccbeb9ba486fddfbd393dc421540a90a' - assert actual.signature == '86007f8e6a982bc271ec063c20f158734f0bc1e23e0e1abf9edeaa208b4810fa1d466171bba79a5c00b0a4c698728f68aa0748d98613cac247c014ee84a6fc41' + assert actual.version == transaction_type_3['version'] + assert actual.network == transaction_type_3['network'] + assert actual.typeGroup == transaction_type_3['typeGroup'] + assert actual.type == transaction_type_3['type'] + assert actual.amount == transaction_type_3['amount'] + assert actual.fee == transaction_type_3['fee'] + assert actual.nonce == transaction_type_3['nonce'] + assert actual.senderPublicKey == transaction_type_3['senderPublicKey'] # noqa + assert actual.signature == transaction_type_3['signature'] - assert actual.asset['votes'] == ['+022cca9529ec97a772156c152a00aad155ee6708243e65c9d211a589cb5d43234d'] # noqa + assert actual.asset == transaction_type_3['asset'] # noqa actual.verify_schnorr() diff --git a/tests/transactions/serializers/test_htlc_claim.py b/tests/transactions/serializers/test_htlc_claim.py deleted file mode 100644 index 5592096a..00000000 --- a/tests/transactions/serializers/test_htlc_claim.py +++ /dev/null @@ -1,6 +0,0 @@ -from crypto.transactions.serializer import Serializer - - -def test_serializer(transaction_type_9): - result = Serializer(transaction_type_9).serialize(False, True) - assert result == transaction_type_9['serialized'] diff --git a/tests/transactions/serializers/test_htlc_lock.py b/tests/transactions/serializers/test_htlc_lock.py deleted file mode 100644 index f0e17b9b..00000000 --- a/tests/transactions/serializers/test_htlc_lock.py +++ /dev/null @@ -1,6 +0,0 @@ -from crypto.transactions.serializer import Serializer - - -def test_serializer(transaction_type_8): - result = Serializer(transaction_type_8).serialize(False, True) - assert result == transaction_type_8['serialized'] diff --git a/tests/transactions/serializers/test_htlc_refund.py b/tests/transactions/serializers/test_htlc_refund.py deleted file mode 100644 index e658a9d8..00000000 --- a/tests/transactions/serializers/test_htlc_refund.py +++ /dev/null @@ -1,6 +0,0 @@ -from crypto.transactions.serializer import Serializer - - -def test_serializer(transaction_type_10): - result = Serializer(transaction_type_10).serialize(False, True) - assert result == transaction_type_10['serialized'] diff --git a/tests/transactions/serializers/test_ipfs.py b/tests/transactions/serializers/test_ipfs.py deleted file mode 100644 index 38dab861..00000000 --- a/tests/transactions/serializers/test_ipfs.py +++ /dev/null @@ -1,6 +0,0 @@ -from crypto.transactions.serializer import Serializer - - -def test_serializer(transaction_type_5): - result = Serializer(transaction_type_5).serialize(False, True) - assert result == transaction_type_5['serialized'] diff --git a/tests/transactions/serializers/test_second_signature_registration.py b/tests/transactions/serializers/test_second_signature_registration.py deleted file mode 100644 index f494e737..00000000 --- a/tests/transactions/serializers/test_second_signature_registration.py +++ /dev/null @@ -1,6 +0,0 @@ -from crypto.transactions.serializer import Serializer - - -def test_serializer(transaction_type_1): - result = Serializer(transaction_type_1).serialize(False, False) - assert result == transaction_type_1['serialized'] diff --git a/tests/transactions/test_signature.py b/tests/transactions/test_signature.py new file mode 100644 index 00000000..c797dd87 --- /dev/null +++ b/tests/transactions/test_signature.py @@ -0,0 +1,76 @@ +from crypto.transactions.builder.transfer import Transfer +from crypto.transactions.signature import Signature + +def test_should_sign_and_verify(): + isVerified = Signature.verify( + bytes.fromhex( + Signature.sign( + bytes.fromhex("814857ce48e291893feab95df02e1dbf7ad3994ba46f247f77e4eefd5d8734a2"), + bytes.fromhex("814857ce48e291893feab95df02e1dbf7ad3994ba46f247f77e4eefd5d8734a2"), + ), + ), + bytes.fromhex('814857ce48e291893feab95df02e1dbf7ad3994ba46f247f77e4eefd5d8734a2'), + bytes.fromhex('e84093c072af70004a38dd95e34def119d2348d5261228175d032e5f2070e19f'), + ) + + assert isVerified == True + +def test_should_sign_and_verify_with_ecdsa_key_with_prefix(): + isVerified = Signature.verify( + bytes.fromhex( + Signature.sign( + bytes.fromhex("6616cd071ecbfe525be817d29eb1ccd6d93af0a9207356b38dcd73fcc84ff297"), + bytes.fromhex("6acdb0def03305800b75e9c020e0a9b0504a543f56253f694ff35f1dce8a193f"), + ), + ), + bytes.fromhex('6616cd071ecbfe525be817d29eb1ccd6d93af0a9207356b38dcd73fcc84ff297'), + bytes.fromhex('025f7362e1baff21b8441c20b3f54583eb2f5925afada140b5a95880a2224a9d48'), + ) + + assert isVerified == True + +def test_should_sign_and_verify_with_ecdsa_key_without_prefix(): + isVerified = Signature.verify( + bytes.fromhex( + Signature.sign( + bytes.fromhex("6616cd071ecbfe525be817d29eb1ccd6d93af0a9207356b38dcd73fcc84ff297"), + bytes.fromhex("6acdb0def03305800b75e9c020e0a9b0504a543f56253f694ff35f1dce8a193f"), + ), + ), + bytes.fromhex('6616cd071ecbfe525be817d29eb1ccd6d93af0a9207356b38dcd73fcc84ff297'), + bytes.fromhex('5f7362e1baff21b8441c20b3f54583eb2f5925afada140b5a95880a2224a9d48'), + ) + + assert isVerified == True + +def test_should_not_sign_and_verify_with_wrong_ecdsa_key(): + isVerified = Signature.verify( + bytes.fromhex( + Signature.sign( + bytes.fromhex("6616cd071ecbfe525be817d29eb1ccd6d93af0a9207356b38dcd73fcc84ff297"), + bytes.fromhex("6acdb0def03305800b75e9c020e0a9b0504a543f56253f694ff35f1dce8a193f"), + ), + ), + bytes.fromhex('6616cd071ecbfe525be817d29eb1ccd6d93af0a9207356b38dcd73fcc84ff297'), + bytes.fromhex('02d076ae01c84ad5e72eb2aae9a3c60784b08cc1f0e8624fe3cc51648a163ee120'), + ) + + assert isVerified == False + +def test_transfer_transaction(): + transaction = Transfer( + recipientId='0xb693449AdDa7EFc015D87944EAE8b7C37EB1690A', + amount=1, + fee=10000000 + ) + transaction.set_type_group(1) + transaction.set_nonce(1) + transaction.sign('my super secret passphrase') + + isVerified = Signature.verify( + bytes.fromhex('d07662b9a917f158a7ad8431aff8b31a70fe3a7af562db3ead41e7b5e00b7d9a27b3faf26699096baddbb45b2f1c3b0aa180301b001e19b8eaeb4e13055eaa0c'), + transaction.transaction.to_bytes(), + bytes.fromhex('023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3'), + ) + + assert isVerified == True diff --git a/tests/utils/test_message.py b/tests/utils/test_message.py index 8f02d5e2..84b6dee5 100644 --- a/tests/utils/test_message.py +++ b/tests/utils/test_message.py @@ -2,31 +2,47 @@ from crypto.utils.message import Message +def test_verify_with_publicKey(message): + result = Message( + message=message['message'], + signature=message['signature'], + public_key=message['publicKey'], + ) -def test_signing(message): - result = Message.sign(message['camelCase_pk']['message'], message['passphrase']) - assert result.to_dict() == message['camelCase_pk'] - + isVerified = result.verify() -def test_verify_with_publickey(message): - result = Message(**message['snake_case_pk']) - assert result.verify() is True + assert isVerified is True +def test_message_sign_verification(message): + message: Message = Message.sign(message['message'], message['passphrase']) -def test_verify_with_publicKey(message): - result = Message(**message['camelCase_pk']) - assert result.verify() is True + isVerified = message.verify() + assert isVerified is True def test_to_dict(message): - result = Message(**message['camelCase_pk']) - assert result.to_dict() == message['camelCase_pk'] + result = Message( + message=message['message'], + signature=message['signature'], + public_key=message['publicKey'], + ) + data = result.to_dict() + + assert data['signature'] == message['signature'] + assert data['public_key'] == message['publicKey'] + assert data['message'] == message['message'] def test_to_json(message): - result = Message(**message['camelCase_pk']) + result = Message( + message=message['message'], + signature=message['signature'], + public_key=message['publicKey'], + ) + json_data = result.to_json() data = json.loads(json_data) - assert data['signature'] == message['camelCase_pk']['signature'] - assert data['publicKey'] == message['camelCase_pk']['publicKey'] - assert data['message'] == message['camelCase_pk']['message'] + + assert data['signature'] == message['signature'] + assert data['public_key'] == message['publicKey'] + assert data['message'] == message['message'] From 5bbbbde4a649d8d707a57bc35167398668dfe85a Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Mon, 29 Jul 2024 14:15:25 +0100 Subject: [PATCH 51/68] refactor: remove old transaction types (#131) --- crypto/constants.py | 23 ---------- crypto/transactions/builder/base.py | 10 ++-- crypto/transactions/builder/htlc_claim.py | 31 ------------- crypto/transactions/builder/htlc_lock.py | 46 ------------------- crypto/transactions/builder/htlc_refund.py | 28 ----------- crypto/transactions/builder/ipfs.py | 25 ---------- .../builder/second_signature_registration.py | 23 ---------- .../transactions/deserializers/htlc_claim.py | 24 ---------- .../transactions/deserializers/htlc_lock.py | 41 ----------------- .../transactions/deserializers/htlc_refund.py | 22 --------- crypto/transactions/deserializers/ipfs.py | 38 --------------- .../second_signature_registration.py | 21 --------- crypto/transactions/serializers/htlc_claim.py | 18 -------- crypto/transactions/serializers/htlc_lock.py | 24 ---------- .../transactions/serializers/htlc_refund.py | 12 ----- crypto/transactions/serializers/ipfs.py | 19 -------- .../second_signature_registration.py | 12 ----- crypto/transactions/transaction.py | 5 +- .../test_multi_signature_registration.py | 2 +- tests/transactions/builder/test_transfer.py | 2 +- .../deserializers/test_transfer.py | 24 ---------- 21 files changed, 6 insertions(+), 444 deletions(-) delete mode 100644 crypto/transactions/builder/htlc_claim.py delete mode 100644 crypto/transactions/builder/htlc_lock.py delete mode 100644 crypto/transactions/builder/htlc_refund.py delete mode 100644 crypto/transactions/builder/ipfs.py delete mode 100644 crypto/transactions/builder/second_signature_registration.py delete mode 100644 crypto/transactions/deserializers/htlc_claim.py delete mode 100644 crypto/transactions/deserializers/htlc_lock.py delete mode 100644 crypto/transactions/deserializers/htlc_refund.py delete mode 100644 crypto/transactions/deserializers/ipfs.py delete mode 100644 crypto/transactions/deserializers/second_signature_registration.py delete mode 100644 crypto/transactions/serializers/htlc_claim.py delete mode 100644 crypto/transactions/serializers/htlc_lock.py delete mode 100644 crypto/transactions/serializers/htlc_refund.py delete mode 100644 crypto/transactions/serializers/ipfs.py delete mode 100644 crypto/transactions/serializers/second_signature_registration.py diff --git a/crypto/constants.py b/crypto/constants.py index e5cf084f..6a4d6af7 100644 --- a/crypto/constants.py +++ b/crypto/constants.py @@ -1,43 +1,28 @@ from enum import Enum TRANSACTION_TRANSFER = 0 -TRANSACTION_SECOND_SIGNATURE_REGISTRATION = 1 TRANSACTION_DELEGATE_REGISTRATION = 2 TRANSACTION_VOTE = 3 TRANSACTION_MULTI_SIGNATURE_REGISTRATION = 4 -TRANSACTION_IPFS = 5 TRANSACTION_MULTI_PAYMENT = 6 TRANSACTION_DELEGATE_RESIGNATION = 7 -TRANSACTION_HTLC_LOCK = 8 -TRANSACTION_HTLC_CLAIM = 9 -TRANSACTION_HTLC_REFUND = 10 TRANSACTION_TYPES = { TRANSACTION_TRANSFER: 'transfer', - TRANSACTION_SECOND_SIGNATURE_REGISTRATION: 'second_signature_registration', TRANSACTION_DELEGATE_REGISTRATION: 'delegate_registration', TRANSACTION_VOTE: 'vote', TRANSACTION_MULTI_SIGNATURE_REGISTRATION: 'multi_signature_registration', - TRANSACTION_IPFS: 'ipfs', TRANSACTION_MULTI_PAYMENT: 'multi_payment', TRANSACTION_DELEGATE_RESIGNATION: 'delegate_resignation', - TRANSACTION_HTLC_LOCK: 'htlc_lock', - TRANSACTION_HTLC_CLAIM: 'htlc_claim', - TRANSACTION_HTLC_REFUND: 'htlc_refund', } TRANSACTION_FEES = { TRANSACTION_TRANSFER: 10000000, - TRANSACTION_SECOND_SIGNATURE_REGISTRATION: 500000000, TRANSACTION_DELEGATE_REGISTRATION: 2500000000, TRANSACTION_VOTE: 100000000, TRANSACTION_MULTI_SIGNATURE_REGISTRATION: 500000000, - TRANSACTION_IPFS: 500000000, TRANSACTION_MULTI_PAYMENT: 10000000, TRANSACTION_DELEGATE_RESIGNATION: 2500000000, - TRANSACTION_HTLC_LOCK: 10000000, - TRANSACTION_HTLC_CLAIM: 0, - TRANSACTION_HTLC_REFUND: 0, } @@ -48,11 +33,3 @@ def __int__(self): TEST = 0 CORE = 1 RESERVED = 1000 # Everything above is available to anyone - - -class HTLC_LOCK_EXPIRATION_TYPE(Enum): - def __str__(self): - return int(self.value) - - EPOCH_TIMESTAMP = 1 - BLOCK_HEIGHT = 2 diff --git a/crypto/transactions/builder/base.py b/crypto/transactions/builder/base.py index efa68ce6..25592cfb 100644 --- a/crypto/transactions/builder/base.py +++ b/crypto/transactions/builder/base.py @@ -1,7 +1,7 @@ from binascii import hexlify, unhexlify from crypto.configuration.fee import get_fee -from crypto.constants import HTLC_LOCK_EXPIRATION_TYPE, TRANSACTION_TYPE_GROUP +from crypto.constants import TRANSACTION_TYPE_GROUP from crypto.identity.private_key import PrivateKey from crypto.identity.public_key import PublicKey from crypto.transactions.signature import Signature @@ -82,12 +82,8 @@ def set_amount(self, amount): def set_sender_public_key(self, public_key): self.transaction.senderPublicKey = public_key - def set_expiration(self, expiration): - if type(expiration) == int: - self.transaction.expiration = expiration - else: - types = {HTLC_LOCK_EXPIRATION_TYPE.EPOCH_TIMESTAMP: 1, HTLC_LOCK_EXPIRATION_TYPE.BLOCK_HEIGHT: 2} - self.transaction.expiration = types[expiration] + def set_expiration(self, expiration: int): + self.transaction.expiration = expiration def set_type_group(self, type_group): if type(type_group) == int: diff --git a/crypto/transactions/builder/htlc_claim.py b/crypto/transactions/builder/htlc_claim.py deleted file mode 100644 index 9f153982..00000000 --- a/crypto/transactions/builder/htlc_claim.py +++ /dev/null @@ -1,31 +0,0 @@ -from crypto.constants import TRANSACTION_HTLC_CLAIM, TRANSACTION_TYPE_GROUP -from crypto.exceptions import ArkInvalidTransaction -from crypto.transactions.builder.base import BaseTransactionBuilder - - -class HtlcClaim(BaseTransactionBuilder): - - transaction_type = TRANSACTION_HTLC_CLAIM - - def __init__(self, lock_transaction_id, unlock_secret, fee=None): - """Create a timelock transaction - - Args: - lock_transaction_id (str): HTLC lock transaction ID we wish to claim - unlock_secret (str): unlock secret required to claim the transaction, must be 64 chars long - fee (int, optional): fee used for the transaction (default is already set) - """ - super().__init__() - - self.transaction.typeGroup = self.get_type_group() - - self.transaction.asset['claim'] = { - 'lockTransactionId': lock_transaction_id, - 'unlockSecret': unlock_secret - } - - if fee: - self.transaction.fee = fee - - def get_type_group(self): - return TRANSACTION_TYPE_GROUP.CORE.value diff --git a/crypto/transactions/builder/htlc_lock.py b/crypto/transactions/builder/htlc_lock.py deleted file mode 100644 index 0ee2c7a5..00000000 --- a/crypto/transactions/builder/htlc_lock.py +++ /dev/null @@ -1,46 +0,0 @@ -from crypto.constants import TRANSACTION_HTLC_LOCK, TRANSACTION_TYPE_GROUP -from crypto.transactions.builder.base import BaseTransactionBuilder - - -class HtlcLock(BaseTransactionBuilder): - - transaction_type = TRANSACTION_HTLC_LOCK - - def __init__(self, recipient_id, amount, secret_hash, expiration_type, expiration_value, vendorField=None, fee=None): - """Create a timelock transaction - - Args: - recipient_id (str): recipient identifier - amount (int): amount of coins you want to transfer - secret_hash (str): a hash of the secret. The SAME hash must be used in the corresponding “claim” transaction - expiration_type (int): type of the expiration. Either block height or network epoch timestamp based - expiration_value (int): Expiration of transaction in seconds or height depending on expiration_type - vendorField (str): value for the vendor field aka smartbridge - fee (int, optional): fee used for the transaction (default is already set) - """ - super().__init__() - - self.transaction.recipientId = recipient_id - - if type(amount) == int and amount > 0: - self.transaction.amount = amount - else: - raise ValueError('Amount is not valid') - - self.transaction.typeGroup = self.get_type_group() - - self.transaction.asset['lock'] = { - 'secretHash': secret_hash, - 'expiration': { - 'type': expiration_type, - 'value': expiration_value - } - } - - self.transaction.vendorField = vendorField.encode() if vendorField else None - - if fee: - self.transaction.fee = fee - - def get_type_group(self): - return TRANSACTION_TYPE_GROUP.CORE.value diff --git a/crypto/transactions/builder/htlc_refund.py b/crypto/transactions/builder/htlc_refund.py deleted file mode 100644 index c18e1b5d..00000000 --- a/crypto/transactions/builder/htlc_refund.py +++ /dev/null @@ -1,28 +0,0 @@ -from crypto.constants import TRANSACTION_HTLC_REFUND, TRANSACTION_TYPE_GROUP -from crypto.transactions.builder.base import BaseTransactionBuilder - - -class HtlcRefund(BaseTransactionBuilder): - - transaction_type = TRANSACTION_HTLC_REFUND - - def __init__(self, lock_transaction_id, fee=None): - """Create a timelock transaction - - Args: - lock_transaction_id (str) : HTLC lock transaction ID we wish to claim - fee (int, optional): fee used for the transaction (default is already set) - """ - super().__init__() - - self.transaction.typeGroup = self.get_type_group() - - self.transaction.asset['refund'] = { - 'lockTransactionId': lock_transaction_id, - } - - if fee: - self.transaction.fee = fee - - def get_type_group(self): - return TRANSACTION_TYPE_GROUP.CORE.value diff --git a/crypto/transactions/builder/ipfs.py b/crypto/transactions/builder/ipfs.py deleted file mode 100644 index e1adf2c4..00000000 --- a/crypto/transactions/builder/ipfs.py +++ /dev/null @@ -1,25 +0,0 @@ -from crypto.constants import TRANSACTION_IPFS, TRANSACTION_TYPE_GROUP -from crypto.transactions.builder.base import BaseTransactionBuilder - - -class IPFS(BaseTransactionBuilder): - - transaction_type = TRANSACTION_IPFS - - def __init__(self, ipfs_id, fee=None): - """Create an ipfs transaction - - Args: - ipfs_id (str): ipfs transaction identifier - fee (int, optional): fee used for the transaction (default is already set) - """ - super().__init__() - - self.transaction.asset['ipfs'] = ipfs_id - self.transaction.typeGroup = self.get_type_group() - - if fee: - self.transaction.fee = fee - - def get_type_group(self): - return TRANSACTION_TYPE_GROUP.CORE.value diff --git a/crypto/transactions/builder/second_signature_registration.py b/crypto/transactions/builder/second_signature_registration.py deleted file mode 100644 index d35c6f67..00000000 --- a/crypto/transactions/builder/second_signature_registration.py +++ /dev/null @@ -1,23 +0,0 @@ -from crypto.constants import TRANSACTION_SECOND_SIGNATURE_REGISTRATION -from crypto.identity.public_key import PublicKey -from crypto.transactions.builder.base import BaseTransactionBuilder - - -class SecondSignatureRegistration(BaseTransactionBuilder): - - transaction_type = TRANSACTION_SECOND_SIGNATURE_REGISTRATION - - def __init__(self, second_passphrase, fee=None): - """Create a second signature registration transaction - - Args: - second_passphrase (str): used for signing a transaction the 2nd time - fee (int, optional): fee used for the transaction (default is already set) - """ - super().__init__() - - public_key = PublicKey.from_passphrase(second_passphrase) - self.transaction.asset['signature'] = {'publicKey': public_key} - - if fee: - self.transaction.fee = fee diff --git a/crypto/transactions/deserializers/htlc_claim.py b/crypto/transactions/deserializers/htlc_claim.py deleted file mode 100644 index 13243520..00000000 --- a/crypto/transactions/deserializers/htlc_claim.py +++ /dev/null @@ -1,24 +0,0 @@ -from binascii import hexlify - -from crypto.transactions.deserializers.base import BaseDeserializer - - -class HtlcClaimDeserializer(BaseDeserializer): - - def deserialize(self): - starting_position = int(self.asset_offset / 2) - lock_transaction_id = hexlify(self.serialized[starting_position:starting_position + 32]) - - unlock_secret = hexlify(self.serialized[starting_position + 32:starting_position + 64]) - - self.transaction.asset['claim'] = { - 'lockTransactionId': lock_transaction_id.decode(), - 'unlockSecret': unlock_secret.decode() - } - - self.transaction.parse_signatures( - hexlify(self.serialized).decode(), - self.asset_offset + (64 * 2) - ) - - return self.transaction diff --git a/crypto/transactions/deserializers/htlc_lock.py b/crypto/transactions/deserializers/htlc_lock.py deleted file mode 100644 index dfbd52bc..00000000 --- a/crypto/transactions/deserializers/htlc_lock.py +++ /dev/null @@ -1,41 +0,0 @@ -from binascii import hexlify, unhexlify - -from base58 import b58encode_check - -from binary.unsigned_integer.reader import read_bit8, read_bit32, read_bit64 - -from crypto.transactions.deserializers.base import BaseDeserializer - - -class HtlcLockDeserializer(BaseDeserializer): - - def deserialize(self): - starting_position = int(self.asset_offset / 2) - - self.transaction.amount = read_bit64(self.serialized, offset=starting_position) - - secret_hash = hexlify(self.serialized)[(starting_position + 8) * 2:(starting_position + 8 + 32) * 2] - - expiration_type = read_bit8(self.serialized, offset=99) - - expiration_value = read_bit32(self.serialized, offset=100) - - recipient_start_index = (starting_position + 45) * 2 - recipientId = hexlify(self.serialized)[recipient_start_index:recipient_start_index + 42] - self.transaction.recipientId = b58encode_check(unhexlify(recipientId)).decode() - - self.transaction.asset['lock'] = { - 'secretHash': secret_hash.decode() - } - - self.transaction.asset['lock']['expiration'] = { - 'type': expiration_type, - 'value': expiration_value - } - - self.transaction.parse_signatures( - hexlify(self.serialized).decode(), - self.asset_offset + (8 + 32 + 1 + 4 + 21) * 2 - ) - - return self.transaction diff --git a/crypto/transactions/deserializers/htlc_refund.py b/crypto/transactions/deserializers/htlc_refund.py deleted file mode 100644 index 9260c1e0..00000000 --- a/crypto/transactions/deserializers/htlc_refund.py +++ /dev/null @@ -1,22 +0,0 @@ -from binascii import hexlify - -from crypto.transactions.deserializers.base import BaseDeserializer - - -class HtlcRefundDeserializer(BaseDeserializer): - - def deserialize(self): - starting_position = int(self.asset_offset) - - lock_transaction_id = hexlify(self.serialized)[starting_position:starting_position + 64] - - self.transaction.asset['refund'] = { - 'lockTransactionId': lock_transaction_id.decode() - } - - self.transaction.parse_signatures( - hexlify(self.serialized).decode(), - self.asset_offset + 64 - ) - - return self.transaction diff --git a/crypto/transactions/deserializers/ipfs.py b/crypto/transactions/deserializers/ipfs.py deleted file mode 100644 index 1cf91a3b..00000000 --- a/crypto/transactions/deserializers/ipfs.py +++ /dev/null @@ -1,38 +0,0 @@ -from binascii import hexlify, unhexlify - -from base58 import b58encode - -from binary.unsigned_integer.reader import read_bit8 -from binary.unsigned_integer.writer import write_bit8 - -from crypto.transactions.deserializers.base import BaseDeserializer - - -class IPFSDeserializer(BaseDeserializer): - - def deserialize(self): - starting_position = int(self.asset_offset / 2) - - hash_function = read_bit8(self.serialized, starting_position) & 0xff - ipfs_hash_length = read_bit8(self.serialized, starting_position + 1) & 0xff - - start_index = (starting_position + 2) * 2 - end_index = start_index + (ipfs_hash_length * 2) - ipfs_hash = hexlify(self.serialized)[start_index:end_index] - - temp_buffer = bytes() - - temp_buffer += write_bit8(hash_function) - temp_buffer += write_bit8(ipfs_hash_length) - temp_buffer += unhexlify(ipfs_hash) - - self.transaction.asset = { - 'ipfs': b58encode(temp_buffer).decode() - } - - self.transaction.parse_signatures( - hexlify(self.serialized).decode(), - self.asset_offset + (ipfs_hash_length + 2) * 2 - ) - - return self.transaction diff --git a/crypto/transactions/deserializers/second_signature_registration.py b/crypto/transactions/deserializers/second_signature_registration.py deleted file mode 100644 index 0b690818..00000000 --- a/crypto/transactions/deserializers/second_signature_registration.py +++ /dev/null @@ -1,21 +0,0 @@ -from binascii import hexlify - -from crypto.transactions.deserializers.base import BaseDeserializer - - -class SecondSignatureRegistrationDeserializer(BaseDeserializer): - - def deserialize(self): - starting_position = int(self.asset_offset) - - public_key = hexlify(self.serialized)[starting_position:starting_position + 66] - - self.transaction.asset = { - 'signature': { - 'publicKey': public_key.decode() - } - } - - self.transaction.parse_signatures(hexlify(self.serialized).decode(), self.asset_offset + 66) - - return self.transaction diff --git a/crypto/transactions/serializers/htlc_claim.py b/crypto/transactions/serializers/htlc_claim.py deleted file mode 100644 index 68ed6330..00000000 --- a/crypto/transactions/serializers/htlc_claim.py +++ /dev/null @@ -1,18 +0,0 @@ -from binascii import unhexlify - -from crypto.transactions.serializers.base import BaseSerializer -from crypto.exceptions import ArkSerializerException - - -class HtlcClaimSerializer(BaseSerializer): - """Serializer handling timelock claim data - """ - - def serialize(self): - self.bytes_data += unhexlify(self.transaction['asset']['claim']['lockTransactionId']) - unlock_secret_bytes = unhexlify(self.transaction['asset']['claim']['unlockSecret'].encode()) - if len(unlock_secret_bytes) != 32: - raise ArkSerializerException("Unlock secret must be 32 bytes long") - - self.bytes_data += unlock_secret_bytes - return self.bytes_data diff --git a/crypto/transactions/serializers/htlc_lock.py b/crypto/transactions/serializers/htlc_lock.py deleted file mode 100644 index 30fb2ec4..00000000 --- a/crypto/transactions/serializers/htlc_lock.py +++ /dev/null @@ -1,24 +0,0 @@ -from binascii import hexlify, unhexlify - -from base58 import b58decode_check - -from binary.hex.writer import write_high -from binary.unsigned_integer.writer import write_bit8, write_bit32, write_bit64 - -from crypto.transactions.serializers.base import BaseSerializer - - -class HtlcLockSerializer(BaseSerializer): - """Serializer handling timelock transfer data - """ - - def serialize(self): - self.bytes_data += write_bit64(self.transaction['amount']) - self.bytes_data += unhexlify(self.transaction['asset']['lock']['secretHash'].encode()) - self.bytes_data += write_bit8(self.transaction['asset']['lock']['expiration']['type']) - self.bytes_data += write_bit32(self.transaction['asset']['lock']['expiration']['value']) - - recipientId = hexlify(b58decode_check(self.transaction['recipientId'])) - self.bytes_data += write_high(recipientId) - - return self.bytes_data diff --git a/crypto/transactions/serializers/htlc_refund.py b/crypto/transactions/serializers/htlc_refund.py deleted file mode 100644 index 1ef2ce52..00000000 --- a/crypto/transactions/serializers/htlc_refund.py +++ /dev/null @@ -1,12 +0,0 @@ -from binascii import unhexlify - -from crypto.transactions.serializers.base import BaseSerializer - - -class HtlcRefundSerializer(BaseSerializer): - """Serializer handling timelock refund data - """ - - def serialize(self): - self.bytes_data += unhexlify(self.transaction['asset']['refund']['lockTransactionId'].encode()) - return self.bytes_data diff --git a/crypto/transactions/serializers/ipfs.py b/crypto/transactions/serializers/ipfs.py deleted file mode 100644 index 88dd629b..00000000 --- a/crypto/transactions/serializers/ipfs.py +++ /dev/null @@ -1,19 +0,0 @@ -from binascii import hexlify - -from base58 import b58decode - -from binary.hex.writer import write_high - -from crypto.transactions.serializers.base import BaseSerializer - - -class IPFSSerializer(BaseSerializer): - """Serializer handling ipfs data - """ - - def serialize(self): - ipfs = hexlify(b58decode(self.transaction['asset']['ipfs'])) - - self.bytes_data += write_high(ipfs) - - return self.bytes_data diff --git a/crypto/transactions/serializers/second_signature_registration.py b/crypto/transactions/serializers/second_signature_registration.py deleted file mode 100644 index dc8f398e..00000000 --- a/crypto/transactions/serializers/second_signature_registration.py +++ /dev/null @@ -1,12 +0,0 @@ -from binascii import unhexlify - -from crypto.transactions.serializers.base import BaseSerializer - - -class SecondSignatureRegistrationSerializer(BaseSerializer): - """Serializer handling registration of the seconds signature - """ - - def serialize(self): - self.bytes_data += unhexlify(self.transaction['asset']['signature']['publicKey'].encode()) - return self.bytes_data diff --git a/crypto/transactions/transaction.py b/crypto/transactions/transaction.py index 81b10a7f..3f8fcc38 100644 --- a/crypto/transactions/transaction.py +++ b/crypto/transactions/transaction.py @@ -5,10 +5,7 @@ from binary.hex.writer import write_high from binary.unsigned_integer.writer import write_bit8 -from crypto.constants import ( - TRANSACTION_DELEGATE_REGISTRATION, TRANSACTION_MULTI_SIGNATURE_REGISTRATION, - TRANSACTION_SECOND_SIGNATURE_REGISTRATION, TRANSACTION_VOTE -) +from crypto.constants import TRANSACTION_DELEGATE_REGISTRATION, TRANSACTION_MULTI_SIGNATURE_REGISTRATION, TRANSACTION_VOTE from crypto.exceptions import ArkInvalidTransaction from crypto.schnorr import schnorr from crypto.transactions.deserializer import Deserializer diff --git a/tests/transactions/builder/test_multi_signature_registration.py b/tests/transactions/builder/test_multi_signature_registration.py index 0724fc22..f720742c 100644 --- a/tests/transactions/builder/test_multi_signature_registration.py +++ b/tests/transactions/builder/test_multi_signature_registration.py @@ -7,7 +7,7 @@ def test_multi_signature_registration_transaction(passphrase): - """Test if a second signature registration transaction gets built + """Test if a multi signature registration transaction gets built """ publicKeys = [ '0205d9bbe71c343ac9a6a83a4344fd404c3534fc7349827097d0835d160bc2b896', diff --git a/tests/transactions/builder/test_transfer.py b/tests/transactions/builder/test_transfer.py index 4436e602..9ce829e4 100644 --- a/tests/transactions/builder/test_transfer.py +++ b/tests/transactions/builder/test_transfer.py @@ -90,7 +90,7 @@ def test_transfer_transaction_custom_fee(passphrase): transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid -def test_transfer_secondsig_transaction(passphrase): +def test_transfer_secondsign_transaction(passphrase): """Test if a transfer transaction with second signature gets built """ transaction = Transfer( diff --git a/tests/transactions/deserializers/test_transfer.py b/tests/transactions/deserializers/test_transfer.py index dc60f11d..413923d2 100644 --- a/tests/transactions/deserializers/test_transfer.py +++ b/tests/transactions/deserializers/test_transfer.py @@ -22,27 +22,3 @@ def test_transfer_deserializer(): assert actual.id == '8d0584d1bbc8fa7a6e4e7904b884f69399df56dcfe6458c2bedc283c55604def' actual.verify_schnorr() - - -@pytest.mark.skip() -def test_transfer_second_signature_deserializer(): - serialized = 'ff02170100000000000100000000000000034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed19280969800000000000000c2eb0b0000000000000000170995750207ecaf0ccf251c1265b92ad84f553662136c29d921b58ae3194020b82e9808f9cd54f7178cb34678f570f28226b8e56ba0ad318297a3bacbb37ab22ddaa5dbf1901cda3ec2d2bca5ce98d6407839ab9b02dd94f611e300ad77147d808a34e942b379c5468760d8605adc0304400a2578a2039468b844f30ad1f0515f9cce33855791296117bfe8ef3caa664152644fd6' - - deserializer = Deserializer(serialized) - actual = deserializer.deserialize() - - assert actual.version == 2 - assert actual.network == 23 - assert actual.typeGroup == 1 - assert actual.expiration == 0 - assert actual.type == 0 - assert actual.amount == 200000000 - assert actual.fee == 10000000 - assert actual.nonce == 1 - assert actual.recipientId == 'AGeYmgbg2LgGxRW2vNNJvQ88PknEJsYizC' - assert actual.senderPublicKey == '034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192' # noqa - assert actual.id == 'be148ab83b75c199f9778f8963814a641a6ee937b6dd5b294082fbf7def94a45' - assert actual.signature == '136c29d921b58ae3194020b82e9808f9cd54f7178cb34678f570f28226b8e56ba0ad318297a3bacbb37ab22ddaa5dbf1901cda3ec2d2bca5ce98d6407839ab9b' - assert actual.signSignature == '02dd94f611e300ad77147d808a34e942b379c5468760d8605adc0304400a2578a2039468b844f30ad1f0515f9cce33855791296117bfe8ef3caa664152644fd6' - - actual.verify_schnorr() From 3df8946da0c39d8eb5acdc6a040427224ea746c6 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Mon, 29 Jul 2024 14:33:42 +0100 Subject: [PATCH 52/68] refactor: tidy base58 (#132) --- crypto/identity/wif.py | 1 + crypto/transactions/serializers/transfer.py | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/crypto/identity/wif.py b/crypto/identity/wif.py index 28877870..1f296312 100644 --- a/crypto/identity/wif.py +++ b/crypto/identity/wif.py @@ -23,4 +23,5 @@ def wif_from_passphrase(passphrase, network_wif=None): private_key = hashlib.sha256(passphrase.encode()) seed = write_bit8(network_wif) + private_key.digest() + write_bit8(0x01) + return b58encode_check(seed).decode() diff --git a/crypto/transactions/serializers/transfer.py b/crypto/transactions/serializers/transfer.py index d7d068c7..cbc69129 100644 --- a/crypto/transactions/serializers/transfer.py +++ b/crypto/transactions/serializers/transfer.py @@ -1,7 +1,3 @@ -from binascii import hexlify - -from base58 import b58decode_check - from binary.hex.writer import write_high from binary.unsigned_integer.writer import write_bit32, write_bit64 From 9aa6664bf5fa754ab8e5fb7541b16d07ad56a669 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Mon, 29 Jul 2024 16:24:06 +0100 Subject: [PATCH 53/68] chore: update editorconfig (#134) --- .editorconfig | 9 ++++--- Makefile | 4 ++-- crypto/__init__.py | 1 + crypto/configuration/__init__.py | 1 + crypto/identity/__init__.py | 1 + crypto/networks/__init__.py | 1 + crypto/schnorr/__init__.py | 1 + crypto/schnorr/schnorr.py | 4 ++-- crypto/transactions/__init__.py | 1 + crypto/transactions/builder/__init__.py | 1 + crypto/transactions/deserializers/__init__.py | 1 + crypto/transactions/serializers/__init__.py | 1 + crypto/utils/__init__.py | 1 + tests/__init__.py | 1 + tests/configuration/__init__.py | 1 + tests/identity/__init__.py | 1 + tests/resources/__init__.py | 1 + tests/transactions/__init__.py | 1 + tests/transactions/builder/__init__.py | 1 + tests/transactions/deserializers/__init__.py | 1 + tests/transactions/serializers/__init__.py | 1 + tests/transactions/test_signature.py | 24 +++++++++---------- tests/utils/__init__.py | 1 + 23 files changed, 41 insertions(+), 19 deletions(-) diff --git a/.editorconfig b/.editorconfig index 99580d06..a938c0a0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,9 +1,12 @@ root = true [*] -charset = utf-8 +indent_style = space +indent_size = 4 end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true insert_final_newline = true -indent_style = space + +[*.y{a,}ml] indent_size = 2 -trim_trailing_whitespace = true diff --git a/Makefile b/Makefile index bf9681ec..7df05407 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ test: - pytest -v -s $(ARGS) + pytest -v -s $(ARGS) lint: - flake8 . + flake8 . diff --git a/crypto/__init__.py b/crypto/__init__.py index e69de29b..8b137891 100644 --- a/crypto/__init__.py +++ b/crypto/__init__.py @@ -0,0 +1 @@ + diff --git a/crypto/configuration/__init__.py b/crypto/configuration/__init__.py index e69de29b..8b137891 100644 --- a/crypto/configuration/__init__.py +++ b/crypto/configuration/__init__.py @@ -0,0 +1 @@ + diff --git a/crypto/identity/__init__.py b/crypto/identity/__init__.py index e69de29b..8b137891 100644 --- a/crypto/identity/__init__.py +++ b/crypto/identity/__init__.py @@ -0,0 +1 @@ + diff --git a/crypto/networks/__init__.py b/crypto/networks/__init__.py index e69de29b..8b137891 100644 --- a/crypto/networks/__init__.py +++ b/crypto/networks/__init__.py @@ -0,0 +1 @@ + diff --git a/crypto/schnorr/__init__.py b/crypto/schnorr/__init__.py index e69de29b..8b137891 100644 --- a/crypto/schnorr/__init__.py +++ b/crypto/schnorr/__init__.py @@ -0,0 +1 @@ + diff --git a/crypto/schnorr/schnorr.py b/crypto/schnorr/schnorr.py index 9f65e7ed..8b76a9ce 100644 --- a/crypto/schnorr/schnorr.py +++ b/crypto/schnorr/schnorr.py @@ -218,8 +218,8 @@ def y(P): def encoded_from_point(P): """ Encode and compress a ``secp256k1`` point: - * ``bytes(2) || bytes(x)`` if y is even - * ``bytes(3) || bytes(x)`` if y is odd + * ``bytes(2) || bytes(x)`` if y is even + * ``bytes(3) || bytes(x)`` if y is odd Args: P (:class:`list`): ``secp256k1`` point diff --git a/crypto/transactions/__init__.py b/crypto/transactions/__init__.py index e69de29b..8b137891 100644 --- a/crypto/transactions/__init__.py +++ b/crypto/transactions/__init__.py @@ -0,0 +1 @@ + diff --git a/crypto/transactions/builder/__init__.py b/crypto/transactions/builder/__init__.py index e69de29b..8b137891 100644 --- a/crypto/transactions/builder/__init__.py +++ b/crypto/transactions/builder/__init__.py @@ -0,0 +1 @@ + diff --git a/crypto/transactions/deserializers/__init__.py b/crypto/transactions/deserializers/__init__.py index e69de29b..8b137891 100644 --- a/crypto/transactions/deserializers/__init__.py +++ b/crypto/transactions/deserializers/__init__.py @@ -0,0 +1 @@ + diff --git a/crypto/transactions/serializers/__init__.py b/crypto/transactions/serializers/__init__.py index e69de29b..8b137891 100644 --- a/crypto/transactions/serializers/__init__.py +++ b/crypto/transactions/serializers/__init__.py @@ -0,0 +1 @@ + diff --git a/crypto/utils/__init__.py b/crypto/utils/__init__.py index e69de29b..8b137891 100644 --- a/crypto/utils/__init__.py +++ b/crypto/utils/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/__init__.py b/tests/__init__.py index e69de29b..8b137891 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/configuration/__init__.py b/tests/configuration/__init__.py index e69de29b..8b137891 100644 --- a/tests/configuration/__init__.py +++ b/tests/configuration/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/identity/__init__.py b/tests/identity/__init__.py index e69de29b..8b137891 100644 --- a/tests/identity/__init__.py +++ b/tests/identity/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/resources/__init__.py b/tests/resources/__init__.py index e69de29b..8b137891 100644 --- a/tests/resources/__init__.py +++ b/tests/resources/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/transactions/__init__.py b/tests/transactions/__init__.py index e69de29b..8b137891 100644 --- a/tests/transactions/__init__.py +++ b/tests/transactions/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/transactions/builder/__init__.py b/tests/transactions/builder/__init__.py index e69de29b..8b137891 100644 --- a/tests/transactions/builder/__init__.py +++ b/tests/transactions/builder/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/transactions/deserializers/__init__.py b/tests/transactions/deserializers/__init__.py index e69de29b..8b137891 100644 --- a/tests/transactions/deserializers/__init__.py +++ b/tests/transactions/deserializers/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/transactions/serializers/__init__.py b/tests/transactions/serializers/__init__.py index e69de29b..8b137891 100644 --- a/tests/transactions/serializers/__init__.py +++ b/tests/transactions/serializers/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/transactions/test_signature.py b/tests/transactions/test_signature.py index c797dd87..c9462ab2 100644 --- a/tests/transactions/test_signature.py +++ b/tests/transactions/test_signature.py @@ -8,9 +8,9 @@ def test_should_sign_and_verify(): bytes.fromhex("814857ce48e291893feab95df02e1dbf7ad3994ba46f247f77e4eefd5d8734a2"), bytes.fromhex("814857ce48e291893feab95df02e1dbf7ad3994ba46f247f77e4eefd5d8734a2"), ), - ), - bytes.fromhex('814857ce48e291893feab95df02e1dbf7ad3994ba46f247f77e4eefd5d8734a2'), - bytes.fromhex('e84093c072af70004a38dd95e34def119d2348d5261228175d032e5f2070e19f'), + ), + bytes.fromhex('814857ce48e291893feab95df02e1dbf7ad3994ba46f247f77e4eefd5d8734a2'), + bytes.fromhex('e84093c072af70004a38dd95e34def119d2348d5261228175d032e5f2070e19f'), ) assert isVerified == True @@ -22,9 +22,9 @@ def test_should_sign_and_verify_with_ecdsa_key_with_prefix(): bytes.fromhex("6616cd071ecbfe525be817d29eb1ccd6d93af0a9207356b38dcd73fcc84ff297"), bytes.fromhex("6acdb0def03305800b75e9c020e0a9b0504a543f56253f694ff35f1dce8a193f"), ), - ), - bytes.fromhex('6616cd071ecbfe525be817d29eb1ccd6d93af0a9207356b38dcd73fcc84ff297'), - bytes.fromhex('025f7362e1baff21b8441c20b3f54583eb2f5925afada140b5a95880a2224a9d48'), + ), + bytes.fromhex('6616cd071ecbfe525be817d29eb1ccd6d93af0a9207356b38dcd73fcc84ff297'), + bytes.fromhex('025f7362e1baff21b8441c20b3f54583eb2f5925afada140b5a95880a2224a9d48'), ) assert isVerified == True @@ -36,9 +36,9 @@ def test_should_sign_and_verify_with_ecdsa_key_without_prefix(): bytes.fromhex("6616cd071ecbfe525be817d29eb1ccd6d93af0a9207356b38dcd73fcc84ff297"), bytes.fromhex("6acdb0def03305800b75e9c020e0a9b0504a543f56253f694ff35f1dce8a193f"), ), - ), - bytes.fromhex('6616cd071ecbfe525be817d29eb1ccd6d93af0a9207356b38dcd73fcc84ff297'), - bytes.fromhex('5f7362e1baff21b8441c20b3f54583eb2f5925afada140b5a95880a2224a9d48'), + ), + bytes.fromhex('6616cd071ecbfe525be817d29eb1ccd6d93af0a9207356b38dcd73fcc84ff297'), + bytes.fromhex('5f7362e1baff21b8441c20b3f54583eb2f5925afada140b5a95880a2224a9d48'), ) assert isVerified == True @@ -50,9 +50,9 @@ def test_should_not_sign_and_verify_with_wrong_ecdsa_key(): bytes.fromhex("6616cd071ecbfe525be817d29eb1ccd6d93af0a9207356b38dcd73fcc84ff297"), bytes.fromhex("6acdb0def03305800b75e9c020e0a9b0504a543f56253f694ff35f1dce8a193f"), ), - ), - bytes.fromhex('6616cd071ecbfe525be817d29eb1ccd6d93af0a9207356b38dcd73fcc84ff297'), - bytes.fromhex('02d076ae01c84ad5e72eb2aae9a3c60784b08cc1f0e8624fe3cc51648a163ee120'), + ), + bytes.fromhex('6616cd071ecbfe525be817d29eb1ccd6d93af0a9207356b38dcd73fcc84ff297'), + bytes.fromhex('02d076ae01c84ad5e72eb2aae9a3c60784b08cc1f0e8624fe3cc51648a163ee120'), ) assert isVerified == False diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py index e69de29b..8b137891 100644 --- a/tests/utils/__init__.py +++ b/tests/utils/__init__.py @@ -0,0 +1 @@ + From 602c6ae69c0d2f9559585c6ee7647818f0cf70e0 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Mon, 29 Jul 2024 17:16:32 +0100 Subject: [PATCH 54/68] refactor: serialize transaction method (#133) --- crypto/transactions/builder/base.py | 16 +++++++++++- tests/transactions/builder/test_transfer.py | 29 +++++++++++++++++++-- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/crypto/transactions/builder/base.py b/crypto/transactions/builder/base.py index 25592cfb..2a5181ef 100644 --- a/crypto/transactions/builder/base.py +++ b/crypto/transactions/builder/base.py @@ -4,6 +4,7 @@ from crypto.constants import TRANSACTION_TYPE_GROUP from crypto.identity.private_key import PrivateKey from crypto.identity.public_key import PublicKey +from crypto.transactions.serializer import Serializer from crypto.transactions.signature import Signature from crypto.transactions.transaction import Transaction @@ -14,7 +15,7 @@ def __init__(self): self.transaction.type = getattr(self, 'transaction_type', None) self.transaction.fee = get_fee(getattr(self, 'transaction_type', None)) self.transaction.nonce = getattr(self, 'nonce', None) - self.transaction.typeGroup = getattr(self, 'typeGroup', 1) + self.transaction.typeGroup = getattr(self, 'typeGroup', int(TRANSACTION_TYPE_GROUP.CORE)) self.transaction.signatures = getattr(self, 'signatures', None) self.transaction.version = getattr(self, 'version', 1) self.transaction.expiration = getattr(self, 'expiration', 0) @@ -64,6 +65,19 @@ def multi_sign(self, passphrase, index): index_formatted = hex(index).replace('x', '') self.transaction.signatures.append(index_formatted + signature.decode()) + def serialize(self, skip_signature=False, skip_second_signature=False, skip_multi_signature=False): + """Perform AIP11 compliant serialization. + + Args: + skip_signature (bool, optional): do you want to skip the signature + skip_second_signature (bool, optional): do you want to skip the 2nd signature + skip_multi_signature (bool, optional): do you want to skip multi signature + + Returns: + str: Serialized string + """ + return Serializer(self.to_dict()).serialize(skip_signature, skip_second_signature, skip_multi_signature) + def schnorr_verify(self): return self.transaction.verify_schnorr() diff --git a/tests/transactions/builder/test_transfer.py b/tests/transactions/builder/test_transfer.py index 9ce829e4..700a63aa 100644 --- a/tests/transactions/builder/test_transfer.py +++ b/tests/transactions/builder/test_transfer.py @@ -17,11 +17,9 @@ def test_transfer_transaction(passphrase): recipientId='0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', amount=1, fee=10000000, - # timestamp=1720707047217, ) transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) transaction.set_nonce(8) - # transaction.transaction.id = '495afb812cb0ecfe7ac4d383b54d6458b53bb9be5ab37e2207bbd7ce82fdbc94' transaction.sign(passphrase) transaction_dict = transaction.to_dict() @@ -146,3 +144,30 @@ def test_transfer_transaction_amount_zero(): recipientId='0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', amount=0 ) + + +def test_transfer_serialize(passphrase): + transaction = Transfer( + recipientId='0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', + amount=1, + fee=10000000, + ) + transaction.set_nonce(6) + transaction.sign(passphrase) + transaction.transaction.signature = '42faaaf6b5b5eff5bb78c7bb2b116ecbc0a83f53445b801818b72afb34b39226646608d5e7048c12d6aedcebfc3156f035b57ca70c6a5e899b7ac2a1be163bb0' + + assert transaction.serialize(False, False, False) == 'ff011e0100000000000600000000000000023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d38096980000000000000100000000000000000000006f0182a0cc707b055322ccf6d4cb6a5aff1aeb2242faaaf6b5b5eff5bb78c7bb2b116ecbc0a83f53445b801818b72afb34b39226646608d5e7048c12d6aedcebfc3156f035b57ca70c6a5e899b7ac2a1be163bb0' + + +def test_transfer_serialize_without_signatures(passphrase): + transaction = Transfer( + recipientId='0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', + amount=1, + fee=10000000, + ) + transaction.set_nonce(6) + transaction.sign(passphrase) + transaction.second_sign(passphrase) + transaction.transaction.signature = '42faaaf6b5b5eff5bb78c7bb2b116ecbc0a83f53445b801818b72afb34b39226646608d5e7048c12d6aedcebfc3156f035b57ca70c6a5e899b7ac2a1be163bb0' + + assert transaction.serialize(True, True, True) == 'ff011e0100000000000600000000000000023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d38096980000000000000100000000000000000000006f0182a0cc707b055322ccf6d4cb6a5aff1aeb22' From 184b1d2bcaa7b4928ce4cbfd98c29a821c880ce3 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Tue, 30 Jul 2024 14:47:08 +0100 Subject: [PATCH 55/68] refactor: add type hints & general tidy up (#135) --- .github/workflows/test.yml | 2 - CHANGELOG.md | 30 -- README.md | 1 - crypto/configuration/fee.py | 11 +- crypto/configuration/network.py | 28 +- crypto/identity/address.py | 35 ++- crypto/identity/private_key.py | 14 +- crypto/identity/public_key.py | 10 +- crypto/identity/wif.py | 7 +- crypto/networks/devnet.py | 1 - crypto/networks/mainnet.py | 1 - crypto/networks/testnet.py | 1 - crypto/schnorr/__init__.py | 1 - crypto/schnorr/schnorr.py | 262 ------------------ crypto/transactions/builder/base.py | 30 +- .../builder/delegate_registration.py | 12 +- .../builder/delegate_resignation.py | 8 +- crypto/transactions/builder/multi_payment.py | 10 +- .../builder/multi_signature_registration.py | 17 +- crypto/transactions/builder/transfer.py | 5 +- crypto/transactions/builder/vote.py | 5 +- crypto/transactions/deserializer.py | 24 +- crypto/transactions/deserializers/base.py | 11 +- .../deserializers/delegate_registration.py | 2 - .../deserializers/delegate_resignation.py | 2 - .../deserializers/multi_payment.py | 3 - .../multi_signature_registration.py | 3 - crypto/transactions/deserializers/transfer.py | 1 - crypto/transactions/deserializers/vote.py | 1 - crypto/transactions/serializer.py | 30 +- crypto/transactions/serializers/base.py | 7 +- .../serializers/delegate_registration.py | 7 +- .../serializers/delegate_resignation.py | 3 +- .../transactions/serializers/multi_payment.py | 3 +- .../multi_signature_registration.py | 4 +- crypto/transactions/serializers/transfer.py | 3 +- crypto/transactions/serializers/vote.py | 4 +- crypto/transactions/signature.py | 2 +- crypto/transactions/transaction.py | 64 +++-- .../test_multi_signature_registration.py | 2 +- tests/transactions/builder/test_transfer.py | 2 +- .../test_multi_signature_registration.py | 2 +- 42 files changed, 193 insertions(+), 478 deletions(-) delete mode 100644 CHANGELOG.md delete mode 100644 crypto/schnorr/__init__.py delete mode 100644 crypto/schnorr/schnorr.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0e601c4f..899afb2c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,5 +32,3 @@ jobs: . venv/bin/activate mkdir test-results pytest -v -s --junitxml=test-reports/junit.xml --cov=crypto --cov-config=.coveragerc --cov-report xml - - name: Codecov - run: bash <(curl -s https://codecov.io/bash) -t ${{ secrets.CODECOV_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 384c278f..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,30 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) -and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). - -## Unreleased - -## 2.0.0 - 2021-09-21 - -## Fixed - -- Add amount option to HTLC transactions - -## 1.0.0 - 2020-02-11 - -### Added - -- Implement AIP11 -- Implement AIP18 - -## 0.1.1 - 2018-08-29 - -### Fixed - -- created network objects instead of loading network settings from an ini file which is not included in the built package - -## 0.1.0 - 2018-08-27 -- Initial Release diff --git a/README.md b/README.md index dca05c1f..87bbdb7e 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ > A simple Cryptography Implementation in Python for the Ark Blockchain. [![Build Status](https://badgen.now.sh/circleci/github/ArkEcosystem/python-crypto)](https://circleci.com/gh/ArkEcosystem/python-crypto) -[![Codecov](https://badgen.now.sh/codecov/c/github/arkecosystem/python-crypto)](https://codecov.io/gh/arkecosystem/python-crypto) [![Latest Version](https://badgen.now.sh/github/release/ArkEcosystem/python-crypto)](https://github.com/ArkEcosystem/python-crypto/releases/latest) [![License: MIT](https://badgen.now.sh/badge/license/MIT/green)](https://opensource.org/licenses/MIT) diff --git a/crypto/configuration/fee.py b/crypto/configuration/fee.py index e1b876c2..d6c3e528 100644 --- a/crypto/configuration/fee.py +++ b/crypto/configuration/fee.py @@ -2,20 +2,18 @@ fees = TRANSACTION_FEES.copy() - -def get_fee(transaction_type): +def get_fee(transaction_type: int, *, default: int = 0) -> int: """Get a fee for a given transaction type Args: transaction_type (int): transaction type for which we wish to get a fee Returns: - int: transaction fee + int | None: transaction fee, or None if the transaction type is not found """ - return fees.get(transaction_type) - + return fees.get(transaction_type, default) -def set_fee(transaction_type, value): +def set_fee(transaction_type: int, value: int) -> None: """Set a fee Args: @@ -23,4 +21,5 @@ def set_fee(transaction_type, value): value (int): fee for a given transaction type """ global fees + fees[transaction_type] = value diff --git a/crypto/configuration/network.py b/crypto/configuration/network.py index 97a4bbab..3440123e 100644 --- a/crypto/configuration/network.py +++ b/crypto/configuration/network.py @@ -1,34 +1,43 @@ +from datetime import datetime +from typing import Type, TypedDict, Union from crypto.networks.devnet import Devnet +from crypto.networks.mainnet import Mainnet +from crypto.networks.testnet import Testnet -network = {} +class NetworkType(TypedDict): + epoch: datetime + version: int + wif: int +network: NetworkType = { + 'epoch': Devnet.epoch, + 'version': Devnet.version, + 'wif': Devnet.wif, +} -def set_network(network_object): +def set_network(network_object: Union[Type[Mainnet], Type[Devnet], Type[Testnet]]) -> None: """Set what network you want to use in the crypto library Args: - network_object (Network object): Testnet, Devnet, Mainnet + network_object: Testnet, Devnet, Mainnet """ global network + network = { 'epoch': network_object.epoch, 'version': network_object.version, 'wif': network_object.wif, } - -def get_network(): +def get_network() -> NetworkType: """Get settings for a selected network, default network is devnet Returns: dict: network settings (default network is devnet) """ - if not network: - set_network(Devnet) return network - -def set_custom_network(epoch, version, wif): +def set_custom_network(epoch: datetime, version: int, wif: int) -> None: """Set custom network Args: @@ -37,6 +46,7 @@ def set_custom_network(epoch, version, wif): wif (int): chains wif """ global network + network = { 'epoch': epoch, 'version': version, diff --git a/crypto/identity/address.py b/crypto/identity/address.py index 514eb7d2..477ec9eb 100644 --- a/crypto/identity/address.py +++ b/crypto/identity/address.py @@ -3,10 +3,10 @@ from crypto.identity.private_key import PrivateKey -from Cryptodome.Hash import RIPEMD160, keccak +from Cryptodome.Hash import keccak from coincurve import PrivateKey, PublicKey -def get_checksum_address(address: bytes) -> str: +def get_checksum_address(address: str) -> str: """Get checksum address Args: @@ -33,50 +33,47 @@ def get_checksum_address(address: bytes) -> str: return "0x" + ''.join(chars) -def address_from_public_key(public_key): +def address_from_public_key(public_key: str) -> str: """Get an address from a public key Args: - public_key (str): + public_key (str): public key to get address Returns: str: address """ - public_key = PublicKey(bytes.fromhex(public_key)).format(compressed=False)[1:] + public_key_bytes = PublicKey(bytes.fromhex(public_key)).format(compressed=False)[1:] keccak_hash = keccak.new( - data=bytearray.fromhex(public_key.hex()), + data=bytearray.fromhex(public_key_bytes.hex()), digest_bits=256, ) return get_checksum_address(unhexlify(keccak_hash.hexdigest()[22:]).hex()) - -def address_from_private_key(private_key): +def address_from_private_key(private_key: str) -> str: """Get an address from private key Args: - private_key (string): + private_key (string): private key to get address Returns: - TYPE: Description + str: address """ - private_key = PrivateKey.from_hex(private_key) - - return address_from_public_key(private_key.public_key.format(compressed=False).hex()) + private_key_object = PrivateKey.from_hex(private_key) + return address_from_public_key(private_key_object.public_key.format(compressed=False).hex()) -def address_from_passphrase(passphrase): +def address_from_passphrase(passphrase: str) -> str: """Get an address from passphrase Args: - passphrase (str): - network_version (int, optional): + passphrase (str): passphrase to get address Returns: - string: address + str: address """ private_key = hashlib.sha256(passphrase.encode()).hexdigest() - address = address_from_private_key(private_key) - return address + + return address_from_private_key(private_key) diff --git a/crypto/identity/private_key.py b/crypto/identity/private_key.py index 3183e90b..5c0781dc 100644 --- a/crypto/identity/private_key.py +++ b/crypto/identity/private_key.py @@ -3,26 +3,25 @@ from coincurve import PrivateKey as PvtKey - class PrivateKey(object): - def __init__(self, private_key): + def __init__(self, private_key: str): self.private_key = PvtKey.from_hex(private_key) self.public_key = hexlify(self.private_key.public_key.format()).decode() - def sign(self, message): + def sign(self, message: bytes) -> bytes: """Sign a message with this private key object Args: message (bytes): bytes data you want to sign Returns: - str: signature of the signed message + bytes: signature of the signed message """ from crypto.transactions.signature import Signature signature = Signature.sign( hexlify(message), - self.private_key.to_hex() + self ) return signature.encode() @@ -36,7 +35,7 @@ def to_hex(self): return self.private_key.to_hex() @classmethod - def from_passphrase(cls, passphrase): + def from_passphrase(cls, passphrase: str): """Create PrivateKey object from a given passphrase Args: @@ -46,10 +45,11 @@ def from_passphrase(cls, passphrase): PrivateKey: Private key object """ private_key = sha256(passphrase.encode()).hexdigest() + return cls(private_key) @classmethod - def from_hex(cls, private_key): + def from_hex(cls, private_key: str): """Create PrivateKey object from a given hex private key Args: diff --git a/crypto/identity/public_key.py b/crypto/identity/public_key.py index f2313580..d32f2b02 100644 --- a/crypto/identity/public_key.py +++ b/crypto/identity/public_key.py @@ -5,16 +5,16 @@ from crypto.identity.private_key import PrivateKey class PublicKey(object): - def __init__(self, public_key): - public_key = unhexlify(public_key.encode()) - self.public_key = PubKey(public_key) + def __init__(self, public_key: str): + self.public_key = PubKey(unhexlify(public_key.encode())) - def to_hex(self): + def to_hex(self) -> str: return hexlify(self.public_key.format()).decode() @classmethod - def from_passphrase(cls, passphrase): + def from_passphrase(cls, passphrase: str) -> str: private_key = PrivateKey.from_passphrase(passphrase) + return private_key.public_key @classmethod diff --git a/crypto/identity/wif.py b/crypto/identity/wif.py index 1f296312..c541ceea 100644 --- a/crypto/identity/wif.py +++ b/crypto/identity/wif.py @@ -1,4 +1,5 @@ import hashlib +from typing import Optional from base58 import b58encode_check @@ -6,12 +7,11 @@ from crypto.configuration.network import get_network - -def wif_from_passphrase(passphrase, network_wif=None): +def wif_from_passphrase(passphrase: str, network_wif: Optional[int] = None): """Get wif from passphrase Args: - passphrase (bytes): + passphrase (str): network_wif (int, optional): Returns: @@ -19,6 +19,7 @@ def wif_from_passphrase(passphrase, network_wif=None): """ if not network_wif: network = get_network() + network_wif = network['wif'] private_key = hashlib.sha256(passphrase.encode()) diff --git a/crypto/networks/devnet.py b/crypto/networks/devnet.py index 6af399f0..dc3d2490 100644 --- a/crypto/networks/devnet.py +++ b/crypto/networks/devnet.py @@ -1,6 +1,5 @@ from datetime import datetime - class Devnet(object): epoch = datetime(2017, 3, 21, 13, 00, 00) version = 30 diff --git a/crypto/networks/mainnet.py b/crypto/networks/mainnet.py index 7e648903..4b21668b 100644 --- a/crypto/networks/mainnet.py +++ b/crypto/networks/mainnet.py @@ -1,6 +1,5 @@ from datetime import datetime - class Mainnet(object): epoch = datetime(2017, 3, 21, 13, 00, 00) version = 23 diff --git a/crypto/networks/testnet.py b/crypto/networks/testnet.py index 7644cecc..cb4f73b4 100644 --- a/crypto/networks/testnet.py +++ b/crypto/networks/testnet.py @@ -1,6 +1,5 @@ from datetime import datetime - class Testnet(object): epoch = datetime(2017, 3, 21, 13, 00, 00) version = 23 diff --git a/crypto/schnorr/__init__.py b/crypto/schnorr/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/crypto/schnorr/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crypto/schnorr/schnorr.py b/crypto/schnorr/schnorr.py deleted file mode 100644 index 8b76a9ce..00000000 --- a/crypto/schnorr/schnorr.py +++ /dev/null @@ -1,262 +0,0 @@ -# From Toons implementation -# see here https://github.com/Moustikitos/dpos/blob/master/dposlib/ark/secp256k1/schnorr.py - -import hashlib -from binascii import unhexlify -from builtins import int - -p = int(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F) -n = int(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141) - - -def point_from_encoded(pubkey): - """ - Decode and decompress a ``secp256k1`` point. - Args: - pubkey (:class:`bytes`): compressed and encoded point - Returns: - :class:`list`: ``secp256k1`` point - """ - pubkey = bytearray(pubkey) - x = int_from_bytes(pubkey[1:]) - y = y_from_x(x) - if y is None: - raise ValueError('Point not on ``secp256k1`` curve') - elif y % 2 != pubkey[0] - 2: - y = -y % p - return [x, y] - - -def y_from_x(x): - """ - Compute :class:`P.y` from :class:`P.x` according to ``y²=x³+7``. - """ - y_sq = (pow(x, 3, p) + 7) % p - y = pow(y_sq, (p + 1) // 4, p) - if pow(y, 2, p) != y_sq: - return None - return y - - -class Point(list): - """ - ``secp256k1`` point . Initialization can be done with sole ``x`` value. - :class:`Point` overrides ``*`` and ``+`` operators which accepts - :class:`list` as argument and returns :class:`Point`. It extends - :class:`list` and allows item access using ``__getattr__``/``__setattr__`` - operators. - """ - - x = property( - lambda cls: list.__getitem__(cls, 0), - lambda cls, v: [ - list.__setitem__(cls, 0, int(v)), - list.__setitem__(cls, 1, y_from_x(int(v))) - ], - None, '' - ) - y = property( - lambda cls: list.__getitem__(cls, 1), - None, None, '' - ) - - def __init__(self, *xy): - if len(xy) == 0: - xy = (0, None) - elif len(xy) == 1: - xy += (y_from_x(int(xy[0])), ) - list.__init__(self, [int(e) if e is not None else e for e in xy[:2]]) - - def __mul__(self, k): - if isinstance(k, int): - return Point(*point_mul(self, k)) - else: - raise TypeError("'%s' should be an int" % k) - __rmul__ = __mul__ - - def __add__(self, P): - if isinstance(P, list): - return Point(*point_add(self, P)) - else: - raise TypeError("'%s' should be a 2-int-length list" % P) - __radd__ = __add__ - - @staticmethod - def decode(pubkey): - """ - Decode and decompress a ``secp256k1`` point. - """ - return Point(*point_from_encoded(pubkey)) - - def encode(self): - """ - Decode and decompress a ``secp256k1`` point. - """ - return encoded_from_point(self) - - -G = Point(0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798) - - -def point_add(P1, P2): - """ - Add ``secp256k1`` points. - Args: - P1 (:class:`list`): first ``secp256k1`` point - P2 (:class:`list`): second ``secp256k1`` point - Returns: - :class:`list`: ``secp256k1`` point - """ - if (P1 is None): - return P2 - if (P2 is None): - return P1 - if (x(P1) == x(P2) and y(P1) != y(P2)): - raise ValueError('One of the point is not on the curve') - if (P1 == P2): - lam = (3 * x(P1) * x(P1) * pow(2 * y(P1), p - 2, p)) % p - else: - lam = ((y(P2) - y(P1)) * pow(x(P2) - x(P1), p - 2, p)) % p - x3 = (lam * lam - x(P1) - x(P2)) % p - return [x3, (lam * (x(P1) - x3) - y(P1)) % p] - - -def bcrypto410_verify(msg, pubkey, sig): - if len(msg) != 32: - raise ValueError('The message must be a 32-byte array.') - if len(sig) != 64: - raise ValueError('The signature must be a 64-byte array.') - - P = Point.decode(pubkey) - r, s = int_from_bytes(sig[:32]), int_from_bytes(sig[32:]) - if r >= p or s >= n: - return False - - e = int_from_bytes(hash_sha256(sig[0:32] + pubkey + msg)) % n - R = Point(*(G*s + point_mul(P, n-e))) # P*(n-e) does not work... - if R is None or not is_quad(R.y) or R.x != r: - return False - return True - - -def b410_schnorr_verify(message, publicKey, signature): - return bcrypto410_verify( - hash_sha256(message), - Point.decode(unhexlify(publicKey)).encode(), - unhexlify(signature) - ) - - -def x(P): - """ - Return :class:`P.x` or :class:`P[0]`. - Args: - P (:class:`list`): ``secp256k1`` point - Returns: - :class:`int`: x - """ - return P[0] - - -def bytes_from_int(x): - return int(x).to_bytes(32, byteorder='big') - - -def int_from_bytes(b): - return int.from_bytes(b, byteorder='big') - - -def jacobi(x): - return pow(x, (p - 1) // 2, p) - - -def is_quad(x): - return jacobi(x) == 1 - - -def hash_sha256(b): - """ - Args: - b (:class:`bytes` or :class:`str`): sequence to be hashed - Returns: - :class:`bytes`: sha256 hash - """ - return hashlib.sha256( - b if isinstance(b, bytes) else b.encode('utf-8') - ).digest() - - -def point_mul(P, n): - """ - Multiply ``secp256k1`` point with scalar. - Args: - P (:class:`list`): ``secp256k1`` point - n (:class:`int`): scalar - Returns: - :class:`list`: ``secp256k1`` point - """ - R = None - for i in range(256): - if ((n >> i) & 1): - R = point_add(R, P) - P = point_add(P, P) - return R - - -def y(P): - """ - Return :class:`P.y` or :class:`P[1]`. - - Args: - P (:class:`list`): ``secp256k1`` point - Returns: - :class:`int`: y - """ - return P[1] - - -def encoded_from_point(P): - """ - Encode and compress a ``secp256k1`` point: - * ``bytes(2) || bytes(x)`` if y is even - * ``bytes(3) || bytes(x)`` if y is odd - - Args: - P (:class:`list`): ``secp256k1`` point - Returns: - :class:`bytes`: compressed and encoded point - """ - return (b'\x03' if y(P) & 1 else b'\x02') + bytes_from_int(x(P)) - - -# https://github.com/bcoin-org/bcrypto/blob/v4.1.0/lib/js/schnorr.js -def bcrypto410_sign(msg, seckey0): - if len(msg) != 32: - raise ValueError('The message must be a 32-byte array.') - - seckey = int_from_bytes(seckey0) - if not (1 <= seckey <= n - 1): - raise ValueError( - 'The secret key must be an integer in the range 1..n-1.' - ) - - k0 = int_from_bytes(hash_sha256(seckey0 + msg)) % n - if k0 == 0: - raise RuntimeError( - 'Failure. This happens only with negligible probability.' - ) - - R = G * k0 - Rraw = bytes_from_int(R.x) - e = int_from_bytes( - hash_sha256(Rraw + encoded_from_point(G*seckey) + msg) - ) % n - - seckey %= n - k0 %= n - k = n - k0 if not is_quad(R.y) else k0 - - s = (k + e * seckey) % n - s %= n - - return Rraw + bytes_from_int(s) diff --git a/crypto/transactions/builder/base.py b/crypto/transactions/builder/base.py index 2a5181ef..8683ebaf 100644 --- a/crypto/transactions/builder/base.py +++ b/crypto/transactions/builder/base.py @@ -9,14 +9,24 @@ from crypto.transactions.transaction import Transaction class BaseTransactionBuilder(object): + transaction: Transaction def __init__(self): self.transaction = Transaction() - self.transaction.type = getattr(self, 'transaction_type', None) - self.transaction.fee = get_fee(getattr(self, 'transaction_type', None)) - self.transaction.nonce = getattr(self, 'nonce', None) + + if hasattr(self, 'transaction_type'): + self.transaction.type = getattr(self, 'transaction_type') + + if hasattr(self, 'transaction_type'): + self.transaction.fee = get_fee(getattr(self, 'transaction_type')) + + if hasattr(self, 'nonce'): + self.transaction.nonce = getattr(self, 'nonce') + + if hasattr(self, 'signatures'): + self.transaction.signatures = getattr(self, 'signatures') + self.transaction.typeGroup = getattr(self, 'typeGroup', int(TRANSACTION_TYPE_GROUP.CORE)) - self.transaction.signatures = getattr(self, 'signatures', None) self.transaction.version = getattr(self, 'version', 1) self.transaction.expiration = getattr(self, 'expiration', 0) if self.transaction.type != 0: @@ -81,19 +91,19 @@ def serialize(self, skip_signature=False, skip_second_signature=False, skip_mult def schnorr_verify(self): return self.transaction.verify_schnorr() - def schnorr_verify_second(self, secondPublicKey): - return self.transaction.verify_schnorr_secondsig(secondPublicKey) + def verify_secondsig_schnorr(self, secondPublicKey): + return self.transaction.verify_secondsig_schnorr(secondPublicKey) - def schnorr_verify_multisig(self): - return self.transaction.verify_schnorr_multisig() + def verify_multisig_schnorr(self): + return self.transaction.verify_multisig_schnorr() def set_nonce(self, nonce): self.transaction.nonce = nonce - def set_amount(self, amount): + def set_amount(self, amount: int): self.transaction.amount = amount - def set_sender_public_key(self, public_key): + def set_sender_public_key(self, public_key: str): self.transaction.senderPublicKey = public_key def set_expiration(self, expiration: int): diff --git a/crypto/transactions/builder/delegate_registration.py b/crypto/transactions/builder/delegate_registration.py index 4dd17dbb..9010606c 100644 --- a/crypto/transactions/builder/delegate_registration.py +++ b/crypto/transactions/builder/delegate_registration.py @@ -1,13 +1,12 @@ +from typing import Optional + from crypto.constants import TRANSACTION_DELEGATE_REGISTRATION -from crypto.identity.public_key import PublicKey from crypto.transactions.builder.base import BaseTransactionBuilder - class DelegateRegistration(BaseTransactionBuilder): - transaction_type = TRANSACTION_DELEGATE_REGISTRATION - def __init__(self, public_key, fee=None): + def __init__(self, public_key: str, fee: Optional[int] = None): """Create a delegate registration transaction Args: @@ -20,8 +19,3 @@ def __init__(self, public_key, fee=None): if fee: self.transaction.fee = fee - - def sign(self, passphrase): - public_key = PublicKey.from_passphrase(passphrase) - #self.transaction.asset['delegate']['publicKey'] = public_key - super().sign(passphrase) diff --git a/crypto/transactions/builder/delegate_resignation.py b/crypto/transactions/builder/delegate_resignation.py index 4075dd81..145d46b3 100644 --- a/crypto/transactions/builder/delegate_resignation.py +++ b/crypto/transactions/builder/delegate_resignation.py @@ -1,12 +1,12 @@ +from typing import Optional + from crypto.constants import TRANSACTION_DELEGATE_RESIGNATION, TRANSACTION_TYPE_GROUP from crypto.transactions.builder.base import BaseTransactionBuilder - class DelegateResignation(BaseTransactionBuilder): - transaction_type = TRANSACTION_DELEGATE_RESIGNATION - def __init__(self, fee=None): + def __init__(self, fee: Optional[int] = None): """Create a delegate resignation transaction Args: @@ -19,5 +19,5 @@ def __init__(self, fee=None): if fee: self.transaction.fee = fee - def get_type_group(self): + def get_type_group(self) -> int: return TRANSACTION_TYPE_GROUP.CORE.value diff --git a/crypto/transactions/builder/multi_payment.py b/crypto/transactions/builder/multi_payment.py index 8b768510..131f0e8b 100644 --- a/crypto/transactions/builder/multi_payment.py +++ b/crypto/transactions/builder/multi_payment.py @@ -1,12 +1,12 @@ +from typing import Optional + from crypto.constants import TRANSACTION_MULTI_PAYMENT, TRANSACTION_TYPE_GROUP from crypto.transactions.builder.base import BaseTransactionBuilder - class MultiPayment(BaseTransactionBuilder): - transaction_type = TRANSACTION_MULTI_PAYMENT - def __init__(self, vendorField=None, fee=None): + def __init__(self, vendorField: Optional[str] = None, fee: Optional[int] = None): """Create a multi payment transaction Args: @@ -24,8 +24,8 @@ def __init__(self, vendorField=None, fee=None): if fee: self.transaction.fee = fee - def get_type_group(self): + def get_type_group(self) -> int: return TRANSACTION_TYPE_GROUP.CORE.value - def add_payment(self, amount, recipient_id): + def add_payment(self, amount: int, recipient_id: str): self.transaction.asset['payments'].append({'amount': amount, 'recipientId': recipient_id}) diff --git a/crypto/transactions/builder/multi_signature_registration.py b/crypto/transactions/builder/multi_signature_registration.py index 56b664d6..b2671585 100644 --- a/crypto/transactions/builder/multi_signature_registration.py +++ b/crypto/transactions/builder/multi_signature_registration.py @@ -1,12 +1,14 @@ -from crypto.constants import TRANSACTION_FEES, TRANSACTION_MULTI_SIGNATURE_REGISTRATION -from crypto.transactions.builder.base import BaseTransactionBuilder +from typing import Optional +from crypto.configuration.fee import get_fee +from crypto.constants import TRANSACTION_MULTI_SIGNATURE_REGISTRATION +from crypto.transactions.builder.base import BaseTransactionBuilder class MultiSignatureRegistration(BaseTransactionBuilder): transaction_type = TRANSACTION_MULTI_SIGNATURE_REGISTRATION - def __init__(self, fee=None): + def __init__(self, fee: Optional[int] = None): """Create a new multi signature transaction Args: @@ -31,7 +33,10 @@ def set_public_keys(self, public_keys): self.transaction.asset['multiSignature']['publicKeys'] = public_keys self.transaction.fee = (len(public_keys) + 1) * self.transaction.fee - def add_participant(self, public_key): + def add_participant(self, public_key: str): self.transaction.asset['multiSignature']['publicKeys'].append(public_key) - self.transaction.fee = (len(self.transaction.asset['multiSignature']['publicKeys']) + 1) * \ - TRANSACTION_FEES.get(TRANSACTION_MULTI_SIGNATURE_REGISTRATION) + + public_key_count = (len(self.transaction.asset['multiSignature']['publicKeys']) + 1) + transaction_fee = get_fee(TRANSACTION_MULTI_SIGNATURE_REGISTRATION) + + self.transaction.fee = public_key_count * transaction_fee diff --git a/crypto/transactions/builder/transfer.py b/crypto/transactions/builder/transfer.py index 5d2f15ce..37d245aa 100644 --- a/crypto/transactions/builder/transfer.py +++ b/crypto/transactions/builder/transfer.py @@ -1,12 +1,13 @@ +from typing import Optional + from crypto.constants import TRANSACTION_TRANSFER from crypto.transactions.builder.base import BaseTransactionBuilder - class Transfer(BaseTransactionBuilder): transaction_type = TRANSACTION_TRANSFER - def __init__(self, recipientId, amount, vendorField=None, fee=None): + def __init__(self, recipientId: str, amount: int, vendorField: Optional[str] = None, fee: Optional[int] = None): """Create a transfer transaction Args: diff --git a/crypto/transactions/builder/vote.py b/crypto/transactions/builder/vote.py index 8086d3e5..28cceffd 100644 --- a/crypto/transactions/builder/vote.py +++ b/crypto/transactions/builder/vote.py @@ -1,8 +1,8 @@ +from typing import Optional + from crypto.constants import TRANSACTION_VOTE from crypto.identity.address import address_from_passphrase from crypto.transactions.builder.base import BaseTransactionBuilder -from typing import Optional - class Vote(BaseTransactionBuilder): transaction_type = TRANSACTION_VOTE @@ -25,4 +25,5 @@ def __init__(self, votes: Optional[list[str]] = None, unvotes: Optional[list[str def sign(self, passphrase): self.transaction.recipientId = address_from_passphrase(passphrase) + super().sign(passphrase) diff --git a/crypto/transactions/deserializer.py b/crypto/transactions/deserializer.py index fa28aae9..01395f11 100644 --- a/crypto/transactions/deserializer.py +++ b/crypto/transactions/deserializer.py @@ -1,31 +1,26 @@ import inspect from binascii import hexlify, unhexlify -from hashlib import sha256 from importlib import import_module +from typing import Union from binary.unsigned_integer.reader import read_bit8, read_bit16, read_bit32, read_bit64 from crypto.constants import TRANSACTION_TYPES from crypto.transactions.deserializers.base import BaseDeserializer - +from crypto.transactions.transaction import Transaction class Deserializer(object): + serialized: bytes - serialized = None - - def __init__(self, serialized): + def __init__(self, serialized: Union[bytes, str]): self.serialized = unhexlify(serialized) - def deserialize(self): + def deserialize(self) -> Transaction: """Deserialize transaction Returns: - object: returns Transaction resource object + Transaction: returns transaction object """ - # circular import with transaction.py :( - I'm thinking of just returning a dict here - # which then needs to be passed to a Transaction object, instead of returning a Transaction - # object - from crypto.transactions.transaction import Transaction transaction = Transaction() transaction.version = read_bit8(self.serialized, offset=1) @@ -53,16 +48,17 @@ def deserialize(self): return transaction - def _handle_transaction_type(self, asset_offset, transaction): + def _handle_transaction_type(self, asset_offset: int, transaction): """Handle deserialization for a given transaction type Args: asset_offset (int): - transaction (object): Transaction resource object + transaction (Transaction): Transaction resource object Returns: - object: Transaction resource object of currently deserialized data + Transaction: Transaction object of currently deserialized data """ + deserializer_name = TRANSACTION_TYPES[transaction.type] module = import_module('crypto.transactions.deserializers.{}'.format(deserializer_name)) for attr in dir(module): diff --git a/crypto/transactions/deserializers/base.py b/crypto/transactions/deserializers/base.py index 6f712e58..6cbfbed9 100644 --- a/crypto/transactions/deserializers/base.py +++ b/crypto/transactions/deserializers/base.py @@ -1,10 +1,11 @@ -class BaseDeserializer(object): +from crypto.transactions.transaction import Transaction - serialized = None - asset_offset = None - transaction = None +class BaseDeserializer(object): + serialized: bytes + asset_offset: int + transaction: Transaction - def __init__(self, serialized, asset_offset, transaction): + def __init__(self, serialized: bytes, asset_offset: int, transaction: Transaction): self.serialized = serialized self.asset_offset = asset_offset self.transaction = transaction diff --git a/crypto/transactions/deserializers/delegate_registration.py b/crypto/transactions/deserializers/delegate_registration.py index d3594716..ce6d8603 100644 --- a/crypto/transactions/deserializers/delegate_registration.py +++ b/crypto/transactions/deserializers/delegate_registration.py @@ -2,9 +2,7 @@ from crypto.transactions.deserializers.base import BaseDeserializer - class DelegateRegistrationDeserializer(BaseDeserializer): - def deserialize(self): starting_position = int(self.asset_offset / 2) diff --git a/crypto/transactions/deserializers/delegate_resignation.py b/crypto/transactions/deserializers/delegate_resignation.py index eaf4bf8a..06757b1e 100644 --- a/crypto/transactions/deserializers/delegate_resignation.py +++ b/crypto/transactions/deserializers/delegate_resignation.py @@ -2,9 +2,7 @@ from crypto.transactions.deserializers.base import BaseDeserializer - class DelegateResignationDeserializer(BaseDeserializer): - def deserialize(self): self.transaction.parse_signatures( hexlify(self.serialized).decode(), diff --git a/crypto/transactions/deserializers/multi_payment.py b/crypto/transactions/deserializers/multi_payment.py index e1c63f1e..23f9adac 100644 --- a/crypto/transactions/deserializers/multi_payment.py +++ b/crypto/transactions/deserializers/multi_payment.py @@ -1,13 +1,10 @@ from binascii import hexlify, unhexlify - from binary.unsigned_integer.reader import read_bit16, read_bit64 from crypto.identity.address import get_checksum_address from crypto.transactions.deserializers.base import BaseDeserializer - class MultiPaymentDeserializer(BaseDeserializer): - def deserialize(self): starting_position = int(self.asset_offset / 2) diff --git a/crypto/transactions/deserializers/multi_signature_registration.py b/crypto/transactions/deserializers/multi_signature_registration.py index f8cc46da..c6381fcc 100644 --- a/crypto/transactions/deserializers/multi_signature_registration.py +++ b/crypto/transactions/deserializers/multi_signature_registration.py @@ -1,12 +1,9 @@ from binascii import hexlify - from binary.unsigned_integer.reader import read_bit8 from crypto.transactions.deserializers.base import BaseDeserializer - class MultiSignatureRegistrationDeserializer(BaseDeserializer): - def deserialize(self): starting_position = int(self.asset_offset / 2) diff --git a/crypto/transactions/deserializers/transfer.py b/crypto/transactions/deserializers/transfer.py index cfe98d91..eb78c876 100644 --- a/crypto/transactions/deserializers/transfer.py +++ b/crypto/transactions/deserializers/transfer.py @@ -1,5 +1,4 @@ from binascii import hexlify, unhexlify - from binary.unsigned_integer.reader import read_bit32, read_bit64 from crypto.identity.address import get_checksum_address diff --git a/crypto/transactions/deserializers/vote.py b/crypto/transactions/deserializers/vote.py index 2c087b6a..a04c1891 100644 --- a/crypto/transactions/deserializers/vote.py +++ b/crypto/transactions/deserializers/vote.py @@ -1,5 +1,4 @@ from binascii import hexlify - from binary.unsigned_integer.reader import read_bit8 from crypto.transactions.deserializers.base import BaseDeserializer diff --git a/crypto/transactions/serializer.py b/crypto/transactions/serializer.py index 20b6e4c5..f9bdf500 100644 --- a/crypto/transactions/serializer.py +++ b/crypto/transactions/serializer.py @@ -10,21 +10,19 @@ from crypto.exceptions import ArkSerializerException from crypto.transactions.serializers.base import BaseSerializer - class Serializer(object): - - transaction = None + transaction: dict = {} def __init__(self, transaction): if not transaction: raise ArkSerializerException('No transaction data provided') self.transaction = transaction - def serialize(self, skip_signature=True, skip_second_signature=True, skip_multi_signature=True, raw=False): + def serialize_bytes(self, skip_signature: bool = True, skip_second_signature: bool = True, skip_multi_signature: bool = True) -> bytes: """Perform AIP11 compliant serialization Returns: - bytes: bytes string + bytes: Serialized bytes """ network_config = get_network() bytes_data = bytes() @@ -41,21 +39,34 @@ def serialize(self, skip_signature=True, skip_second_signature=True, skip_multi_ bytes_data += write_bit64(self.transaction.get('fee')) if self.transaction.get('vendorField'): - vendorFieldLength = len(self.transaction.get('vendorField')) + vendorFieldLength = len(self.transaction.get('vendorField') or '') + bytes_data += write_bit8(vendorFieldLength) bytes_data += self.transaction['vendorField'].encode() elif self.transaction.get('vendorFieldHex'): vendorField_hex_length = len(self.transaction['vendorFieldHex']) + bytes_data += write_bit8(vendorField_hex_length / 2) bytes_data += self.transaction['vendorFieldHex'] else: bytes_data += write_bit8(0x00) + bytes_data = self._handle_transaction_type(bytes_data) bytes_data = self._handle_signature(bytes_data, skip_signature, skip_second_signature, skip_multi_signature) - return bytes_data if raw else hexlify(bytes_data).decode() + return bytes_data + + def serialize(self, skip_signature: bool = True, skip_second_signature: bool = True, skip_multi_signature: bool = True) -> str: + """Perform AIP11 compliant serialization - def _handle_transaction_type(self, bytes_data): + Returns: + str: Serialized string + """ + bytes_data = self.serialize_bytes(skip_signature, skip_second_signature, skip_multi_signature) + + return hexlify(bytes_data).decode() + + def _handle_transaction_type(self, bytes_data) -> bytes: """Serialize transaction specific data (eg. delegate registration) Args: @@ -78,9 +89,10 @@ def _handle_transaction_type(self, bytes_data): # this attribute is actually a specific serializer that we want to use serializer = attribute break + return serializer(self.transaction, bytes_data).serialize() - def _handle_signature(self, bytes_data, skip_signature, skip_second_signature, skip_multi_signature): + def _handle_signature(self, bytes_data, skip_signature, skip_second_signature, skip_multi_signature) -> bytes: """Serialize signature data of the transaction Args: diff --git a/crypto/transactions/serializers/base.py b/crypto/transactions/serializers/base.py index 7fca2c11..84cbb955 100644 --- a/crypto/transactions/serializers/base.py +++ b/crypto/transactions/serializers/base.py @@ -1,9 +1,8 @@ class BaseSerializer(object): + transaction: dict + bytes_data: bytes - transaction = None - bytes_data = None - - def __init__(self, transaction, bytes_data=bytes()): + def __init__(self, transaction: dict, bytes_data: bytes = bytes()): self.transaction = transaction self.bytes_data = bytes_data diff --git a/crypto/transactions/serializers/delegate_registration.py b/crypto/transactions/serializers/delegate_registration.py index 7db5c32e..c6925ec9 100644 --- a/crypto/transactions/serializers/delegate_registration.py +++ b/crypto/transactions/serializers/delegate_registration.py @@ -1,15 +1,12 @@ -from binascii import hexlify, unhexlify - -from binary.unsigned_integer.writer import write_bit8 +from binary.hex.writer import write_high from crypto.transactions.serializers.base import BaseSerializer -from binary.hex.writer import write_high class DelegateRegistrationSerializer(BaseSerializer): """Serializer handling delegate registration data """ - def serialize(self): + def serialize(self) -> bytes: delegate_bytes = self.transaction['asset']['validatorPublicKey'].encode() self.bytes_data += write_high(delegate_bytes) diff --git a/crypto/transactions/serializers/delegate_resignation.py b/crypto/transactions/serializers/delegate_resignation.py index fb6c7a20..07abb148 100644 --- a/crypto/transactions/serializers/delegate_resignation.py +++ b/crypto/transactions/serializers/delegate_resignation.py @@ -1,9 +1,8 @@ from crypto.transactions.serializers.base import BaseSerializer - class DelegateResignationSerializer(BaseSerializer): """Serializer handling delegate resignation data """ - def serialize(self): + def serialize(self) -> bytes: return self.bytes_data diff --git a/crypto/transactions/serializers/multi_payment.py b/crypto/transactions/serializers/multi_payment.py index a328b7c4..50817681 100644 --- a/crypto/transactions/serializers/multi_payment.py +++ b/crypto/transactions/serializers/multi_payment.py @@ -3,12 +3,11 @@ from crypto.transactions.serializers.base import BaseSerializer - class MultiPaymentSerializer(BaseSerializer): """Serializer handling multi payment data """ - def serialize(self): + def serialize(self) -> bytes: self.bytes_data += write_bit16(len(self.transaction['asset']['payments'])) for payment in self.transaction['asset']['payments']: diff --git a/crypto/transactions/serializers/multi_signature_registration.py b/crypto/transactions/serializers/multi_signature_registration.py index b87fe0bc..3a594661 100644 --- a/crypto/transactions/serializers/multi_signature_registration.py +++ b/crypto/transactions/serializers/multi_signature_registration.py @@ -1,15 +1,13 @@ from binascii import unhexlify - from binary.unsigned_integer.writer import write_bit8 from crypto.transactions.serializers.base import BaseSerializer - class MultiSignatureSerializer(BaseSerializer): """Serializer handling delegate registration data """ - def serialize(self): + def serialize(self) -> bytes: public_keys_length = len(self.transaction['asset']['multiSignature']['publicKeys']) self.bytes_data += write_bit8(self.transaction['asset']['multiSignature']['min']) self.bytes_data += write_bit8(public_keys_length) diff --git a/crypto/transactions/serializers/transfer.py b/crypto/transactions/serializers/transfer.py index cbc69129..5a2edbb5 100644 --- a/crypto/transactions/serializers/transfer.py +++ b/crypto/transactions/serializers/transfer.py @@ -3,12 +3,11 @@ from crypto.transactions.serializers.base import BaseSerializer - class TransferSerializer(BaseSerializer): """Serializer handling transfer data """ - def serialize(self): + def serialize(self) -> bytes: self.bytes_data += write_bit64(self.transaction['amount']) self.bytes_data += write_bit32(self.transaction.get('expiration', 0)) diff --git a/crypto/transactions/serializers/vote.py b/crypto/transactions/serializers/vote.py index 4adbe070..30c6d69d 100644 --- a/crypto/transactions/serializers/vote.py +++ b/crypto/transactions/serializers/vote.py @@ -1,15 +1,13 @@ from binascii import unhexlify - from binary.unsigned_integer.writer import write_bit8 from crypto.transactions.serializers.base import BaseSerializer - class VoteSerializer(BaseSerializer): """Serializer handling vote data """ - def serialize(self): + def serialize(self) -> bytes: vote_bytes = [] unvote_bytes = [] diff --git a/crypto/transactions/signature.py b/crypto/transactions/signature.py index 03ac4ac0..106d0608 100644 --- a/crypto/transactions/signature.py +++ b/crypto/transactions/signature.py @@ -4,7 +4,7 @@ class Signature: @staticmethod - def verify(signature, message, publicKey: bytes): + def verify(signature, message, publicKey: Union[bytes, str]): # Remove leading byte ('02' / '03') from ECDSA key if (len(publicKey) == 33): publicKey = publicKey[1:] diff --git a/crypto/transactions/transaction.py b/crypto/transactions/transaction.py index 3f8fcc38..966dafa6 100644 --- a/crypto/transactions/transaction.py +++ b/crypto/transactions/transaction.py @@ -1,14 +1,12 @@ import json -from binascii import unhexlify from hashlib import sha256 +from typing import Optional from binary.hex.writer import write_high from binary.unsigned_integer.writer import write_bit8 from crypto.constants import TRANSACTION_DELEGATE_REGISTRATION, TRANSACTION_MULTI_SIGNATURE_REGISTRATION, TRANSACTION_VOTE from crypto.exceptions import ArkInvalidTransaction -from crypto.schnorr import schnorr -from crypto.transactions.deserializer import Deserializer from crypto.transactions.serializer import Serializer from crypto.transactions.signature import Signature @@ -35,10 +33,25 @@ 'expiration': None } - class Transaction(object): - - def __init__(self, *args, **kwargs): + id: str + type: int + fee: int + nonce: int + typeGroup: int + signatures: list + version: int + expiration: int + type: int + amount: int + recipientId: str + senderPublicKey: str + asset: dict + vendorField: Optional[bytes] + vendorFieldHex: bytes + network: int + + def __init__(self, **kwargs): for attribute, attribute_value in TRANSACTION_ATTRIBUTES.items(): if callable(attribute_value): attribute_value = attribute_value() @@ -73,7 +86,7 @@ def to_json(self): data = self.to_dict() return json.dumps(data) - def to_bytes(self, skip_signature=True, skip_second_signature=True, skip_multi_signature=True): + def to_bytes(self, skip_signature=True, skip_second_signature=True, skip_multi_signature=True) -> bytes: """Convert the transaction to its byte representation Args: @@ -84,20 +97,16 @@ def to_bytes(self, skip_signature=True, skip_second_signature=True, skip_multi_s Returns: bytes: bytes representation of the transaction """ - return Serializer(self.to_dict()).serialize(skip_signature=skip_signature, + return Serializer(self.to_dict()).serialize_bytes(skip_signature=skip_signature, skip_second_signature=skip_second_signature, - skip_multi_signature=skip_multi_signature, - raw=True) + skip_multi_signature=skip_multi_signature) - def parse_signatures(self, serialized, start_offset): + def parse_signatures(self, serialized: str, start_offset: int): """Parse the signature, second signature and multi signatures Args: serialized (str): parses a given serialized string start_offset (int): - - Returns: - None: methods returns nothing """ signature_end_offset = start_offset + (64 * 2) @@ -122,9 +131,7 @@ def parse_signatures(self, serialized, start_offset): signature_formatted = signature_index + signature self.signatures.append(signature_formatted) - return - - def serialize(self, skip_signature=True, skip_second_signature=True, skip_multi_signature=True): + def serialize(self, skip_signature=True, skip_second_signature=True, skip_multi_signature=True) -> str: """Perform AIP11 compliant serialization. Args: @@ -136,17 +143,21 @@ def serialize(self, skip_signature=True, skip_second_signature=True, skip_multi_ str: Serialized string """ data = self.to_dict() + return Serializer(data).serialize(skip_signature, skip_second_signature, skip_multi_signature) - def deserialize(self, serialized): + @staticmethod + def deserialize(serialized: bytes): """Perform AIP11 compliant deserialization. Args: - serialized (str): parses a given serialized string + serialized (bytes): parses a given serialized string Returns: crypto.transactions.transaction.Transaction: Transaction """ + from crypto.transactions.deserializer import Deserializer + return Deserializer(serialized).deserialize() def verify_schnorr(self): @@ -160,7 +171,7 @@ def verify_schnorr(self): return is_valid - def verify_schnorr_secondsig(self, secondPublicKey): + def verify_secondsig_schnorr(self, secondPublicKey: bytes): """Verify the transaction. Method will raise an exception if invalid, if it's valid it will returns True """ @@ -169,7 +180,9 @@ def verify_schnorr_secondsig(self, secondPublicKey): if not is_valid: raise ArkInvalidTransaction('Transaction could not be verified') - def verify_schnorr_multisig(self): + return is_valid + + def verify_multisig_schnorr(self): """Verify the multisignatures transaction. Method will raise an exception if invalid, it will returns True """ @@ -180,7 +193,7 @@ def verify_schnorr_multisig(self): return is_valid - def _handle_transaction_type(self, bytes_data): + def _handle_transaction_type(self, bytes_data) -> bytes: """Handled each transaction type differently Args: @@ -190,10 +203,7 @@ def _handle_transaction_type(self, bytes_data): NotImplementedError: raised only if the child transaction doesn't implement this required method """ - if self.type == TRANSACTION_SECOND_SIGNATURE_REGISTRATION: - public_key = self.asset['signature']['publicKey'] - bytes_data += unhexlify(public_key) - elif self.type == TRANSACTION_DELEGATE_REGISTRATION: + if self.type == TRANSACTION_DELEGATE_REGISTRATION: bytes_data += self.asset['delegate']['username'].encode() elif self.type == TRANSACTION_VOTE: bytes_data += ''.join(self.asset['votes']).encode() @@ -203,7 +213,7 @@ def _handle_transaction_type(self, bytes_data): return bytes_data - def _handle_signature(self, bytes_data, skip_signature, skip_second_signature, skip_multi_signature): + def _handle_signature(self, bytes_data, skip_signature, skip_second_signature, skip_multi_signature) -> bytes: """Internal method, used to handle the signature Args: diff --git a/tests/transactions/builder/test_multi_signature_registration.py b/tests/transactions/builder/test_multi_signature_registration.py index f720742c..025de87e 100644 --- a/tests/transactions/builder/test_multi_signature_registration.py +++ b/tests/transactions/builder/test_multi_signature_registration.py @@ -44,4 +44,4 @@ def test_multi_signature_registration_transaction(passphrase): assert transaction_dict['asset']['multiSignature']['publicKeys'][1] == publicKeys[1] assert transaction_dict['asset']['multiSignature']['publicKeys'][2] == '03860d76b1df09659ac282cea3da5bd84fc45729f348a4a8e5f802186be72dc17f' - transaction.schnorr_verify_multisig() # if no exception is raised, it means the transaction is valid + transaction.verify_multisig_schnorr() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_transfer.py b/tests/transactions/builder/test_transfer.py index 700a63aa..75aa73dc 100644 --- a/tests/transactions/builder/test_transfer.py +++ b/tests/transactions/builder/test_transfer.py @@ -111,7 +111,7 @@ def test_transfer_secondsign_transaction(passphrase): assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid - transaction.schnorr_verify_second(PublicKey.from_passphrase('second top secret passphrase')) # if no exception is raised, it means the transaction is valid + transaction.verify_secondsig_schnorr(PublicKey.from_passphrase('second top secret passphrase')) # if no exception is raised, it means the transaction is valid def test_parse_signatures(transaction_type_0): diff --git a/tests/transactions/deserializers/test_multi_signature_registration.py b/tests/transactions/deserializers/test_multi_signature_registration.py index 5488ed21..db1dea0a 100644 --- a/tests/transactions/deserializers/test_multi_signature_registration.py +++ b/tests/transactions/deserializers/test_multi_signature_registration.py @@ -29,4 +29,4 @@ def test_multi_signature_registration_deserializer(): '021d03e19f1e39ac9b985b867e8362002df387fd778a682218bc3e6ce4b5c7f59eaa6f893ffb8deec2e4c9b28287417e2ab7b427356ca74d790f7fb63b6f8dcbd5' # noqa ] - actual.verify_schnorr_multisig() + actual.verify_multisig_schnorr() From bac823a2c0e41c3f43f462c89339809eb9972e11 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Tue, 30 Jul 2024 15:58:41 +0100 Subject: [PATCH 56/68] refactor: move serialized test data to fixtures (#136) --- .../test_delegate_registration.py | 4 +- .../test_delegate_resignation.py | 4 +- .../deserializers/test_multi_payment.py | 34 ++++++++------- .../test_multi_signature_registration.py | 41 ++++++++----------- .../deserializers/test_transfer.py | 31 +++++++------- 5 files changed, 48 insertions(+), 66 deletions(-) diff --git a/tests/transactions/deserializers/test_delegate_registration.py b/tests/transactions/deserializers/test_delegate_registration.py index a521c0d5..fbafac82 100644 --- a/tests/transactions/deserializers/test_delegate_registration.py +++ b/tests/transactions/deserializers/test_delegate_registration.py @@ -2,9 +2,7 @@ def test_delegate_registration_deserializer(transaction_type_2): - serialized = transaction_type_2['serialized'] - - deserializer = Deserializer(serialized) + deserializer = Deserializer(transaction_type_2['serialized']) actual = deserializer.deserialize() assert actual.version == transaction_type_2['version'] diff --git a/tests/transactions/deserializers/test_delegate_resignation.py b/tests/transactions/deserializers/test_delegate_resignation.py index 48b41459..5b23c83c 100644 --- a/tests/transactions/deserializers/test_delegate_resignation.py +++ b/tests/transactions/deserializers/test_delegate_resignation.py @@ -2,9 +2,7 @@ def test_delegate_resignation_deserializer(transaction_type_7): - serialized = transaction_type_7['serialized'] - - deserializer = Deserializer(serialized) + deserializer = Deserializer(transaction_type_7['serialized']) actual = deserializer.deserialize() assert actual.version == transaction_type_7['version'] diff --git a/tests/transactions/deserializers/test_multi_payment.py b/tests/transactions/deserializers/test_multi_payment.py index e9fd2e63..3f65c6d0 100644 --- a/tests/transactions/deserializers/test_multi_payment.py +++ b/tests/transactions/deserializers/test_multi_payment.py @@ -1,25 +1,23 @@ from crypto.transactions.deserializer import Deserializer -def test_multi_payment_deserializer(): - serialized = 'ff011e0100000006000500000000000000023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3809698000000000000020000e1f505000000006f0182a0cc707b055322ccf6d4cb6a5aff1aeb2200c2eb0b00000000b693449adda7efc015d87944eae8b7c37eb1690a6031b2c73de394a12b4988f43f0fe13cd7782469857914b92e6c05561ff6d7634783a89157e487dc38f104bf40fc279c38602dbfba0cc679dfe631ba4c77929c' # noqa - - deserializer = Deserializer(serialized) +def test_multi_payment_deserializer(transaction_type_6): + deserializer = Deserializer(transaction_type_6['serialized']) actual = deserializer.deserialize() - assert actual.version == 1 - assert actual.network == 30 - assert actual.typeGroup == 1 - assert actual.type == 6 - assert actual.nonce == 5 - assert actual.senderPublicKey == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' - assert actual.fee == 10000000 - assert actual.amount == 0 - assert actual.asset['payments'][0]['amount'] == 100000000 # noqa - assert actual.asset['payments'][0]['recipientId'] == '0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22' # noqa - assert actual.asset['payments'][1]['amount'] == 200000000 # noqa - assert actual.asset['payments'][1]['recipientId'] == '0xb693449AdDa7EFc015D87944EAE8b7C37EB1690A' # noqa - assert actual.signature == '6031b2c73de394a12b4988f43f0fe13cd7782469857914b92e6c05561ff6d7634783a89157e487dc38f104bf40fc279c38602dbfba0cc679dfe631ba4c77929c' - assert actual.id == '46799c223ff8f19d00c3e3bd617c6692f3cab1aed33dbc99ad9706d22f709c5e' + assert actual.version == transaction_type_6['version'] + assert actual.network == transaction_type_6['network'] + assert actual.typeGroup == transaction_type_6['typeGroup'] + assert actual.type == transaction_type_6['type'] + assert actual.nonce == transaction_type_6['nonce'] + assert actual.senderPublicKey == transaction_type_6['senderPublicKey'] + assert actual.fee == transaction_type_6['fee'] + assert actual.amount == transaction_type_6['amount'] + assert actual.signature == transaction_type_6['signature'] + assert actual.id == transaction_type_6['id'] + + for index, payment in enumerate(transaction_type_6['asset']['payments']): + assert actual.asset['payments'][index]['amount'] == payment['amount'] + assert actual.asset['payments'][index]['recipientId'] == payment['recipientId'] actual.verify_schnorr() diff --git a/tests/transactions/deserializers/test_multi_signature_registration.py b/tests/transactions/deserializers/test_multi_signature_registration.py index db1dea0a..d22b0c07 100644 --- a/tests/transactions/deserializers/test_multi_signature_registration.py +++ b/tests/transactions/deserializers/test_multi_signature_registration.py @@ -1,32 +1,23 @@ from crypto.transactions.deserializer import Deserializer -def test_multi_signature_registration_deserializer(): - serialized = 'ff011e0100000004000500000000000000023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d30065cd1d00000000000203029fab3cb2f5e248ae7cbb4de646741da4d73c493b2a03ab5c71507fb2c0dcca9203629f9dbf7f1e91cefa845126189816ceae357bdd1f41bd14787318a7d5b55d48027941d2059f89a26d89e87d3385e261a0ede1234aaeaa487012b69d6b67962dc504aee2652b4a41350b9821c261bb0a3c3492146a6e0ebda4e28a185d6c2e9eb7702b4889e8b9b5fadd989e71dbb7527b3be071e15cf3a93165e8f1b8c7b16974008c7c7018082d9afddd70652f559377a941240cf797819fe02f657d4509c8bcb133156741163cfb8e9b4db6ef398bdd5f94a24e1f0134d04783391bc13370201b015306973b045d83191ce57c56f097b79c0771a94310ee67aed8b96e1bb7f497e451fe569c297a857f840f46b801534179e87a5e07015fb03727746be450e925d6021d03e19f1e39ac9b985b867e8362002df387fd778a682218bc3e6ce4b5c7f59eaa6f893ffb8deec2e4c9b28287417e2ab7b427356ca74d790f7fb63b6f8dcbd5' # noqa - - deserializer = Deserializer(serialized) +def test_multi_signature_registration_deserializer(transaction_type_4): + deserializer = Deserializer(transaction_type_4['serialized']) actual = deserializer.deserialize() - data = actual.to_dict() - assert data['amount'] == 0 - assert data['nonce'] == 5 - assert data['version'] == 1 - assert data['network'] == 30 - assert data['typeGroup'] == 1 - assert data['fee'] == 500000000 - assert data['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' - assert data['signature'] == '04aee2652b4a41350b9821c261bb0a3c3492146a6e0ebda4e28a185d6c2e9eb7702b4889e8b9b5fadd989e71dbb7527b3be071e15cf3a93165e8f1b8c7b16974' - assert data['id'] == '75affed80fcad541787fd673fafa6b90561feac2d3f4a2dae857641109c44787' - assert data['asset']['multiSignature']['min'] == 2 - assert data['asset']['multiSignature']['publicKeys'] == [ - '029fab3cb2f5e248ae7cbb4de646741da4d73c493b2a03ab5c71507fb2c0dcca92', - '03629f9dbf7f1e91cefa845126189816ceae357bdd1f41bd14787318a7d5b55d48', - '027941d2059f89a26d89e87d3385e261a0ede1234aaeaa487012b69d6b67962dc5' - ] - assert data['signatures'] == [ - '008c7c7018082d9afddd70652f559377a941240cf797819fe02f657d4509c8bcb133156741163cfb8e9b4db6ef398bdd5f94a24e1f0134d04783391bc13370201b', # noqa - '015306973b045d83191ce57c56f097b79c0771a94310ee67aed8b96e1bb7f497e451fe569c297a857f840f46b801534179e87a5e07015fb03727746be450e925d6', # noqa - '021d03e19f1e39ac9b985b867e8362002df387fd778a682218bc3e6ce4b5c7f59eaa6f893ffb8deec2e4c9b28287417e2ab7b427356ca74d790f7fb63b6f8dcbd5' # noqa - ] + assert actual.version == transaction_type_4['version'] + assert actual.network == transaction_type_4['network'] + assert actual.typeGroup == transaction_type_4['typeGroup'] + assert actual.type == transaction_type_4['type'] + assert actual.nonce == transaction_type_4['nonce'] + assert actual.senderPublicKey == transaction_type_4['senderPublicKey'] + assert actual.fee == transaction_type_4['fee'] + assert actual.amount == transaction_type_4['amount'] + assert actual.signature == transaction_type_4['signature'] + assert actual.id == transaction_type_4['id'] + + assert actual.asset['multiSignature']['min'] == transaction_type_4['asset']['multiSignature']['min'] + assert actual.asset['multiSignature']['publicKeys'] == transaction_type_4['asset']['multiSignature']['publicKeys'] + assert actual.signatures == transaction_type_4['signatures'] actual.verify_multisig_schnorr() diff --git a/tests/transactions/deserializers/test_transfer.py b/tests/transactions/deserializers/test_transfer.py index 413923d2..ec033f96 100644 --- a/tests/transactions/deserializers/test_transfer.py +++ b/tests/transactions/deserializers/test_transfer.py @@ -1,24 +1,21 @@ -import pytest from crypto.transactions.deserializer import Deserializer -def test_transfer_deserializer(): - serialized = 'ff011e0100000000000300000000000000023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d38096980000000000000100000000000000000000006f0182a0cc707b055322ccf6d4cb6a5aff1aeb221c9e1c2493b1e597d682f31700989f76eec068375d4ce5e1721cc9265b66c9b551ea1afa28c4f443120e43cff5cc7f137c4572749b0467561038127b4eafded9' - - deserializer = Deserializer(serialized) +def test_transfer_deserializer(transaction_type_0): + deserializer = Deserializer(transaction_type_0['serialized']) actual = deserializer.deserialize() - assert actual.version == 1 - assert actual.network == 30 - assert actual.typeGroup == 1 - assert actual.type == 0 - assert actual.nonce == 3 - assert actual.senderPublicKey == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' # noqa - assert actual.fee == 10000000 - assert actual.amount == 1 - assert actual.expiration == 0 - assert actual.recipientId == '0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22' - assert actual.signature == '1c9e1c2493b1e597d682f31700989f76eec068375d4ce5e1721cc9265b66c9b551ea1afa28c4f443120e43cff5cc7f137c4572749b0467561038127b4eafded9' - assert actual.id == '8d0584d1bbc8fa7a6e4e7904b884f69399df56dcfe6458c2bedc283c55604def' + assert actual.version == transaction_type_0['version'] + assert actual.network == transaction_type_0['network'] + assert actual.typeGroup == transaction_type_0['typeGroup'] + assert actual.type == transaction_type_0['type'] + assert actual.nonce == transaction_type_0['nonce'] + assert actual.senderPublicKey == transaction_type_0['senderPublicKey'] + assert actual.fee == transaction_type_0['fee'] + assert actual.amount == transaction_type_0['amount'] + assert actual.expiration == transaction_type_0['expiration'] + assert actual.recipientId == transaction_type_0['recipientId'] + assert actual.signature == transaction_type_0['signature'] + assert actual.id == transaction_type_0['id'] actual.verify_schnorr() From 527cbd6ac6bb6656d3559dc43a4bd436661e7e64 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Wed, 31 Jul 2024 10:06:27 +0100 Subject: [PATCH 57/68] chore: rename delegate to validator (#137) --- crypto/constants.py | 12 +++++------ ...istration.py => validator_registration.py} | 8 ++++---- ...esignation.py => validator_resignation.py} | 8 ++++---- crypto/transactions/builder/vote.py | 4 ++-- ...istration.py => validator_registration.py} | 2 +- ...esignation.py => validator_resignation.py} | 2 +- crypto/transactions/serializer.py | 2 +- .../serializers/delegate_registration.py | 14 ------------- .../multi_signature_registration.py | 2 +- .../serializers/validator_registration.py | 14 +++++++++++++ ...esignation.py => validator_resignation.py} | 4 ++-- crypto/transactions/transaction.py | 6 +++--- tests/conftest.py | 2 +- ...tion.py => test_validator_registration.py} | 20 +++++++++---------- ...ation.py => test_validator_resignation.py} | 20 +++++++++---------- ...tion.py => test_validator_registration.py} | 2 +- ...ation.py => test_validator_resignation.py} | 2 +- ...tion.py => test_validator_registration.py} | 0 ...ation.py => test_validator_resignation.py} | 0 19 files changed, 62 insertions(+), 62 deletions(-) rename crypto/transactions/builder/{delegate_registration.py => validator_registration.py} (69%) rename crypto/transactions/builder/{delegate_resignation.py => validator_resignation.py} (66%) rename crypto/transactions/deserializers/{delegate_registration.py => validator_registration.py} (89%) rename crypto/transactions/deserializers/{delegate_resignation.py => validator_resignation.py} (83%) delete mode 100644 crypto/transactions/serializers/delegate_registration.py create mode 100644 crypto/transactions/serializers/validator_registration.py rename crypto/transactions/serializers/{delegate_resignation.py => validator_resignation.py} (56%) rename tests/transactions/builder/{test_delegate_registration.py => test_validator_registration.py} (72%) rename tests/transactions/builder/{test_delegate_resignation.py => test_validator_resignation.py} (69%) rename tests/transactions/deserializers/{test_delegate_registration.py => test_validator_registration.py} (92%) rename tests/transactions/deserializers/{test_delegate_resignation.py => test_validator_resignation.py} (92%) rename tests/transactions/serializers/{test_delegate_registration.py => test_validator_registration.py} (100%) rename tests/transactions/serializers/{test_delegate_resignation.py => test_validator_resignation.py} (100%) diff --git a/crypto/constants.py b/crypto/constants.py index 6a4d6af7..81fb27c8 100644 --- a/crypto/constants.py +++ b/crypto/constants.py @@ -1,28 +1,28 @@ from enum import Enum TRANSACTION_TRANSFER = 0 -TRANSACTION_DELEGATE_REGISTRATION = 2 +TRANSACTION_VALIDATOR_REGISTRATION = 2 TRANSACTION_VOTE = 3 TRANSACTION_MULTI_SIGNATURE_REGISTRATION = 4 TRANSACTION_MULTI_PAYMENT = 6 -TRANSACTION_DELEGATE_RESIGNATION = 7 +TRANSACTION_VALIDATOR_RESIGNATION = 7 TRANSACTION_TYPES = { TRANSACTION_TRANSFER: 'transfer', - TRANSACTION_DELEGATE_REGISTRATION: 'delegate_registration', + TRANSACTION_VALIDATOR_REGISTRATION: 'validator_registration', TRANSACTION_VOTE: 'vote', TRANSACTION_MULTI_SIGNATURE_REGISTRATION: 'multi_signature_registration', TRANSACTION_MULTI_PAYMENT: 'multi_payment', - TRANSACTION_DELEGATE_RESIGNATION: 'delegate_resignation', + TRANSACTION_VALIDATOR_RESIGNATION: 'validator_resignation', } TRANSACTION_FEES = { TRANSACTION_TRANSFER: 10000000, - TRANSACTION_DELEGATE_REGISTRATION: 2500000000, + TRANSACTION_VALIDATOR_REGISTRATION: 2500000000, TRANSACTION_VOTE: 100000000, TRANSACTION_MULTI_SIGNATURE_REGISTRATION: 500000000, TRANSACTION_MULTI_PAYMENT: 10000000, - TRANSACTION_DELEGATE_RESIGNATION: 2500000000, + TRANSACTION_VALIDATOR_RESIGNATION: 2500000000, } diff --git a/crypto/transactions/builder/delegate_registration.py b/crypto/transactions/builder/validator_registration.py similarity index 69% rename from crypto/transactions/builder/delegate_registration.py rename to crypto/transactions/builder/validator_registration.py index 9010606c..f8a207cd 100644 --- a/crypto/transactions/builder/delegate_registration.py +++ b/crypto/transactions/builder/validator_registration.py @@ -1,13 +1,13 @@ from typing import Optional -from crypto.constants import TRANSACTION_DELEGATE_REGISTRATION +from crypto.constants import TRANSACTION_VALIDATOR_REGISTRATION from crypto.transactions.builder.base import BaseTransactionBuilder -class DelegateRegistration(BaseTransactionBuilder): - transaction_type = TRANSACTION_DELEGATE_REGISTRATION +class ValidatorRegistration(BaseTransactionBuilder): + transaction_type = TRANSACTION_VALIDATOR_REGISTRATION def __init__(self, public_key: str, fee: Optional[int] = None): - """Create a delegate registration transaction + """Create a validator registration transaction Args: public_key (str): BLS public key of a validator you want to register diff --git a/crypto/transactions/builder/delegate_resignation.py b/crypto/transactions/builder/validator_resignation.py similarity index 66% rename from crypto/transactions/builder/delegate_resignation.py rename to crypto/transactions/builder/validator_resignation.py index 145d46b3..217c33dc 100644 --- a/crypto/transactions/builder/delegate_resignation.py +++ b/crypto/transactions/builder/validator_resignation.py @@ -1,13 +1,13 @@ from typing import Optional -from crypto.constants import TRANSACTION_DELEGATE_RESIGNATION, TRANSACTION_TYPE_GROUP +from crypto.constants import TRANSACTION_VALIDATOR_RESIGNATION, TRANSACTION_TYPE_GROUP from crypto.transactions.builder.base import BaseTransactionBuilder -class DelegateResignation(BaseTransactionBuilder): - transaction_type = TRANSACTION_DELEGATE_RESIGNATION +class ValidatorResignation(BaseTransactionBuilder): + transaction_type = TRANSACTION_VALIDATOR_RESIGNATION def __init__(self, fee: Optional[int] = None): - """Create a delegate resignation transaction + """Create a validator resignation transaction Args: fee (int, optional): fee used for the transaction (default is already set) diff --git a/crypto/transactions/builder/vote.py b/crypto/transactions/builder/vote.py index 28cceffd..4ffc92d1 100644 --- a/crypto/transactions/builder/vote.py +++ b/crypto/transactions/builder/vote.py @@ -11,8 +11,8 @@ def __init__(self, votes: Optional[list[str]] = None, unvotes: Optional[list[str """Create a vote transaction Args: - vote (str, optional): address of a delegate you want to vote - unvote (str, optional): address of a delegate you want to unvote + vote (str, optional): address of a validator you want to vote + unvote (str, optional): address of a validator you want to unvote fee (int, optional): fee used for the transaction (default is already set) """ super().__init__() diff --git a/crypto/transactions/deserializers/delegate_registration.py b/crypto/transactions/deserializers/validator_registration.py similarity index 89% rename from crypto/transactions/deserializers/delegate_registration.py rename to crypto/transactions/deserializers/validator_registration.py index ce6d8603..18dbebcd 100644 --- a/crypto/transactions/deserializers/delegate_registration.py +++ b/crypto/transactions/deserializers/validator_registration.py @@ -2,7 +2,7 @@ from crypto.transactions.deserializers.base import BaseDeserializer -class DelegateRegistrationDeserializer(BaseDeserializer): +class ValidatorRegistrationDeserializer(BaseDeserializer): def deserialize(self): starting_position = int(self.asset_offset / 2) diff --git a/crypto/transactions/deserializers/delegate_resignation.py b/crypto/transactions/deserializers/validator_resignation.py similarity index 83% rename from crypto/transactions/deserializers/delegate_resignation.py rename to crypto/transactions/deserializers/validator_resignation.py index 06757b1e..78e36371 100644 --- a/crypto/transactions/deserializers/delegate_resignation.py +++ b/crypto/transactions/deserializers/validator_resignation.py @@ -2,7 +2,7 @@ from crypto.transactions.deserializers.base import BaseDeserializer -class DelegateResignationDeserializer(BaseDeserializer): +class ValidatorResignationDeserializer(BaseDeserializer): def deserialize(self): self.transaction.parse_signatures( hexlify(self.serialized).decode(), diff --git a/crypto/transactions/serializer.py b/crypto/transactions/serializer.py index f9bdf500..ff9c2800 100644 --- a/crypto/transactions/serializer.py +++ b/crypto/transactions/serializer.py @@ -67,7 +67,7 @@ def serialize(self, skip_signature: bool = True, skip_second_signature: bool = T return hexlify(bytes_data).decode() def _handle_transaction_type(self, bytes_data) -> bytes: - """Serialize transaction specific data (eg. delegate registration) + """Serialize transaction specific data (eg. validator registration) Args: bytes_data (bytes): already serialized data about a transaction (eg. version, network) diff --git a/crypto/transactions/serializers/delegate_registration.py b/crypto/transactions/serializers/delegate_registration.py deleted file mode 100644 index c6925ec9..00000000 --- a/crypto/transactions/serializers/delegate_registration.py +++ /dev/null @@ -1,14 +0,0 @@ -from binary.hex.writer import write_high - -from crypto.transactions.serializers.base import BaseSerializer - -class DelegateRegistrationSerializer(BaseSerializer): - """Serializer handling delegate registration data - """ - - def serialize(self) -> bytes: - delegate_bytes = self.transaction['asset']['validatorPublicKey'].encode() - - self.bytes_data += write_high(delegate_bytes) - - return self.bytes_data diff --git a/crypto/transactions/serializers/multi_signature_registration.py b/crypto/transactions/serializers/multi_signature_registration.py index 3a594661..950dfd8c 100644 --- a/crypto/transactions/serializers/multi_signature_registration.py +++ b/crypto/transactions/serializers/multi_signature_registration.py @@ -4,7 +4,7 @@ from crypto.transactions.serializers.base import BaseSerializer class MultiSignatureSerializer(BaseSerializer): - """Serializer handling delegate registration data + """Serializer handling multi-signature data """ def serialize(self) -> bytes: diff --git a/crypto/transactions/serializers/validator_registration.py b/crypto/transactions/serializers/validator_registration.py new file mode 100644 index 00000000..77fbbe80 --- /dev/null +++ b/crypto/transactions/serializers/validator_registration.py @@ -0,0 +1,14 @@ +from binary.hex.writer import write_high + +from crypto.transactions.serializers.base import BaseSerializer + +class ValidatorRegistrationSerializer(BaseSerializer): + """Serializer handling validator registration data + """ + + def serialize(self) -> bytes: + validator_bytes = self.transaction['asset']['validatorPublicKey'].encode() + + self.bytes_data += write_high(validator_bytes) + + return self.bytes_data diff --git a/crypto/transactions/serializers/delegate_resignation.py b/crypto/transactions/serializers/validator_resignation.py similarity index 56% rename from crypto/transactions/serializers/delegate_resignation.py rename to crypto/transactions/serializers/validator_resignation.py index 07abb148..38d0e307 100644 --- a/crypto/transactions/serializers/delegate_resignation.py +++ b/crypto/transactions/serializers/validator_resignation.py @@ -1,7 +1,7 @@ from crypto.transactions.serializers.base import BaseSerializer -class DelegateResignationSerializer(BaseSerializer): - """Serializer handling delegate resignation data +class ValidatorResignationSerializer(BaseSerializer): + """Serializer handling validator resignation data """ def serialize(self) -> bytes: diff --git a/crypto/transactions/transaction.py b/crypto/transactions/transaction.py index 966dafa6..306d06d5 100644 --- a/crypto/transactions/transaction.py +++ b/crypto/transactions/transaction.py @@ -5,7 +5,7 @@ from binary.hex.writer import write_high from binary.unsigned_integer.writer import write_bit8 -from crypto.constants import TRANSACTION_DELEGATE_REGISTRATION, TRANSACTION_MULTI_SIGNATURE_REGISTRATION, TRANSACTION_VOTE +from crypto.constants import TRANSACTION_VALIDATOR_REGISTRATION, TRANSACTION_MULTI_SIGNATURE_REGISTRATION, TRANSACTION_VOTE from crypto.exceptions import ArkInvalidTransaction from crypto.transactions.serializer import Serializer from crypto.transactions.signature import Signature @@ -203,8 +203,8 @@ def _handle_transaction_type(self, bytes_data) -> bytes: NotImplementedError: raised only if the child transaction doesn't implement this required method """ - if self.type == TRANSACTION_DELEGATE_REGISTRATION: - bytes_data += self.asset['delegate']['username'].encode() + if self.type == TRANSACTION_VALIDATOR_REGISTRATION: + bytes_data += self.asset['validator']['username'].encode() elif self.type == TRANSACTION_VOTE: bytes_data += ''.join(self.asset['votes']).encode() elif self.type == TRANSACTION_MULTI_SIGNATURE_REGISTRATION: diff --git a/tests/conftest.py b/tests/conftest.py index e55c3429..de90741f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -140,7 +140,7 @@ def transaction_type_6(): @pytest.fixture def transaction_type_7(): - """Transaction of type "delegate resignation" + """Transaction of type "validator resignation" """ data = { 'version': 1, diff --git a/tests/transactions/builder/test_delegate_registration.py b/tests/transactions/builder/test_validator_registration.py similarity index 72% rename from tests/transactions/builder/test_delegate_registration.py rename to tests/transactions/builder/test_validator_registration.py index b9bf6bb5..8f71c6d0 100644 --- a/tests/transactions/builder/test_delegate_registration.py +++ b/tests/transactions/builder/test_validator_registration.py @@ -1,17 +1,17 @@ from crypto.configuration.network import set_network -from crypto.constants import TRANSACTION_DELEGATE_REGISTRATION, TRANSACTION_TYPE_GROUP +from crypto.constants import TRANSACTION_VALIDATOR_REGISTRATION, TRANSACTION_TYPE_GROUP from crypto.networks.devnet import Devnet -from crypto.transactions.builder.delegate_registration import DelegateRegistration +from crypto.transactions.builder.validator_registration import ValidatorRegistration set_network(Devnet) -def test_delegate_registration_transaction(passphrase): - """Test if a delegate registration transaction gets built +def test_validator_registration_transaction(passphrase): + """Test if a validator registration transaction gets built """ bls_public_key = 'a227bf7c57eaa6e4f5de7b17495b4ea0be645d1204ce2fc9b54dbfabe23a59b6377e924c12aa4a831483af021fbc29ec' - transaction = DelegateRegistration(bls_public_key) + transaction = ValidatorRegistration(bls_public_key) transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) transaction.set_nonce(1) transaction.sign(passphrase) @@ -20,7 +20,7 @@ def test_delegate_registration_transaction(passphrase): assert transaction_dict['nonce'] == 1 assert transaction_dict['signature'] assert transaction_dict['asset']['validatorPublicKey'] == bls_public_key - assert transaction_dict['type'] is TRANSACTION_DELEGATE_REGISTRATION + assert transaction_dict['type'] is TRANSACTION_VALIDATOR_REGISTRATION assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 2500000000 @@ -30,12 +30,12 @@ def test_delegate_registration_transaction(passphrase): transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid -def test_delegate_registration_transaction_custom_fee(passphrase): - """Test if a delegate registration transaction gets built with a custom fee +def test_validator_registration_transaction_custom_fee(passphrase): + """Test if a validator registration transaction gets built with a custom fee """ bls_public_key = 'a227bf7c57eaa6e4f5de7b17495b4ea0be645d1204ce2fc9b54dbfabe23a59b6377e924c12aa4a831483af021fbc29ec' - transaction = DelegateRegistration(bls_public_key, 5) + transaction = ValidatorRegistration(bls_public_key, 5) transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) transaction.set_nonce(1) transaction.sign(passphrase) @@ -44,7 +44,7 @@ def test_delegate_registration_transaction_custom_fee(passphrase): assert transaction_dict['nonce'] == 1 assert transaction_dict['signature'] assert transaction_dict['asset']['validatorPublicKey'] == bls_public_key - assert transaction_dict['type'] is TRANSACTION_DELEGATE_REGISTRATION + assert transaction_dict['type'] is TRANSACTION_VALIDATOR_REGISTRATION assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 5 diff --git a/tests/transactions/builder/test_delegate_resignation.py b/tests/transactions/builder/test_validator_resignation.py similarity index 69% rename from tests/transactions/builder/test_delegate_resignation.py rename to tests/transactions/builder/test_validator_resignation.py index fd3a957f..02461da7 100644 --- a/tests/transactions/builder/test_delegate_resignation.py +++ b/tests/transactions/builder/test_validator_resignation.py @@ -1,15 +1,15 @@ from crypto.configuration.network import set_network -from crypto.constants import TRANSACTION_DELEGATE_RESIGNATION, TRANSACTION_TYPE_GROUP +from crypto.constants import TRANSACTION_VALIDATOR_RESIGNATION, TRANSACTION_TYPE_GROUP from crypto.networks.devnet import Devnet -from crypto.transactions.builder.delegate_resignation import DelegateResignation +from crypto.transactions.builder.validator_resignation import ValidatorResignation set_network(Devnet) -def test_delegate_resignation_transaction(passphrase): - """Test if delegate resignation transaction gets built +def test_validator_resignation_transaction(passphrase): + """Test if validator resignation transaction gets built """ - transaction = DelegateResignation() + transaction = ValidatorResignation() transaction.set_nonce(1) transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) transaction.sign(passphrase) @@ -17,7 +17,7 @@ def test_delegate_resignation_transaction(passphrase): assert transaction_dict['nonce'] == 1 assert transaction_dict['signature'] - assert transaction_dict['type'] is TRANSACTION_DELEGATE_RESIGNATION + assert transaction_dict['type'] is TRANSACTION_VALIDATOR_RESIGNATION assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 2500000000 @@ -27,10 +27,10 @@ def test_delegate_resignation_transaction(passphrase): transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid -def test_delegate_resignation_transaction_custom_fee(passphrase): - """Test if delegate resignation transaction gets built with a custom fee +def test_validator_resignation_transaction_custom_fee(passphrase): + """Test if validator resignation transaction gets built with a custom fee """ - transaction = DelegateResignation(5) + transaction = ValidatorResignation(5) transaction.set_nonce(1) transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) transaction.sign(passphrase) @@ -38,7 +38,7 @@ def test_delegate_resignation_transaction_custom_fee(passphrase): assert transaction_dict['nonce'] == 1 assert transaction_dict['signature'] - assert transaction_dict['type'] is TRANSACTION_DELEGATE_RESIGNATION + assert transaction_dict['type'] is TRANSACTION_VALIDATOR_RESIGNATION assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 5 diff --git a/tests/transactions/deserializers/test_delegate_registration.py b/tests/transactions/deserializers/test_validator_registration.py similarity index 92% rename from tests/transactions/deserializers/test_delegate_registration.py rename to tests/transactions/deserializers/test_validator_registration.py index fbafac82..087532bf 100644 --- a/tests/transactions/deserializers/test_delegate_registration.py +++ b/tests/transactions/deserializers/test_validator_registration.py @@ -1,7 +1,7 @@ from crypto.transactions.deserializer import Deserializer -def test_delegate_registration_deserializer(transaction_type_2): +def test_validator_registration_deserializer(transaction_type_2): deserializer = Deserializer(transaction_type_2['serialized']) actual = deserializer.deserialize() diff --git a/tests/transactions/deserializers/test_delegate_resignation.py b/tests/transactions/deserializers/test_validator_resignation.py similarity index 92% rename from tests/transactions/deserializers/test_delegate_resignation.py rename to tests/transactions/deserializers/test_validator_resignation.py index 5b23c83c..b1dbd65c 100644 --- a/tests/transactions/deserializers/test_delegate_resignation.py +++ b/tests/transactions/deserializers/test_validator_resignation.py @@ -1,7 +1,7 @@ from crypto.transactions.deserializer import Deserializer -def test_delegate_resignation_deserializer(transaction_type_7): +def test_validator_resignation_deserializer(transaction_type_7): deserializer = Deserializer(transaction_type_7['serialized']) actual = deserializer.deserialize() diff --git a/tests/transactions/serializers/test_delegate_registration.py b/tests/transactions/serializers/test_validator_registration.py similarity index 100% rename from tests/transactions/serializers/test_delegate_registration.py rename to tests/transactions/serializers/test_validator_registration.py diff --git a/tests/transactions/serializers/test_delegate_resignation.py b/tests/transactions/serializers/test_validator_resignation.py similarity index 100% rename from tests/transactions/serializers/test_delegate_resignation.py rename to tests/transactions/serializers/test_validator_resignation.py From e55335f5a7020395b661314f9b522764f37822ae Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Wed, 31 Jul 2024 12:16:50 +0100 Subject: [PATCH 58/68] refactor: validate bls public key (#138) --- .../builder/validator_registration.py | 18 +++++++++++++ requirements.txt | 10 +++++++ setup.py | 1 + .../builder/test_validator_registration.py | 26 +++++++++++++++++-- 4 files changed, 53 insertions(+), 2 deletions(-) diff --git a/crypto/transactions/builder/validator_registration.py b/crypto/transactions/builder/validator_registration.py index f8a207cd..94790684 100644 --- a/crypto/transactions/builder/validator_registration.py +++ b/crypto/transactions/builder/validator_registration.py @@ -1,5 +1,7 @@ from typing import Optional +import blspy + from crypto.constants import TRANSACTION_VALIDATOR_REGISTRATION from crypto.transactions.builder.base import BaseTransactionBuilder @@ -15,7 +17,23 @@ def __init__(self, public_key: str, fee: Optional[int] = None): """ super().__init__() + self.validate_bls_public_key(public_key) + self.transaction.asset['validatorPublicKey'] = public_key if fee: self.transaction.fee = fee + + def validate_bls_public_key(self, public_key: str): + """Validate BLS public key + + Args: + public_key (str): BLS public key to validate + """ + if len(public_key) != 96: + raise ValueError('Invalid BLS public key') + + try: + blspy.PublicKeyMPL.from_bytes(bytes.fromhex(public_key)) + except Exception: + raise ValueError('Invalid BLS public key') diff --git a/requirements.txt b/requirements.txt index bbfed554..20a537ce 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,14 +4,20 @@ attrs==23.2.0 base58==2.1.1 beautifulsoup4==4.12.3 binary-helpers==0.0.4 +blspy==2.0.3 btclib==2023.7.12 btclib_libsecp256k1==0.4.0 +cached-property==1.5.2 certifi==2024.7.4 cffi==1.16.0 charset-normalizer==3.3.2 coincurve==20.0.0 commonmark==0.9.1 coverage==7.5.4 +cytoolz==0.12.3 +eth-hash==0.7.0 +eth-typing==4.4.0 +eth-utils==4.1.1 exceptiongroup==1.2.1 flake8==7.1.0 flake8-import-order==0.18.2 @@ -22,6 +28,7 @@ iniconfig==2.0.0 markdown-it-py==3.0.0 mccabe==0.7.0 mdurl==0.1.2 +numpy==2.0.1 packaging==24.1 pluggy==1.5.0 py==1.11.0 @@ -36,9 +43,12 @@ pytest-cov==5.0.0 PyYAML==6.0.1 requests==2.32.3 rich==13.7.1 +scipy==1.14.0 six==1.16.0 soupsieve==2.5 toml==0.10.2 tomli==2.0.1 +toolz==0.12.1 tqdm==4.66.4 +typing_extensions==4.12.2 urllib3==2.2.2 diff --git a/setup.py b/setup.py index e68046aa..76e39818 100644 --- a/setup.py +++ b/setup.py @@ -6,6 +6,7 @@ requires = [ 'base58', 'binary-helpers', + 'blspy', 'coincurve', 'pycryptodomex', 'btclib', diff --git a/tests/transactions/builder/test_validator_registration.py b/tests/transactions/builder/test_validator_registration.py index 8f71c6d0..74bdfca0 100644 --- a/tests/transactions/builder/test_validator_registration.py +++ b/tests/transactions/builder/test_validator_registration.py @@ -9,7 +9,7 @@ def test_validator_registration_transaction(passphrase): """Test if a validator registration transaction gets built """ - bls_public_key = 'a227bf7c57eaa6e4f5de7b17495b4ea0be645d1204ce2fc9b54dbfabe23a59b6377e924c12aa4a831483af021fbc29ec' + bls_public_key = 'b5fea88b9aab3f0b122e5a7e1b07917e62a63ea59103d0a0715ecded3c41685af88f0a9606309b148b3b50f51a2e7036' transaction = ValidatorRegistration(bls_public_key) transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) @@ -30,10 +30,32 @@ def test_validator_registration_transaction(passphrase): transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid +def test_validator_registration_transaction_with_invalid_bls_public_key(): + """Test if a validator registration transaction fails with an invalid BLS public key + """ + try: + ValidatorRegistration('b5fea88b9aab3f0b122e5a7e1b07917e62a63ea59103d0a0715ecded3c41685af88f0a9606309b148b3b50f51a2edddd') + + raise Exception('ValidatorRegistration should raise an exception with an invalid BLS public key') + except ValueError as e: + assert e.args[0] == 'Invalid BLS public key' + + +def test_validator_registration_transaction_with_invalid_bls_public_key_by_length(): + """Test if a validator registration transaction fails with an invalid BLS public key + """ + try: + ValidatorRegistration('023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3') + + raise Exception('ValidatorRegistration should raise an exception with an invalid BLS public key') + except ValueError as e: + assert e.args[0] == 'Invalid BLS public key' + + def test_validator_registration_transaction_custom_fee(passphrase): """Test if a validator registration transaction gets built with a custom fee """ - bls_public_key = 'a227bf7c57eaa6e4f5de7b17495b4ea0be645d1204ce2fc9b54dbfabe23a59b6377e924c12aa4a831483af021fbc29ec' + bls_public_key = 'b5fea88b9aab3f0b122e5a7e1b07917e62a63ea59103d0a0715ecded3c41685af88f0a9606309b148b3b50f51a2e7036' transaction = ValidatorRegistration(bls_public_key, 5) transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) From cb1da790ed56a5153569fabf1c914fcdfbe0a2fb Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Mon, 12 Aug 2024 11:06:28 +0100 Subject: [PATCH 59/68] feat: implement username transaction types (#139) --- crypto/constants.py | 6 +++ .../builder/username_registration.py | 21 ++++++++ .../builder/username_resignation.py | 23 +++++++++ .../deserializers/username_registration.py | 25 ++++++++++ .../deserializers/username_resignation.py | 12 +++++ .../serializers/username_registration.py | 17 +++++++ .../serializers/username_resignation.py | 8 ++++ tests/conftest.py | 43 +++++++++++++++++ .../builder/test_multi_payment.py | 2 - .../test_multi_signature_registration.py | 1 - tests/transactions/builder/test_transfer.py | 2 - .../builder/test_username_registration.py | 48 +++++++++++++++++++ .../builder/test_username_resignation.py | 42 ++++++++++++++++ .../builder/test_validator_registration.py | 2 - .../builder/test_validator_resignation.py | 2 - tests/transactions/builder/test_vote.py | 4 -- .../test_username_registration.py | 20 ++++++++ .../test_username_resignation.py | 19 ++++++++ .../serializers/test_username_registration.py | 6 +++ .../serializers/test_username_resignation.py | 6 +++ 20 files changed, 296 insertions(+), 13 deletions(-) create mode 100644 crypto/transactions/builder/username_registration.py create mode 100644 crypto/transactions/builder/username_resignation.py create mode 100644 crypto/transactions/deserializers/username_registration.py create mode 100644 crypto/transactions/deserializers/username_resignation.py create mode 100644 crypto/transactions/serializers/username_registration.py create mode 100644 crypto/transactions/serializers/username_resignation.py create mode 100644 tests/transactions/builder/test_username_registration.py create mode 100644 tests/transactions/builder/test_username_resignation.py create mode 100644 tests/transactions/deserializers/test_username_registration.py create mode 100644 tests/transactions/deserializers/test_username_resignation.py create mode 100644 tests/transactions/serializers/test_username_registration.py create mode 100644 tests/transactions/serializers/test_username_resignation.py diff --git a/crypto/constants.py b/crypto/constants.py index 81fb27c8..dac42194 100644 --- a/crypto/constants.py +++ b/crypto/constants.py @@ -6,6 +6,8 @@ TRANSACTION_MULTI_SIGNATURE_REGISTRATION = 4 TRANSACTION_MULTI_PAYMENT = 6 TRANSACTION_VALIDATOR_RESIGNATION = 7 +TRANSACTION_USERNAME_REGISTRATION = 8 +TRANSACTION_USERNAME_RESIGNATION = 9 TRANSACTION_TYPES = { TRANSACTION_TRANSFER: 'transfer', @@ -14,6 +16,8 @@ TRANSACTION_MULTI_SIGNATURE_REGISTRATION: 'multi_signature_registration', TRANSACTION_MULTI_PAYMENT: 'multi_payment', TRANSACTION_VALIDATOR_RESIGNATION: 'validator_resignation', + TRANSACTION_USERNAME_REGISTRATION: 'username_registration', + TRANSACTION_USERNAME_RESIGNATION: 'username_resignation', } TRANSACTION_FEES = { @@ -23,6 +27,8 @@ TRANSACTION_MULTI_SIGNATURE_REGISTRATION: 500000000, TRANSACTION_MULTI_PAYMENT: 10000000, TRANSACTION_VALIDATOR_RESIGNATION: 2500000000, + TRANSACTION_USERNAME_REGISTRATION: 2500000000, + TRANSACTION_USERNAME_RESIGNATION: 2500000000, } diff --git a/crypto/transactions/builder/username_registration.py b/crypto/transactions/builder/username_registration.py new file mode 100644 index 00000000..4a540eb2 --- /dev/null +++ b/crypto/transactions/builder/username_registration.py @@ -0,0 +1,21 @@ +from typing import Optional + +from crypto.constants import TRANSACTION_USERNAME_REGISTRATION +from crypto.transactions.builder.base import BaseTransactionBuilder + +class UsernameRegistration(BaseTransactionBuilder): + transaction_type = TRANSACTION_USERNAME_REGISTRATION + + def __init__(self, username: str, fee: Optional[int] = None): + """Create a username registration transaction + + Args: + username (str): username you want to register + fee (int, optional): fee used for the transaction (default is already set) + """ + super().__init__() + + self.transaction.asset['username'] = username + + if fee: + self.transaction.fee = fee diff --git a/crypto/transactions/builder/username_resignation.py b/crypto/transactions/builder/username_resignation.py new file mode 100644 index 00000000..3d0a24dd --- /dev/null +++ b/crypto/transactions/builder/username_resignation.py @@ -0,0 +1,23 @@ +from typing import Optional + +from crypto.constants import TRANSACTION_USERNAME_RESIGNATION, TRANSACTION_TYPE_GROUP +from crypto.transactions.builder.base import BaseTransactionBuilder + +class UsernameResignation(BaseTransactionBuilder): + transaction_type = TRANSACTION_USERNAME_RESIGNATION + + def __init__(self, fee: Optional[int] = None): + """Create a username resignation transaction + + Args: + fee (int, optional): fee used for the transaction (default is already set) + """ + super().__init__() + + self.transaction.typeGroup = self.get_type_group() + + if fee: + self.transaction.fee = fee + + def get_type_group(self) -> int: + return TRANSACTION_TYPE_GROUP.CORE.value diff --git a/crypto/transactions/deserializers/username_registration.py b/crypto/transactions/deserializers/username_registration.py new file mode 100644 index 00000000..7c1c6b68 --- /dev/null +++ b/crypto/transactions/deserializers/username_registration.py @@ -0,0 +1,25 @@ +from binascii import hexlify, unhexlify + +from binary.unsigned_integer.reader import read_bit8 + +from crypto.transactions.deserializers.base import BaseDeserializer + +class UsernameRegistrationDeserializer(BaseDeserializer): + def deserialize(self): + starting_position = int(self.asset_offset / 2) + + username_length = read_bit8(self.serialized, starting_position) & 0xff + + start_index = self.asset_offset + 2 + end_index = start_index + (username_length * 2) + username = hexlify(self.serialized)[start_index:end_index] + username = unhexlify(username) + + self.transaction.asset['username'] = username.decode() + + self.transaction.parse_signatures( + hexlify(self.serialized).decode(), + self.asset_offset + (username_length + 1) * 2 + ) + + return self.transaction diff --git a/crypto/transactions/deserializers/username_resignation.py b/crypto/transactions/deserializers/username_resignation.py new file mode 100644 index 00000000..e278b111 --- /dev/null +++ b/crypto/transactions/deserializers/username_resignation.py @@ -0,0 +1,12 @@ +from binascii import hexlify + +from crypto.transactions.deserializers.base import BaseDeserializer + +class UsernameResignationDeserializer(BaseDeserializer): + def deserialize(self): + self.transaction.parse_signatures( + hexlify(self.serialized).decode(), + self.asset_offset + ) + + return self.transaction diff --git a/crypto/transactions/serializers/username_registration.py b/crypto/transactions/serializers/username_registration.py new file mode 100644 index 00000000..0b5a6a2a --- /dev/null +++ b/crypto/transactions/serializers/username_registration.py @@ -0,0 +1,17 @@ +from binascii import hexlify, unhexlify +from binary.hex.writer import write_high +from binary.unsigned_integer.writer import write_bit8 + +from crypto.transactions.serializers.base import BaseSerializer + +class UsernameRegistrationSerializer(BaseSerializer): + """Serializer handling username registration data + """ + + def serialize(self) -> bytes: + username_bytes = hexlify(self.transaction['asset']['username'].encode()) + + self.bytes_data += write_bit8(len(username_bytes) // 2) + self.bytes_data += unhexlify(username_bytes) + + return self.bytes_data diff --git a/crypto/transactions/serializers/username_resignation.py b/crypto/transactions/serializers/username_resignation.py new file mode 100644 index 00000000..a5543181 --- /dev/null +++ b/crypto/transactions/serializers/username_resignation.py @@ -0,0 +1,8 @@ +from crypto.transactions.serializers.base import BaseSerializer + +class UsernameResignationSerializer(BaseSerializer): + """Serializer handling username resignation data + """ + + def serialize(self) -> bytes: + return self.bytes_data diff --git a/tests/conftest.py b/tests/conftest.py index de90741f..ee5950a4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -158,6 +158,49 @@ def transaction_type_7(): return data +@pytest.fixture +def transaction_type_8(): + """Transaction of type "username registration" + """ + data = { + 'version': 1, + 'network': 30, + 'typeGroup': 1, + 'type': 8, + 'nonce': 9, + 'senderPublicKey': '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3', + 'fee': 2500000000, + 'asset': { + 'username': 'test_username' + }, + 'signature': 'd6886e48b8df120f5f51d22c00583ef9cda61c32fd578cd07812f95cca16cdf98972e7f0789221cf1e0ad8fe174f68b4b3fe57a32692c83e0f7b1bd18c9b1640', # noqa + 'amount': 0, + 'id': 'f6ae0049bc3b79ddac96e37ceb6dadbf311b817d70ae1613ca5a97ceba6c8d40', + 'serialized': 'ff011e0100000008000900000000000000023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d300f9029500000000000d746573745f757365726e616d65d6886e48b8df120f5f51d22c00583ef9cda61c32fd578cd07812f95cca16cdf98972e7f0789221cf1e0ad8fe174f68b4b3fe57a32692c83e0f7b1bd18c9b1640' # noqa + } + return data + + +@pytest.fixture +def transaction_type_9(): + """Transaction of type "username resignation" + """ + data = { + 'version': 1, + 'network': 30, + 'typeGroup': 1, + 'type': 9, + 'nonce': 9, + 'senderPublicKey': '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3', + 'fee': 2500000000, + 'amount': 0, + 'signature': '728c2c5d5f090e8c5dfd433bb2b15b30442cbafb9b882117f7b6f284da4093c6a96e8456f764628ec809514ac4e8b06d5450978b9b763f7d01f696b8881f702a', + 'id': '9d01eb12cb47d5acbfe0ba0aff501e24e8313e2b08ecba118bb45332903d776b', + 'serialized': 'ff011e0100000009000900000000000000023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d300f902950000000000728c2c5d5f090e8c5dfd433bb2b15b30442cbafb9b882117f7b6f284da4093c6a96e8456f764628ec809514ac4e8b06d5450978b9b763f7d01f696b8881f702a' + } + return data + + @pytest.fixture def message(): data = { diff --git a/tests/transactions/builder/test_multi_payment.py b/tests/transactions/builder/test_multi_payment.py index ea363397..231277a5 100644 --- a/tests/transactions/builder/test_multi_payment.py +++ b/tests/transactions/builder/test_multi_payment.py @@ -20,7 +20,6 @@ def test_multi_payment_transaction(passphrase): assert transaction_dict['nonce'] == 1 assert transaction_dict['signature'] assert transaction_dict['type'] is TRANSACTION_MULTI_PAYMENT - assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 10000000 assert transaction_dict['expiration'] == 0 @@ -48,7 +47,6 @@ def test_multi_payment_transaction_custom_fee(passphrase): assert transaction_dict['nonce'] == 1 assert transaction_dict['signature'] assert transaction_dict['type'] is TRANSACTION_MULTI_PAYMENT - assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 5 assert transaction_dict['expiration'] == 0 diff --git a/tests/transactions/builder/test_multi_signature_registration.py b/tests/transactions/builder/test_multi_signature_registration.py index 025de87e..b6808d14 100644 --- a/tests/transactions/builder/test_multi_signature_registration.py +++ b/tests/transactions/builder/test_multi_signature_registration.py @@ -34,7 +34,6 @@ def test_multi_signature_registration_transaction(passphrase): assert transaction_dict['fee'] == 2000000000 assert transaction_dict['signature'] assert transaction_dict['type'] is TRANSACTION_MULTI_SIGNATURE_REGISTRATION - assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['asset']['multiSignature']['min'] == 2 assert transaction_dict['expiration'] == 0 diff --git a/tests/transactions/builder/test_transfer.py b/tests/transactions/builder/test_transfer.py index 75aa73dc..0504dccd 100644 --- a/tests/transactions/builder/test_transfer.py +++ b/tests/transactions/builder/test_transfer.py @@ -79,7 +79,6 @@ def test_transfer_transaction_custom_fee(passphrase): assert transaction_dict['nonce'] == 1 assert transaction_dict['signature'] assert transaction_dict['type'] is TRANSACTION_TRANSFER - assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 5 assert transaction_dict['expiration'] == 0 @@ -105,7 +104,6 @@ def test_transfer_secondsign_transaction(passphrase): assert transaction_dict['signature'] assert transaction_dict['signSignature'] assert transaction_dict['type'] is TRANSACTION_TRANSFER - assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['expiration'] == 0 assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' diff --git a/tests/transactions/builder/test_username_registration.py b/tests/transactions/builder/test_username_registration.py new file mode 100644 index 00000000..a3599dc2 --- /dev/null +++ b/tests/transactions/builder/test_username_registration.py @@ -0,0 +1,48 @@ +from crypto.configuration.network import set_network +from crypto.constants import TRANSACTION_USERNAME_REGISTRATION, TRANSACTION_TYPE_GROUP +from crypto.networks.devnet import Devnet +from crypto.transactions.builder.username_registration import UsernameRegistration + +set_network(Devnet) + + +def test_username_registration_transaction(passphrase): + """Test if a username registration transaction gets built + """ + username_name = 'mr.username' + + transaction = UsernameRegistration(username_name) + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.set_nonce(1) + transaction.sign(passphrase) + transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 + assert transaction_dict['signature'] + assert transaction_dict['asset']['username'] == username_name + assert transaction_dict['type'] is TRANSACTION_USERNAME_REGISTRATION + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['fee'] == 2500000000 + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid + + +def test_username_registration_transaction_custom_fee(passphrase): + """Test if a username registration transaction gets built with a custom fee + """ + username_name = 'mr.username' + + transaction = UsernameRegistration(username_name, 5) + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.set_nonce(1) + transaction.sign(passphrase) + transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 + assert transaction_dict['signature'] + assert transaction_dict['asset']['username'] == username_name + assert transaction_dict['type'] is TRANSACTION_USERNAME_REGISTRATION + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['fee'] == 5 + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_username_resignation.py b/tests/transactions/builder/test_username_resignation.py new file mode 100644 index 00000000..ac9d0562 --- /dev/null +++ b/tests/transactions/builder/test_username_resignation.py @@ -0,0 +1,42 @@ +from crypto.configuration.network import set_network +from crypto.constants import TRANSACTION_USERNAME_RESIGNATION, TRANSACTION_TYPE_GROUP +from crypto.networks.devnet import Devnet +from crypto.transactions.builder.username_resignation import UsernameResignation + +set_network(Devnet) + + +def test_username_resignation_transaction(): + """Test if username resignation transaction gets built + """ + transaction = UsernameResignation() + transaction.set_nonce(1) + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.sign('testing') + transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 + assert transaction_dict['signature'] + assert transaction_dict['type'] is TRANSACTION_USERNAME_RESIGNATION + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['fee'] == 2500000000 + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid + + +def test_username_resignation_transaction_custom_fee(): + """Test if username resignation transaction gets built with a custom fee + """ + transaction = UsernameResignation(5) + transaction.set_nonce(1) + transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) + transaction.sign('testing') + transaction_dict = transaction.to_dict() + + assert transaction_dict['nonce'] == 1 + assert transaction_dict['signature'] + assert transaction_dict['type'] is TRANSACTION_USERNAME_RESIGNATION + assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value + assert transaction_dict['fee'] == 5 + + transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_validator_registration.py b/tests/transactions/builder/test_validator_registration.py index 74bdfca0..65156a67 100644 --- a/tests/transactions/builder/test_validator_registration.py +++ b/tests/transactions/builder/test_validator_registration.py @@ -21,7 +21,6 @@ def test_validator_registration_transaction(passphrase): assert transaction_dict['signature'] assert transaction_dict['asset']['validatorPublicKey'] == bls_public_key assert transaction_dict['type'] is TRANSACTION_VALIDATOR_REGISTRATION - assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 2500000000 assert transaction_dict['expiration'] == 0 @@ -67,7 +66,6 @@ def test_validator_registration_transaction_custom_fee(passphrase): assert transaction_dict['signature'] assert transaction_dict['asset']['validatorPublicKey'] == bls_public_key assert transaction_dict['type'] is TRANSACTION_VALIDATOR_REGISTRATION - assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 5 assert transaction_dict['expiration'] == 0 diff --git a/tests/transactions/builder/test_validator_resignation.py b/tests/transactions/builder/test_validator_resignation.py index 02461da7..2bcf2d04 100644 --- a/tests/transactions/builder/test_validator_resignation.py +++ b/tests/transactions/builder/test_validator_resignation.py @@ -18,7 +18,6 @@ def test_validator_resignation_transaction(passphrase): assert transaction_dict['nonce'] == 1 assert transaction_dict['signature'] assert transaction_dict['type'] is TRANSACTION_VALIDATOR_RESIGNATION - assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 2500000000 assert transaction_dict['expiration'] == 0 @@ -39,7 +38,6 @@ def test_validator_resignation_transaction_custom_fee(passphrase): assert transaction_dict['nonce'] == 1 assert transaction_dict['signature'] assert transaction_dict['type'] is TRANSACTION_VALIDATOR_RESIGNATION - assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 5 assert transaction_dict['expiration'] == 0 diff --git a/tests/transactions/builder/test_vote.py b/tests/transactions/builder/test_vote.py index b7f78fd9..734083f3 100644 --- a/tests/transactions/builder/test_vote.py +++ b/tests/transactions/builder/test_vote.py @@ -22,7 +22,6 @@ def test_vote_transaction(passphrase): assert transaction_dict['asset']['votes'] == votes assert transaction_dict['asset']['unvotes'] == [] assert transaction_dict['type'] is TRANSACTION_VOTE - assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 100000000 assert transaction_dict['expiration'] == 0 @@ -46,7 +45,6 @@ def test_unvote_transaction(passphrase): assert transaction_dict['asset']['votes'] == [] assert transaction_dict['asset']['unvotes'] == unvotes assert transaction_dict['type'] is TRANSACTION_VOTE - assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 100000000 assert transaction_dict['expiration'] == 0 @@ -71,7 +69,6 @@ def test_vote_swap_transaction(passphrase): assert transaction_dict['asset']['votes'] == votes assert transaction_dict['asset']['unvotes'] == unvotes assert transaction_dict['type'] is TRANSACTION_VOTE - assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 100000000 assert transaction_dict['expiration'] == 0 @@ -95,7 +92,6 @@ def test_vote_transaction_custom_fee(passphrase): assert transaction_dict['asset']['votes'] == votes assert transaction_dict['asset']['unvotes'] == [] assert transaction_dict['type'] is TRANSACTION_VOTE - assert transaction_dict['typeGroup'] == 1 assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value assert transaction_dict['fee'] == 5 assert transaction_dict['expiration'] == 0 diff --git a/tests/transactions/deserializers/test_username_registration.py b/tests/transactions/deserializers/test_username_registration.py new file mode 100644 index 00000000..eb0709ec --- /dev/null +++ b/tests/transactions/deserializers/test_username_registration.py @@ -0,0 +1,20 @@ +from crypto.transactions.deserializer import Deserializer + + +def test_username_registration_deserializer(transaction_type_8): + deserializer = Deserializer(transaction_type_8['serialized']) + actual = deserializer.deserialize() + + assert actual.version == transaction_type_8['version'] + assert actual.network == transaction_type_8['network'] + assert actual.typeGroup == transaction_type_8['typeGroup'] + assert actual.type == transaction_type_8['type'] + assert actual.nonce == transaction_type_8['nonce'] + assert actual.senderPublicKey == transaction_type_8['senderPublicKey'] + assert actual.fee == transaction_type_8['fee'] + assert actual.asset == transaction_type_8['asset'] + assert actual.signature == transaction_type_8['signature'] + assert actual.amount == transaction_type_8['amount'] + assert actual.id == transaction_type_8['id'] + + actual.verify_schnorr() diff --git a/tests/transactions/deserializers/test_username_resignation.py b/tests/transactions/deserializers/test_username_resignation.py new file mode 100644 index 00000000..fb43f759 --- /dev/null +++ b/tests/transactions/deserializers/test_username_resignation.py @@ -0,0 +1,19 @@ +from crypto.transactions.deserializer import Deserializer + + +def test_username_resignation_deserializer(transaction_type_9): + deserializer = Deserializer(transaction_type_9['serialized']) + actual = deserializer.deserialize() + + assert actual.version == transaction_type_9['version'] + assert actual.network == transaction_type_9['network'] + assert actual.typeGroup == transaction_type_9['typeGroup'] + assert actual.type == transaction_type_9['type'] + assert actual.nonce == transaction_type_9['nonce'] + assert actual.senderPublicKey == transaction_type_9['senderPublicKey'] + assert actual.fee == transaction_type_9['fee'] + assert actual.signature == transaction_type_9['signature'] + assert actual.amount == transaction_type_9['amount'] + assert actual.id == transaction_type_9['id'] + + actual.verify_schnorr() diff --git a/tests/transactions/serializers/test_username_registration.py b/tests/transactions/serializers/test_username_registration.py new file mode 100644 index 00000000..f0e17b9b --- /dev/null +++ b/tests/transactions/serializers/test_username_registration.py @@ -0,0 +1,6 @@ +from crypto.transactions.serializer import Serializer + + +def test_serializer(transaction_type_8): + result = Serializer(transaction_type_8).serialize(False, True) + assert result == transaction_type_8['serialized'] diff --git a/tests/transactions/serializers/test_username_resignation.py b/tests/transactions/serializers/test_username_resignation.py new file mode 100644 index 00000000..5592096a --- /dev/null +++ b/tests/transactions/serializers/test_username_resignation.py @@ -0,0 +1,6 @@ +from crypto.transactions.serializer import Serializer + + +def test_serializer(transaction_type_9): + result = Serializer(transaction_type_9).serialize(False, True) + assert result == transaction_type_9['serialized'] From ae4c2e054a93854b39d1dd04f54cf1354eb936ce Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Tue, 20 Aug 2024 13:01:11 +0100 Subject: [PATCH 60/68] feat: bls key generation (#141) --- .github/workflows/test.yml | 11 +- .gitmodules | 3 + README.md | 11 +- crypto/identity/bls_private_key.py | 37 ++++++ crypto/identity/bls_public_key.py | 22 ++++ crypto/transactions/builder/base.py | 3 + .../builder/validator_registration.py | 12 +- requirements.txt | 2 +- setup.py | 2 +- tests/identity/conftest.py | 117 ++++++++++++++++++ tests/identity/test_bls_private_key.py | 14 +++ tests/identity/test_bls_public_key.py | 14 +++ thirdparty/bls-signatures | 1 + 13 files changed, 237 insertions(+), 12 deletions(-) create mode 100644 .gitmodules create mode 100644 crypto/identity/bls_private_key.py create mode 100644 crypto/identity/bls_public_key.py create mode 100644 tests/identity/test_bls_private_key.py create mode 100644 tests/identity/test_bls_public_key.py create mode 160000 thirdparty/bls-signatures diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 899afb2c..99c4d2fa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,16 +17,23 @@ jobs: python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v1 + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: true + ref: ${{ github.head_ref }} + - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + - name: install dependencies run: | python3 -m venv venv . venv/bin/activate pip install .[test] + - name: run tests run: | . venv/bin/activate diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..b1b4af97 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "thirdparty/bls-signatures"] + path = thirdparty/bls-signatures + url = https://github.com/ArkEcosystem/bls-signatures diff --git a/README.md b/README.md index 87bbdb7e..9e753a64 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,12 @@ ## Guide for contributing 1. Fork the repository on GitHub. -2. Run the tests to confirm they all pass on your system. If they don’t, you’ll need to investigate why they fail. If you’re unable to diagnose this yourself raise it as a bug report. -3. Make your change. -4. Write tests that demonstrate your bug or feature. -5. Run the entire test suite again, confirming that all tests pass including the ones you just added. -6. Send a GitHub Pull Request. GitHub Pull Requests are the expected method of code collaboration on this project. +2. Clone locally (making sure to load submodules): `git clone --recurse-submodules git@github.com:ArkEcosystem/python-crypto.git` +3. Run the tests to confirm they all pass on your system. If they don’t, you’ll need to investigate why they fail. If you’re unable to diagnose this yourself raise it as a bug report. +4. Make your change. +5. Write tests that demonstrate your bug or feature. +6. Run the entire test suite again, confirming that all tests pass including the ones you just added. +7. Send a GitHub Pull Request. GitHub Pull Requests are the expected method of code collaboration on this project. If you have any questions, requests or ideas open an issue or ask us in #developers channel on the [ArkEcosystem Discord](https://discord.ark.io/). diff --git a/crypto/identity/bls_private_key.py b/crypto/identity/bls_private_key.py new file mode 100644 index 00000000..dc148138 --- /dev/null +++ b/crypto/identity/bls_private_key.py @@ -0,0 +1,37 @@ +from btclib.mnemonic import bip39 + +import sys +import os +from os.path import dirname + +sys.path.append(os.path.join(dirname(dirname(dirname(__file__))), 'thirdparty/bls-signatures/python-impl')) + +from schemes import BasicSchemeMPL + +class BLSPrivateKey(object): + def __init__(self, private_key: bytes): + self.private_key = private_key + + def to_hex(self): + """Returns a private key in hex format + + Returns: + str: private key in hex format + """ + return self.private_key.hex() + + @classmethod + def from_passphrase(cls, passphrase: str): + """Create PrivateKey object from a given passphrase + + Args: + passphrase (str): + + Returns: + PrivateKey: Private key object + """ + seed = bip39.seed_from_mnemonic(passphrase, '') + + sk = BasicSchemeMPL.key_gen(seed) + + return cls(bytes(BasicSchemeMPL.derive_child_sk(sk, 0))) diff --git a/crypto/identity/bls_public_key.py b/crypto/identity/bls_public_key.py new file mode 100644 index 00000000..a52fa24b --- /dev/null +++ b/crypto/identity/bls_public_key.py @@ -0,0 +1,22 @@ +import sys +import os +from os.path import dirname + +sys.path.append(os.path.join(dirname(dirname(dirname(__file__))), 'thirdparty/bls-signatures/python-impl')) + +from schemes import PrivateKey + +from crypto.identity.bls_private_key import BLSPrivateKey + +class BLSPublicKey: + def __init__(self, public_key: bytes): + self.public_key = public_key + + def to_hex(self) -> str: + return self.public_key.hex() + + @classmethod + def from_passphrase(cls, passphrase: str): + private_key = BLSPrivateKey.from_passphrase(passphrase) + + return cls(bytes(PrivateKey.from_bytes(private_key.private_key).get_g1())) diff --git a/crypto/transactions/builder/base.py b/crypto/transactions/builder/base.py index 8683ebaf..c348e613 100644 --- a/crypto/transactions/builder/base.py +++ b/crypto/transactions/builder/base.py @@ -66,6 +66,9 @@ def multi_sign(self, passphrase, index): if not self.transaction.signatures: self.transaction.signatures = [] + if self.transaction.senderPublicKey is None: + raise Exception('Sender Public Key is required for multi signature') + index = len(self.transaction.signatures) if index == -1 else index msg = self.transaction.to_bytes() diff --git a/crypto/transactions/builder/validator_registration.py b/crypto/transactions/builder/validator_registration.py index 94790684..78559cd5 100644 --- a/crypto/transactions/builder/validator_registration.py +++ b/crypto/transactions/builder/validator_registration.py @@ -1,10 +1,16 @@ from typing import Optional -import blspy - from crypto.constants import TRANSACTION_VALIDATOR_REGISTRATION from crypto.transactions.builder.base import BaseTransactionBuilder +import sys +import os +from os.path import dirname + +sys.path.append(os.path.join(dirname(dirname(dirname(__file__))), 'thirdparty/bls-signatures/python-impl')) + +from ec import G1FromBytes + class ValidatorRegistration(BaseTransactionBuilder): transaction_type = TRANSACTION_VALIDATOR_REGISTRATION @@ -34,6 +40,6 @@ def validate_bls_public_key(self, public_key: str): raise ValueError('Invalid BLS public key') try: - blspy.PublicKeyMPL.from_bytes(bytes.fromhex(public_key)) + G1FromBytes(bytes.fromhex(public_key)).check_valid() except Exception: raise ValueError('Invalid BLS public key') diff --git a/requirements.txt b/requirements.txt index 20a537ce..433db20b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,6 @@ attrs==23.2.0 base58==2.1.1 beautifulsoup4==4.12.3 binary-helpers==0.0.4 -blspy==2.0.3 btclib==2023.7.12 btclib_libsecp256k1==0.4.0 cached-property==1.5.2 @@ -14,6 +13,7 @@ charset-normalizer==3.3.2 coincurve==20.0.0 commonmark==0.9.1 coverage==7.5.4 +cryptography==43.0.0 cytoolz==0.12.3 eth-hash==0.7.0 eth-typing==4.4.0 diff --git a/setup.py b/setup.py index 76e39818..b27e4fc1 100644 --- a/setup.py +++ b/setup.py @@ -6,10 +6,10 @@ requires = [ 'base58', 'binary-helpers', - 'blspy', 'coincurve', 'pycryptodomex', 'btclib', + 'cryptography', ] tests_require = [ diff --git a/tests/identity/conftest.py b/tests/identity/conftest.py index 325e38cd..46354bd6 100644 --- a/tests/identity/conftest.py +++ b/tests/identity/conftest.py @@ -14,3 +14,120 @@ def identity(): 'passphrase': 'my super secret passphrase' } return data + +@pytest.fixture +def validator(): + """Validator fixture + """ + return { + 'bls_public_key': 'b0093ac8f37588e15df7cfb04d0722dc5486cec062233136d3b6a16d41946577a1f332c4c29c0601ccefac1905dbb611', + 'bls_private_key': '3c0e55f46009b02bd739a16945babe797e6cbd096294dcc2550bce4baea2bde9', + 'passphrase': 'bless organ december boring ill obvious unaware dinosaur broccoli build hamster rebuild skin airport stay entry denial agent october thought duck trouble decorate way', + } + +@pytest.fixture +def bls_keys(): + """BLS private keys fixture + """ + return [ + { + 'bls_public_key': '90507d5a1a4cde6729f61a0e8fcc34f854113faf05f995b3ffd320639c4ffd118c335099350c92daa58e9ba22ca71af1', + 'bls_private_key': '710b0de2981d407d144161a5123f498a88355e3b0a559aa7942d90d49d0d5b34', + 'passphrase': 'famous dolphin salad photo spend stock portion outdoor print fiscal element smoke silent ritual verify current better raw visual mom real bubble certain banana', + }, + { + 'bls_public_key': 'af7f00ec30273a99411aa940ecf40646fa684f82d7d70e536a76c7c02e8ee6f3ffd297c42ad3775922938a31925bc1ab', + 'bls_private_key': '5a010d4c79c01fc4d26fcce16a601da683bd2dcb67aa09d6d4ab29042020e62b', + 'passphrase': 'good copper economy hope purity budget mistake achieve tail endless travel vibrant office cement inmate gospel effort desert garbage fiscal direct siege bright habit', + }, + { + 'bls_public_key': '84e79b94cc89e0a95b8aaa57949f04986f00ac3c28518af5e85152cd3806159dce3a3cd371b7a9c07568c43de6c3f621', + 'bls_private_key': '311886ef722406cdad272e4930679fb06b0bbe2ba12593cc48fa93547c60ba46', + 'passphrase': 'hidden soap wall giant abuse force raw coral spy assault key parade churn reflect lounge fly yellow awkward air ocean young melody cake decide', + }, + { + 'bls_public_key': 'aab82e8feea3ece5b7ccf99d820dc13b28f443d387ef371c8556671f2359469a5952d610624b85747ac4ba8ae9cbfb6b', + 'bls_private_key': '5432d15659b8a75a692e5800bd9c2d8f2515e45716ccb1d4b1b366447a7c1451', + 'passphrase': 'salt refuse nothing ordinary rigid exile fitness acoustic enjoy top pole miracle stem clinic range multiply worry jump question broken whip hidden annual umbrella', + }, + { + 'bls_public_key': 'a18dfb7ba3053845bf26f9e4038cb1b964670140b855c2558d329a4a7df0ad81f413d24deb9e6edf87780b3ab2ba1f4b', + 'bls_private_key': '46180073978c64b90b062759408ed18729ee2031944cca65b0bdfa2789116949', + 'passphrase': 'bounce salon piece fire vacuum laundry welcome solution ribbon ivory street smoke birth library announce duck case refuse cry first camp what turn canal', + }, + { + 'bls_public_key': '85caef761ec3845f5f835ea749e274ca90e3515d485219e8a3b469358f1cbf796926ef438e813db684e8f3a05b0210d8', + 'bls_private_key': '20d999f46f2d25e3338c4509faab59177310e648503e6f86818bb4a5a71b1a1f', + 'passphrase': 'always predict vocal eager pluck food check today obscure cancel coil begin muscle mistake onion fruit forget extra cousin shoulder cradle chase toss short', + }, + { + 'bls_public_key': 'b79975e1f4eefea36eef216295b8b9d46fac639943bc5e1a8aa5815f1593b592466e0df424d5a2136e05cf3ae8e75b15', + 'bls_private_key': '2acea7bda926715a45f327a3d686bb35105b88dce85be9c80218e148402eff0a', + 'passphrase': 'slam peanut energy bright network gas subway have aerobic scare planet depth lazy claim render civil capable green panel loud venue warfare melt jelly', + }, + { + 'bls_public_key': 'b0c002ade13cbff5f6c696e86b27ccca2ea396ccf2c7a686ab82f07ea3b38234ffd7eb6858cad6cdc09a6032e5653115', + 'bls_private_key': '03d12d4ed66ea5866cc5fd998958a3dd02d5605a34294a57ad300860c1c21b27', + 'passphrase': 'essence giant awful mixed mistake task vocal prevent room spot weird car safe mom gorilla reopen ability syrup step omit column shell rotate crumble', + }, + { + 'bls_public_key': 'a158a42fb1862f16cdc59fa69a57d57bd86931ad856030dd28550377e69ebc586dc5cb3444c5d89a92de05cb7f5467ad', + 'bls_private_key': '664b966a562ebf45125834dd5b9505221d40bf712f679442281dd3d380b8c56a', + 'passphrase': 'this fix sort sword knee more hill into innocent idea bargain have phrase fall zebra manage casino apology leisure arch shrimp target patrol cradle', + }, + { + 'bls_public_key': '9720ffa79fc8e9f04c153653c5cbc5bb1d2f1fc387d2640f6023585da3f3344374367240249e667d7f640fd1e0828202', + 'bls_private_key': '6237b95b32b77331fa0fc760d607f104b0bc975e789f226853d8e3f0daf985e1', + 'passphrase': 'paper ghost fatigue citizen broom radar honey success mother toilet insane all filter dutch excite kid dignity gaze exist layer elegant nerve lake silver', + }, + { + 'bls_public_key': '870b7c4db4ca1902e3d1aaf82e993e8c78f1b66b59febf7c41aceba45abe1403136c8b32a2ab3ad66d6fa2c40a724935', + 'bls_private_key': '0bb418ef9a25dc1f4cb1568c6b29abdc262b86d6589027b002b47f65fa4fd454', + 'passphrase': 'aisle orbit train among zero correct task sell scheme combine harsh gasp scissors addict tank key trust mirror immense common barely accident industry road', + }, + { + 'bls_public_key': '8bc136c71bd8fbb02c01ef2b90c7d0ade4f82d6275117e21e7794861487c9ce74c9e6f6470ab06a25d41b650f140a304', + 'bls_private_key': '183894353dc05c1c2bb7d3030d7638cadb951c07f560a4af79ab1a4057fccf57', + 'passphrase': 'trend exotic educate fantasy truck day amateur worth home more spice display jelly ivory swift mind assume what total often grace process dizzy payment', + }, + { + 'bls_public_key': 'a97a89995be26a0dce2e6b53b08453eb37b6705210527cf39c40fa928fdb31b62b580794f794c52d273d650fe066bf60', + 'bls_private_key': '46c042254f22e5d779faa8c799c1b1f6673468195a367a4b328344c8fdc4572d', + 'passphrase': 'cabbage rhythm rice pelican vintage benefit soccer stove pioneer bleak fossil emerge tell pretty champion depart today lobster into abandon dignity perfect scissors silly', + }, + { + 'bls_public_key': '8925b5568d5f97be7ba52d39c96cae06133b1deffa4b030529205ca8548e6ae0df91875aa34d59cc6fe1787f9e05f42b', + 'bls_private_key': '6191cad0ba2f8c4bc3fe148d0f4e119c117f31c3b8320bd976db5f631b761b62', + 'passphrase': 'romance eyebrow capital dinner apple video extend puppy rely pistol tissue color dynamic parade ketchup mother giraffe vintage basic risk orchard paddle another disagree', + }, + { + 'bls_public_key': '8aefc9325c182ddc2ae8fd6825871481b28966ff6e876da7bb72db86cc8fc8df7d01e6b52b01bd5c0478f3006cdf819f', + 'bls_private_key': '1e0a8618b7e867c46ce04e1b1071977340d3a2175241878c899dceeda4fad764', + 'passphrase': 'stereo tank target husband local retreat liquid zero master shy control viable ceiling negative universe glove tomato neglect rain motion cage early cherry sock', + }, + { + 'bls_public_key': 'b1186f7dee7d5dad1e451eb5c18ade0d9d706dd0ceea7d8407ee6e6656caadf6426f08ce8e0f31260892e685c3f4c4e1', + 'bls_private_key': '56d24d247c4cb234ce3e18a61ad94484d8c949e4df1211d28caa8115d300da2d', + 'passphrase': 'ladder where left grid stand code exercise cattle success rent air keep enhance news time six often fat similar quantum open rain bag code', + }, + { + 'bls_public_key': '8942eb575dd8a1e0145125471eb594b7724574f92f604415d8b0c5e3056ee183e304797c033a6ad81c6c5a18de7647fd', + 'bls_private_key': '5313dc4c76303f43ad04d25274ba776268efd95dcf960ea671684a9f91fd5f7d', + 'passphrase': 'huge sorry trash price oxygen sheriff hello purchase era enrich life pole basket infant wing release injury punch inflict good much bargain pair biology', + }, + { + 'bls_public_key': '8cacf7a6fdca95d05261ae074fb108e014022393f636a02890c27b8c3235624650f5d357cde2fc037f312a956f2a9b1b', + 'bls_private_key': '139341609069795a42a2fbbb3dcc335fdc80b86f1c473f2c7d270ed8a580467b', + 'passphrase': 'increase garbage input leaf accuse hold proud begin copy wire sell immense crop slide coach nerve food assume attack matrix text announce appear mobile', + }, + { + 'bls_public_key': 'afe1816457e64227fb1a3da8a3141caaa4cd03937b5ba5e2b454801f4f058d1536d8e64adbb2f1a9b5e2956dcfdbe101', + 'bls_private_key': '30f661095ecd42a5e5ea0685e56accb26ebf99c635720a253139177e5acde38d', + 'passphrase': 'wine apology crew sick pet fork twelve fluid flavor perfect phrase love crime attack simple emotion smart visa keen saddle unlock foil crush neck', + }, + { + 'bls_public_key': '84950d69eeb3d84552a61a1a4e4eaa0bf3f3c924abe9cd84f5673cdbc79bc0d664a56478b0faedce995d4367b57331cf', + 'bls_private_key': '305430a7d6571049cea36418c65c150314b6a0787af701a5a4a38237307eae5e', + 'passphrase': 'simple capital style robot duck police dose life bacon library figure wonder involve glory gallery cattle hockey balance home rigid arrive buyer crouch whale', + }, + ] diff --git a/tests/identity/test_bls_private_key.py b/tests/identity/test_bls_private_key.py new file mode 100644 index 00000000..e4076001 --- /dev/null +++ b/tests/identity/test_bls_private_key.py @@ -0,0 +1,14 @@ +from crypto.identity.bls_public_key import BLSPrivateKey + + +def test_private_key_from_passphrase(validator): + private_key = BLSPrivateKey.from_passphrase(validator['passphrase']).to_hex() + + assert private_key == validator['bls_private_key'] + + +def test_many_private_keys_from_passphrase(bls_keys): + for bls_private_key in bls_keys: + private_key = BLSPrivateKey.from_passphrase(bls_private_key['passphrase']).to_hex() + + assert private_key == bls_private_key['bls_private_key'] diff --git a/tests/identity/test_bls_public_key.py b/tests/identity/test_bls_public_key.py new file mode 100644 index 00000000..93b3fdd8 --- /dev/null +++ b/tests/identity/test_bls_public_key.py @@ -0,0 +1,14 @@ +from crypto.identity.bls_public_key import BLSPublicKey + + +def test_public_key_from_passphrase(validator): + public_key = BLSPublicKey.from_passphrase(validator['passphrase']).to_hex() + + assert public_key == validator['bls_public_key'] + + +def test_many_public_keys_from_passphrase(bls_keys): + for bls_public_key in bls_keys: + private_key = BLSPublicKey.from_passphrase(bls_public_key['passphrase']).to_hex() + + assert private_key == bls_public_key['bls_public_key'] diff --git a/thirdparty/bls-signatures b/thirdparty/bls-signatures new file mode 160000 index 00000000..119502a2 --- /dev/null +++ b/thirdparty/bls-signatures @@ -0,0 +1 @@ +Subproject commit 119502a2aee25afe55823ba91853c656866ef287 From 5c84bc5b29e8e278bc63126eff8e407ca77c5a9c Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Wed, 21 Aug 2024 15:58:47 +0100 Subject: [PATCH 61/68] fix: include submodule in packages (#142) --- .gitmodules | 4 ++-- crypto/identity/bls_private_key.py | 2 +- crypto/identity/bls_public_key.py | 2 +- {thirdparty => crypto/thirdparty}/bls-signatures | 0 setup.py | 3 ++- 5 files changed, 6 insertions(+), 5 deletions(-) rename {thirdparty => crypto/thirdparty}/bls-signatures (100%) diff --git a/.gitmodules b/.gitmodules index b1b4af97..484eaa7e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "thirdparty/bls-signatures"] - path = thirdparty/bls-signatures +[submodule "crypto/thirdparty/bls-signatures"] + path = crypto/thirdparty/bls-signatures url = https://github.com/ArkEcosystem/bls-signatures diff --git a/crypto/identity/bls_private_key.py b/crypto/identity/bls_private_key.py index dc148138..0e56c03e 100644 --- a/crypto/identity/bls_private_key.py +++ b/crypto/identity/bls_private_key.py @@ -4,7 +4,7 @@ import os from os.path import dirname -sys.path.append(os.path.join(dirname(dirname(dirname(__file__))), 'thirdparty/bls-signatures/python-impl')) +sys.path.append(os.path.join(dirname(dirname(__file__)), 'thirdparty/bls-signatures/python-impl')) from schemes import BasicSchemeMPL diff --git a/crypto/identity/bls_public_key.py b/crypto/identity/bls_public_key.py index a52fa24b..00e91571 100644 --- a/crypto/identity/bls_public_key.py +++ b/crypto/identity/bls_public_key.py @@ -2,7 +2,7 @@ import os from os.path import dirname -sys.path.append(os.path.join(dirname(dirname(dirname(__file__))), 'thirdparty/bls-signatures/python-impl')) +sys.path.append(os.path.join(dirname(dirname(__file__)), 'thirdparty/bls-signatures/python-impl')) from schemes import PrivateKey diff --git a/thirdparty/bls-signatures b/crypto/thirdparty/bls-signatures similarity index 100% rename from thirdparty/bls-signatures rename to crypto/thirdparty/bls-signatures diff --git a/setup.py b/setup.py index b27e4fc1..7ce27155 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,8 @@ author='Ark Ecosystem', author_email='info@ark.io', url='https://github.com/ArkEcosystem/python-crypto', - packages=setuptools.find_packages(exclude=['tests', 'tests.*']), + packages=setuptools.find_packages(exclude=['tests', 'tests.*', 'crypto/thirdparty/bls-signatures']) + + ['crypto/thirdparty/bls-signatures/python-impl'], install_requires=requires, extras_require=extras_require, tests_require=tests_require, From 474a49a1b7b2d7fa1023677ad650d9fffdc0e821 Mon Sep 17 00:00:00 2001 From: Alex Barnsley <8069294+alexbarnsley@users.noreply.github.com> Date: Thu, 22 Aug 2024 12:47:32 +0100 Subject: [PATCH 62/68] fix: multi sign signatures & fees (#143) --- crypto/transactions/builder/base.py | 4 +- .../builder/multi_signature_registration.py | 6 +- tests/conftest.py | 6 ++ .../test_multi_signature_registration.py | 2 +- .../test_multi_signature_registration.py | 77 +++++++++++++++++++ 5 files changed, 87 insertions(+), 8 deletions(-) diff --git a/crypto/transactions/builder/base.py b/crypto/transactions/builder/base.py index c348e613..bca7c814 100644 --- a/crypto/transactions/builder/base.py +++ b/crypto/transactions/builder/base.py @@ -73,10 +73,10 @@ def multi_sign(self, passphrase, index): msg = self.transaction.to_bytes() secret = unhexlify(PrivateKey.from_passphrase(passphrase).to_hex()) - signature = hexlify(Signature.sign(msg, secret).encode()) + signature = Signature.sign(msg, secret) index_formatted = hex(index).replace('x', '') - self.transaction.signatures.append(index_formatted + signature.decode()) + self.transaction.signatures.append(index_formatted + signature) def serialize(self, skip_signature=False, skip_second_signature=False, skip_multi_signature=False): """Perform AIP11 compliant serialization. diff --git a/crypto/transactions/builder/multi_signature_registration.py b/crypto/transactions/builder/multi_signature_registration.py index b2671585..5f0402da 100644 --- a/crypto/transactions/builder/multi_signature_registration.py +++ b/crypto/transactions/builder/multi_signature_registration.py @@ -31,12 +31,8 @@ def set_min(self, minimum_participants): def set_public_keys(self, public_keys): self.transaction.asset['multiSignature']['publicKeys'] = public_keys - self.transaction.fee = (len(public_keys) + 1) * self.transaction.fee def add_participant(self, public_key: str): self.transaction.asset['multiSignature']['publicKeys'].append(public_key) - public_key_count = (len(self.transaction.asset['multiSignature']['publicKeys']) + 1) - transaction_fee = get_fee(TRANSACTION_MULTI_SIGNATURE_REGISTRATION) - - self.transaction.fee = public_key_count * transaction_fee + self.transaction.fee = get_fee(TRANSACTION_MULTI_SIGNATURE_REGISTRATION) diff --git a/tests/conftest.py b/tests/conftest.py index ee5950a4..ace8eb38 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,12 @@ import pytest +@pytest.fixture +def passphrase(): + """Passphrase used for tests""" + + return 'my super secret passphrase' + @pytest.fixture def transaction_type_0(): """Transaction of type "transfer" diff --git a/tests/transactions/builder/test_multi_signature_registration.py b/tests/transactions/builder/test_multi_signature_registration.py index b6808d14..cce3d46f 100644 --- a/tests/transactions/builder/test_multi_signature_registration.py +++ b/tests/transactions/builder/test_multi_signature_registration.py @@ -31,7 +31,7 @@ def test_multi_signature_registration_transaction(passphrase): assert transaction_dict['nonce'] == 1 assert transaction_dict['version'] == 1 - assert transaction_dict['fee'] == 2000000000 + assert transaction_dict['fee'] == 500000000 assert transaction_dict['signature'] assert transaction_dict['type'] is TRANSACTION_MULTI_SIGNATURE_REGISTRATION assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value diff --git a/tests/transactions/deserializers/test_multi_signature_registration.py b/tests/transactions/deserializers/test_multi_signature_registration.py index d22b0c07..e079cc3c 100644 --- a/tests/transactions/deserializers/test_multi_signature_registration.py +++ b/tests/transactions/deserializers/test_multi_signature_registration.py @@ -1,4 +1,8 @@ +from crypto.constants import TRANSACTION_FEES +from crypto.identity.public_key import PublicKey +from crypto.transactions.builder.multi_signature_registration import MultiSignatureRegistration from crypto.transactions.deserializer import Deserializer +from crypto.transactions.serializer import Serializer def test_multi_signature_registration_deserializer(transaction_type_4): @@ -21,3 +25,76 @@ def test_multi_signature_registration_deserializer(transaction_type_4): assert actual.signatures == transaction_type_4['signatures'] actual.verify_multisig_schnorr() + + +def test_multi_signature_registration_deserializer_from_serialized(transaction_type_4): + result = Serializer(transaction_type_4).serialize(False, True, False) + + deserializer = Deserializer(result) + actual = deserializer.deserialize() + + assert actual.version == transaction_type_4['version'] + assert actual.network == transaction_type_4['network'] + assert actual.typeGroup == transaction_type_4['typeGroup'] + assert actual.type == transaction_type_4['type'] + assert actual.nonce == transaction_type_4['nonce'] + assert actual.senderPublicKey == transaction_type_4['senderPublicKey'] + assert actual.fee == transaction_type_4['fee'] + assert actual.amount == transaction_type_4['amount'] + assert actual.signature == transaction_type_4['signature'] + assert actual.id == transaction_type_4['id'] + + assert actual.asset['multiSignature']['min'] == transaction_type_4['asset']['multiSignature']['min'] + assert actual.asset['multiSignature']['publicKeys'] == transaction_type_4['asset']['multiSignature']['publicKeys'] + assert actual.signatures == transaction_type_4['signatures'] + + actual.verify_multisig_schnorr() + +def test_with_methods(passphrase): + transaction = MultiSignatureRegistration() + + passphrase_2 = f'{passphrase} 2' + passphrase_3 = f'{passphrase} 3' + + sender_public_key = PublicKey.from_passphrase(passphrase) + participant_2 = PublicKey.from_passphrase(passphrase_2) + participant_3 = PublicKey.from_passphrase(passphrase_3) + + transaction.set_sender_public_key(sender_public_key) + transaction.set_nonce(15) + transaction.set_min(2) + transaction.add_participant(sender_public_key) + transaction.add_participant(participant_2) + transaction.add_participant(participant_3) + + transaction.multi_sign(passphrase, 0) + transaction.multi_sign(passphrase_2, 1) + transaction.multi_sign(passphrase_3, 2) + + transaction.sign(passphrase) + + result = Serializer(transaction.to_dict()).serialize(False, True, False) + + deserializer = Deserializer(result) + actual = deserializer.deserialize() + + assert actual.version == 1 + assert actual.network == 30 + assert actual.typeGroup == 1 + assert actual.type == 4 + assert actual.nonce == 15 + assert actual.senderPublicKey == sender_public_key + assert actual.fee == TRANSACTION_FEES[4] + assert actual.amount == 0 + assert actual.signature == transaction.transaction.signature + assert actual.id == transaction.transaction.id + + assert actual.asset['multiSignature']['min'] == 2 + assert actual.asset['multiSignature']['publicKeys'][0] == sender_public_key + assert actual.asset['multiSignature']['publicKeys'][1] == participant_2 + assert actual.asset['multiSignature']['publicKeys'][2] == participant_3 + + assert actual.signatures == transaction.transaction.signatures + assert len(actual.signatures) == 3 + + actual.verify_multisig_schnorr() From 9e014e6ee400f333b2d15233f4ea598029ca61c9 Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Thu, 28 Nov 2024 14:17:07 -0600 Subject: [PATCH 63/68] add fixtures --- tests/fixtures/evm-sign.json | 16 ++++++++++++++++ tests/fixtures/transfer.json | 16 ++++++++++++++++ tests/fixtures/unvote.json | 16 ++++++++++++++++ tests/fixtures/validator-registration.json | 16 ++++++++++++++++ tests/fixtures/validator-resignation.json | 16 ++++++++++++++++ tests/fixtures/vote.json | 16 ++++++++++++++++ 6 files changed, 96 insertions(+) create mode 100644 tests/fixtures/evm-sign.json create mode 100644 tests/fixtures/transfer.json create mode 100644 tests/fixtures/unvote.json create mode 100644 tests/fixtures/validator-registration.json create mode 100644 tests/fixtures/validator-resignation.json create mode 100644 tests/fixtures/vote.json diff --git a/tests/fixtures/evm-sign.json b/tests/fixtures/evm-sign.json new file mode 100644 index 00000000..600ffa2d --- /dev/null +++ b/tests/fixtures/evm-sign.json @@ -0,0 +1,16 @@ +{ + "data": { + "network": 30, + "nonce": "13", + "gasPrice": 5, + "gasLimit": 1000000, + "value": "0", + "recipientAddress": "0xE536720791A7DaDBeBdBCD8c8546fb0791a11901", + "data": "a9059cbb00000000000000000000000027fa7caffaae77ddb9ab232fdbda56d5e5af2393000000000000000000000000000000000000000000000000016345785d8a0000", + "signature": "ba30f9042519079895c7408b0e92046c3f20680e0a9294e38ab3cfdd19b26cd4036fe2a80644abb922f1ad7cd682811a83c20120a8030df47b244a3bc44f4dbd00", + "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", + "senderAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", + "id": "3935ff0fe84ea6ac42fc889ed7cda4f97ddd11fd2d1c31e9201f14866acb6edc" + }, + "serialized": "1e0d000000000000000500000040420f00000000000000000000000000000000000000000000000000000000000000000001e536720791a7dadbebdbcd8c8546fb0791a1190144000000a9059cbb00000000000000000000000027fa7caffaae77ddb9ab232fdbda56d5e5af2393000000000000000000000000000000000000000000000000016345785d8a0000ba30f9042519079895c7408b0e92046c3f20680e0a9294e38ab3cfdd19b26cd4036fe2a80644abb922f1ad7cd682811a83c20120a8030df47b244a3bc44f4dbd00" +} diff --git a/tests/fixtures/transfer.json b/tests/fixtures/transfer.json new file mode 100644 index 00000000..4c577082 --- /dev/null +++ b/tests/fixtures/transfer.json @@ -0,0 +1,16 @@ +{ + "data": { + "network": 30, + "nonce": "12", + "gasPrice": 5, + "gasLimit": 21000, + "value": "10000000000000000000", + "recipientAddress": "0x07Ac3E438719be72a9e2591bB6015F10E8Af2468", + "data": "", + "signature": "b3bc84c8caf1b75c18a78dde87df9f555161003d341eafad659ab672501185e413a26284c3c95056809c7d440c4ffab26179c538864c4d14534ebd5a961852bf01", + "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", + "senderAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", + "id": "b5d7b17d30da123d9eebc8bb6012c1a4e950e1dad2b080404bb052c30b8a8b2e" + }, + "serialized": "1e0c0000000000000005000000085200000000000000000000000000000000000000000000000000008ac7230489e800000107ac3e438719be72a9e2591bb6015f10e8af246800000000b3bc84c8caf1b75c18a78dde87df9f555161003d341eafad659ab672501185e413a26284c3c95056809c7d440c4ffab26179c538864c4d14534ebd5a961852bf01" +} diff --git a/tests/fixtures/unvote.json b/tests/fixtures/unvote.json new file mode 100644 index 00000000..d5e3a7cf --- /dev/null +++ b/tests/fixtures/unvote.json @@ -0,0 +1,16 @@ +{ + "data": { + "network": 30, + "nonce": "13", + "gasPrice": 5, + "gasLimit": 200000, + "value": "0", + "recipientAddress": "0x522B3294E6d06aA25Ad0f1B8891242E335D3B459", + "data": "3174b689", + "signature": "d7534ec92c06a8547d0f2b3d3259dff5b0b17f8673d68dff9af023009c9c450e24205cb5f4fd6165d71c8b3ba3e9f741d1853110d44bd1e798e87f1a5d6a89c501", + "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", + "senderAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", + "id": "92ca281a6699a4eb08e8e5c4a644c216026f6c6d3560611c50cab54d1300b690" + }, + "serialized": "1e0d0000000000000005000000400d0300000000000000000000000000000000000000000000000000000000000000000001522b3294e6d06aa25ad0f1b8891242e335d3b459040000003174b689d7534ec92c06a8547d0f2b3d3259dff5b0b17f8673d68dff9af023009c9c450e24205cb5f4fd6165d71c8b3ba3e9f741d1853110d44bd1e798e87f1a5d6a89c501" +} diff --git a/tests/fixtures/validator-registration.json b/tests/fixtures/validator-registration.json new file mode 100644 index 00000000..170909cc --- /dev/null +++ b/tests/fixtures/validator-registration.json @@ -0,0 +1,16 @@ +{ + "data": { + "network": 30, + "nonce": "12", + "gasPrice": 5, + "gasLimit": 500000, + "value": "0", + "recipientAddress": "0x522B3294E6d06aA25Ad0f1B8891242E335D3B459", + "data": "602a9eee00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030a08058db53e2665c84a40f5152e76dd2b652125a6079130d4c315e728bcf4dd1dfb44ac26e82302331d61977d314111800000000000000000000000000000000", + "signature": "91b2ca61808b94392afa151ee893784a5221ab27b8fdf5871cc17c75e87acca8396530b2f320641326f00199478552e673d124406b44bcbe6075966016658d2201", + "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", + "senderAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", + "id": "3457dfd59d42a174feb30a1aac757e54caddd87d21e6483386a3440cc0fa6c5f" + }, + "serialized": "1e0c000000000000000500000020a10700000000000000000000000000000000000000000000000000000000000000000001522b3294e6d06aa25ad0f1b8891242e335d3b45984000000602a9eee00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030a08058db53e2665c84a40f5152e76dd2b652125a6079130d4c315e728bcf4dd1dfb44ac26e82302331d61977d31411180000000000000000000000000000000091b2ca61808b94392afa151ee893784a5221ab27b8fdf5871cc17c75e87acca8396530b2f320641326f00199478552e673d124406b44bcbe6075966016658d2201" +} diff --git a/tests/fixtures/validator-resignation.json b/tests/fixtures/validator-resignation.json new file mode 100644 index 00000000..77a37778 --- /dev/null +++ b/tests/fixtures/validator-resignation.json @@ -0,0 +1,16 @@ +{ + "data": { + "network": 30, + "nonce": "12", + "gasPrice": 5, + "gasLimit": 150000, + "value": "0", + "recipientAddress": "0x522B3294E6d06aA25Ad0f1B8891242E335D3B459", + "data": "b85f5da2", + "signature": "94fd248dc5984b56be6c9661c5a32fa062fb21af62b1474a33d985302f9bda8a044c30e4feb1f06da437c15d9e997816aa3233b3f142cd780e1ff69b80269d0d00", + "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", + "senderAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", + "id": "ab469546888715725add275778bcf0c1dd68afc163b48018e22a044db718e5b9" + }, + "serialized": "1e0c0000000000000005000000f0490200000000000000000000000000000000000000000000000000000000000000000001522b3294e6d06aa25ad0f1b8891242e335d3b45904000000b85f5da294fd248dc5984b56be6c9661c5a32fa062fb21af62b1474a33d985302f9bda8a044c30e4feb1f06da437c15d9e997816aa3233b3f142cd780e1ff69b80269d0d00" +} diff --git a/tests/fixtures/vote.json b/tests/fixtures/vote.json new file mode 100644 index 00000000..d7992974 --- /dev/null +++ b/tests/fixtures/vote.json @@ -0,0 +1,16 @@ +{ + "data": { + "network": 30, + "nonce": "12", + "gasPrice": 5, + "gasLimit": 200000, + "value": "0", + "recipientAddress": "0x522B3294E6d06aA25Ad0f1B8891242E335D3B459", + "data": "6dd7d8ea000000000000000000000000512f366d524157bcf734546eb29a6d687b762255", + "signature": "e1fd7b0ddc466072e2eac37b73283e8303d80ceb2dd2d64a8d6cdf5866662bc5261a08ca2d64942b6bb93b42ed820f1c8c1c92ce2312d380cc83fea022bfc2f301", + "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", + "senderAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", + "id": "749744e0d689c46e37ff2993a984599eac4989a9ef0028337b335c9d43abf936" + }, + "serialized": "1e0c0000000000000005000000400d0300000000000000000000000000000000000000000000000000000000000000000001522b3294e6d06aa25ad0f1b8891242e335d3b459240000006dd7d8ea000000000000000000000000512f366d524157bcf734546eb29a6d687b762255e1fd7b0ddc466072e2eac37b73283e8303d80ceb2dd2d64a8d6cdf5866662bc5261a08ca2d64942b6bb93b42ed820f1c8c1c92ce2312d380cc83fea022bfc2f301" +} From 36b09bb68ab04ed5ec53b8421030c26898b6198b Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Fri, 29 Nov 2024 09:16:28 -0600 Subject: [PATCH 64/68] wip --- crypto/identity/private_key.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/crypto/identity/private_key.py b/crypto/identity/private_key.py index 5c0781dc..d15d9ae1 100644 --- a/crypto/identity/private_key.py +++ b/crypto/identity/private_key.py @@ -17,14 +17,9 @@ def sign(self, message: bytes) -> bytes: Returns: bytes: signature of the signed message """ - from crypto.transactions.signature import Signature - - signature = Signature.sign( - hexlify(message), - self - ) - - return signature.encode() + signature = self.private_key.sign(message) + + return hexlify(signature).decode() def to_hex(self): """Returns a private key in hex format From 056399ba8342f0d41353407d4bc23a2d9b08aec2 Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Fri, 29 Nov 2024 09:19:01 -0600 Subject: [PATCH 65/68] create transaction types and builder as per other languages --- crypto/transactions/builder/base.py | 167 ++++++----------- crypto/transactions/builder/transfer.py | 31 ---- .../transactions/builder/transfer_builder.py | 12 ++ .../types/abstract_transaction.py | 93 ++++++++++ crypto/transactions/types/transfer.py | 6 + tests/transactions/builder/test_transfer.py | 171 ------------------ .../builder/test_transfer_builder.py | 39 ++++ 7 files changed, 206 insertions(+), 313 deletions(-) delete mode 100644 crypto/transactions/builder/transfer.py create mode 100644 crypto/transactions/builder/transfer_builder.py create mode 100644 crypto/transactions/types/abstract_transaction.py create mode 100644 crypto/transactions/types/transfer.py delete mode 100644 tests/transactions/builder/test_transfer.py create mode 100644 tests/transactions/builder/test_transfer_builder.py diff --git a/crypto/transactions/builder/base.py b/crypto/transactions/builder/base.py index bca7c814..89330386 100644 --- a/crypto/transactions/builder/base.py +++ b/crypto/transactions/builder/base.py @@ -1,36 +1,59 @@ -from binascii import hexlify, unhexlify +from typing import Optional -from crypto.configuration.fee import get_fee -from crypto.constants import TRANSACTION_TYPE_GROUP +from crypto.configuration.network import get_network from crypto.identity.private_key import PrivateKey -from crypto.identity.public_key import PublicKey -from crypto.transactions.serializer import Serializer -from crypto.transactions.signature import Signature -from crypto.transactions.transaction import Transaction - -class BaseTransactionBuilder(object): - transaction: Transaction - - def __init__(self): - self.transaction = Transaction() - - if hasattr(self, 'transaction_type'): - self.transaction.type = getattr(self, 'transaction_type') - - if hasattr(self, 'transaction_type'): - self.transaction.fee = get_fee(getattr(self, 'transaction_type')) - - if hasattr(self, 'nonce'): - self.transaction.nonce = getattr(self, 'nonce') - - if hasattr(self, 'signatures'): - self.transaction.signatures = getattr(self, 'signatures') - - self.transaction.typeGroup = getattr(self, 'typeGroup', int(TRANSACTION_TYPE_GROUP.CORE)) - self.transaction.version = getattr(self, 'version', 1) - self.transaction.expiration = getattr(self, 'expiration', 0) - if self.transaction.type != 0: - self.transaction.amount = getattr(self, 'amount', 0) +from crypto.transactions.types.abstract_transaction import AbstractTransaction + + +class AbstractTransactionBuilder: + def __init__(self, data: Optional[dict] = None): + default_data = { + 'value': '0', + 'senderPublicKey': '', + 'gasPrice': '5', + 'nonce': '1', + 'network': get_network()['version'], + 'gasLimit': 1_000_000, + 'data': '', + } + self.transaction = self.get_transaction_instance(data or default_data) + + def __str__(self): + return self.to_json() + + @classmethod + def new(cls, data: Optional[dict] = None): + return cls(data) + + def gas_limit(self, gas_limit: int): + self.transaction.data['gasLimit'] = gas_limit + return self + + def recipient_address(self, recipient_address: str): + self.transaction.data['recipientAddress'] = recipient_address + return self + + def gas_price(self, gas_price: int): + self.transaction.data['gasPrice'] = gas_price + return self + + def nonce(self, nonce: str): + self.transaction.data['nonce'] = nonce + return self + + def network(self, network: int): + self.transaction.data['network'] = network + return self + + def sign(self, passphrase: str): + keys = PrivateKey.from_passphrase(passphrase) + self.transaction.data['senderPublicKey'] = keys.public_key + self.transaction = self.transaction.sign(keys) + self.transaction.data['id'] = self.transaction.get_id() + return self + + def verify(self): + return self.transaction.verify() def to_dict(self): return self.transaction.to_dict() @@ -38,83 +61,5 @@ def to_dict(self): def to_json(self): return self.transaction.to_json() - def sign(self, passphrase): - """Sign the transaction using the given passphrase - - Args: - passphrase (str): passphrase associated with the account sending this transaction - """ - self.transaction.senderPublicKey = PublicKey.from_passphrase(passphrase) - - msg = self.transaction.to_bytes(False, True, False) - secret = unhexlify(PrivateKey.from_passphrase(passphrase).to_hex()) - self.transaction.signature = Signature.sign(msg, secret) - self.transaction.id = self.transaction.get_id() - - def second_sign(self, passphrase): - """Sign the transaction using the given second passphrase - - Args: - passphrase (str): 2nd passphrase associated with the account sending this transaction - """ - msg = self.transaction.to_bytes(False, True, False) - secret = unhexlify(PrivateKey.from_passphrase(passphrase).to_hex()) - self.transaction.signSignature = Signature.sign(msg, secret) - self.transaction.id = self.transaction.get_id() - - def multi_sign(self, passphrase, index): - if not self.transaction.signatures: - self.transaction.signatures = [] - - if self.transaction.senderPublicKey is None: - raise Exception('Sender Public Key is required for multi signature') - - index = len(self.transaction.signatures) if index == -1 else index - - msg = self.transaction.to_bytes() - secret = unhexlify(PrivateKey.from_passphrase(passphrase).to_hex()) - signature = Signature.sign(msg, secret) - - index_formatted = hex(index).replace('x', '') - self.transaction.signatures.append(index_formatted + signature) - - def serialize(self, skip_signature=False, skip_second_signature=False, skip_multi_signature=False): - """Perform AIP11 compliant serialization. - - Args: - skip_signature (bool, optional): do you want to skip the signature - skip_second_signature (bool, optional): do you want to skip the 2nd signature - skip_multi_signature (bool, optional): do you want to skip multi signature - - Returns: - str: Serialized string - """ - return Serializer(self.to_dict()).serialize(skip_signature, skip_second_signature, skip_multi_signature) - - def schnorr_verify(self): - return self.transaction.verify_schnorr() - - def verify_secondsig_schnorr(self, secondPublicKey): - return self.transaction.verify_secondsig_schnorr(secondPublicKey) - - def verify_multisig_schnorr(self): - return self.transaction.verify_multisig_schnorr() - - def set_nonce(self, nonce): - self.transaction.nonce = nonce - - def set_amount(self, amount: int): - self.transaction.amount = amount - - def set_sender_public_key(self, public_key: str): - self.transaction.senderPublicKey = public_key - - def set_expiration(self, expiration: int): - self.transaction.expiration = expiration - - def set_type_group(self, type_group): - if type(type_group) == int: - self.transaction.typeGroup = type_group - else: - types = {TRANSACTION_TYPE_GROUP.TEST: 0, TRANSACTION_TYPE_GROUP.CORE: 1, TRANSACTION_TYPE_GROUP.RESERVED: 1000} - self.transaction.typeGroup = types[type_group] + def get_transaction_instance(self, data: dict) -> AbstractTransaction: + raise NotImplementedError("Subclasses must implement get_transaction_instance()") diff --git a/crypto/transactions/builder/transfer.py b/crypto/transactions/builder/transfer.py deleted file mode 100644 index 37d245aa..00000000 --- a/crypto/transactions/builder/transfer.py +++ /dev/null @@ -1,31 +0,0 @@ -from typing import Optional - -from crypto.constants import TRANSACTION_TRANSFER -from crypto.transactions.builder.base import BaseTransactionBuilder - -class Transfer(BaseTransactionBuilder): - - transaction_type = TRANSACTION_TRANSFER - - def __init__(self, recipientId: str, amount: int, vendorField: Optional[str] = None, fee: Optional[int] = None): - """Create a transfer transaction - - Args: - recipientId (str): address to which you want to send coins - amount (int): amount of coins you want to transfer - vendorField (str): value for the vendor field aka smartbridge - fee (int, optional): fee used for the transaction (default is already set) - """ - super().__init__() - - self.transaction.recipientId = recipientId - - if type(amount) == int and amount > 0: - self.transaction.amount = amount - else: - raise ValueError('Amount is not valid') - - self.transaction.vendorField = vendorField.encode() if vendorField else None - - if fee: - self.transaction.fee = fee diff --git a/crypto/transactions/builder/transfer_builder.py b/crypto/transactions/builder/transfer_builder.py new file mode 100644 index 00000000..9af863c8 --- /dev/null +++ b/crypto/transactions/builder/transfer_builder.py @@ -0,0 +1,12 @@ +from crypto.transactions.builder.base import AbstractTransactionBuilder +from crypto.transactions.types.transfer import Transfer + + +class TransferBuilder(AbstractTransactionBuilder): + def value(self, value: str): + self.transaction.data['value'] = value + self.transaction.refresh_payload_data() + return self + + def get_transaction_instance(self, data: dict): + return Transfer(data) diff --git a/crypto/transactions/types/abstract_transaction.py b/crypto/transactions/types/abstract_transaction.py new file mode 100644 index 00000000..cdb6f143 --- /dev/null +++ b/crypto/transactions/types/abstract_transaction.py @@ -0,0 +1,93 @@ +import json +from typing import Optional + +from crypto.configuration.network import get_network +from crypto.identity.address import address_from_public_key +from crypto.transactions.serializer import Serializer +# @todo add TransactionHasher +# from crypto.utils.transaction_hasher import TransactionHasher +from crypto.identity.private_key import PrivateKey + + +class AbstractTransaction: + def __init__(self, data: Optional[dict] = None): + self.data = data or {} + self.refresh_payload_data() + + def get_payload(self) -> str: + return '' + + def decode_payload(self, data: dict) -> Optional[dict]: + if 'data' not in data: + return None + + payload = data['data'] + + if payload == '': + return None + + # @TODO: add abidecoder + return {} + + def refresh_payload_data(self): + self.data['data'] = self.get_payload().lstrip('0x') + + def get_id(self) -> str: + return self.hash(skip_signature=False).hex() + + def get_bytes(self, skip_signature: bool = False) -> bytes: + return Serializer.get_bytes(self, skip_signature) + + def sign(self, private_key: PrivateKey): + hash_ = self.hash(skip_signature=True) + signature = private_key.sign_compact(hash_) + # Extraer el recovery ID y la firma + recovery_id = signature[0] - 27 - 4 + signature_hex = signature[1:].hex() + # Añadir el recovery ID al final + signature_hex += format(recovery_id, '02x') + self.data['signature'] = signature_hex + return self + + def get_public_key(self, compact_signature): + # @TODO: Implementar este método + pass + + def recover_sender(self): + compact_signature = self.get_signature() + public_key = self.get_public_key(compact_signature) + self.data['senderPublicKey'] = public_key.hex() + self.data['senderAddress'] = address_from_public_key(self.data['senderPublicKey']) + + def verify(self) -> bool: + # @TODO: Implementar este método + return True # Retornar True temporalmente + + def serialize(self, skip_signature: bool = False) -> bytes: + return Serializer(self).serialize(skip_signature) + + def to_dict(self) -> dict: + return {k: v for k, v in self.data.items() if v is not None} + + def to_json(self) -> str: + return json.dumps(self.to_dict()) + + def hash(self, skip_signature: bool) -> bytes: + hash_data = { + 'gasPrice': self.data.get('gasPrice'), + 'network': self.data.get('network', get_network().get('version')), + 'nonce': self.data.get('nonce'), + 'value': self.data.get('value'), + 'gasLimit': self.data.get('gasLimit'), + 'data': self.data.get('data'), + 'recipientAddress': self.data.get('recipientAddress'), + 'signature': self.data.get('signature') if not skip_signature else None, + } + + # @TODO: add transaction hasher + # return TransactionHasher.to_hash(hash_data, skip_signature) + return b'' + + def get_signature(self): + # @TODO: Implementar este método + pass diff --git a/crypto/transactions/types/transfer.py b/crypto/transactions/types/transfer.py new file mode 100644 index 00000000..abe87738 --- /dev/null +++ b/crypto/transactions/types/transfer.py @@ -0,0 +1,6 @@ +from crypto.transactions.types.abstract_transaction import AbstractTransaction + + +class Transfer(AbstractTransaction): + def get_payload(self) -> str: + return '' diff --git a/tests/transactions/builder/test_transfer.py b/tests/transactions/builder/test_transfer.py deleted file mode 100644 index 0504dccd..00000000 --- a/tests/transactions/builder/test_transfer.py +++ /dev/null @@ -1,171 +0,0 @@ -import pytest - -from crypto.configuration.network import set_network -from crypto.constants import TRANSACTION_TRANSFER, TRANSACTION_TYPE_GROUP -from crypto.identity.public_key import PublicKey -from crypto.networks.devnet import Devnet -from crypto.transactions.builder.transfer import Transfer - - -set_network(Devnet) - - -def test_transfer_transaction(passphrase): - """Test if a transfer transaction gets built - """ - transaction = Transfer( - recipientId='0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', - amount=1, - fee=10000000, - ) - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(8) - transaction.sign(passphrase) - transaction_dict = transaction.to_dict() - - print(transaction_dict, transaction) - - assert transaction_dict['version'] == 1 - assert transaction_dict['nonce'] == 8 - assert transaction_dict['type'] is TRANSACTION_TRANSFER - assert transaction_dict['typeGroup'] == 1 - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 10000000 - assert transaction_dict['amount'] == 1 - assert transaction_dict['expiration'] == 0 - assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid - - -def test_transfer_transaction_update_amount(passphrase): - """Test if a transfer transaction can update an amount - """ - transaction = Transfer( - recipientId='0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', - amount=200000000 - ) - transaction.set_amount(10) - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(1) - transaction.sign(passphrase) - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['type'] is TRANSACTION_TRANSFER - assert transaction_dict['typeGroup'] == 1 - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['amount'] == 10 - assert transaction_dict['expiration'] == 0 - assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid - - -def test_transfer_transaction_custom_fee(passphrase): - """Test if a transfer transaction gets built with a custom fee - """ - transaction = Transfer( - recipientId='0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', - amount=200000000, - fee=5 - ) - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(1) - transaction.sign(passphrase) - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['type'] is TRANSACTION_TRANSFER - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 5 - assert transaction_dict['expiration'] == 0 - assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid - - -def test_transfer_secondsign_transaction(passphrase): - """Test if a transfer transaction with second signature gets built - """ - transaction = Transfer( - recipientId='0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', - amount=200000000, - ) - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(1) - transaction.sign(passphrase) - transaction.second_sign('second top secret passphrase') - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['signSignature'] - assert transaction_dict['type'] is TRANSACTION_TRANSFER - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['expiration'] == 0 - assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid - transaction.verify_secondsig_schnorr(PublicKey.from_passphrase('second top secret passphrase')) # if no exception is raised, it means the transaction is valid - - -def test_parse_signatures(transaction_type_0): - """Test if parse signature works when parsing serialized data - """ - transfer = Transfer( - recipientId=transaction_type_0['recipientId'], - amount=transaction_type_0['amount'] - ) - assert transfer.transaction.signature is None - transfer.transaction.parse_signatures(transaction_type_0['serialized'], 166) - assert transfer.transaction.signature - - -def test_transfer_transaction_amount_not_int(): - with pytest.raises(ValueError): - """Test error handling in constructor for non-integer amount - """ - Transfer( - recipientId='0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', - amount='bad amount' - ) - - -def test_transfer_transaction_amount_zero(): - with pytest.raises(ValueError): - """Test error handling in constructor for non-integer amount - """ - Transfer( - recipientId='0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', - amount=0 - ) - - -def test_transfer_serialize(passphrase): - transaction = Transfer( - recipientId='0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', - amount=1, - fee=10000000, - ) - transaction.set_nonce(6) - transaction.sign(passphrase) - transaction.transaction.signature = '42faaaf6b5b5eff5bb78c7bb2b116ecbc0a83f53445b801818b72afb34b39226646608d5e7048c12d6aedcebfc3156f035b57ca70c6a5e899b7ac2a1be163bb0' - - assert transaction.serialize(False, False, False) == 'ff011e0100000000000600000000000000023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d38096980000000000000100000000000000000000006f0182a0cc707b055322ccf6d4cb6a5aff1aeb2242faaaf6b5b5eff5bb78c7bb2b116ecbc0a83f53445b801818b72afb34b39226646608d5e7048c12d6aedcebfc3156f035b57ca70c6a5e899b7ac2a1be163bb0' - - -def test_transfer_serialize_without_signatures(passphrase): - transaction = Transfer( - recipientId='0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', - amount=1, - fee=10000000, - ) - transaction.set_nonce(6) - transaction.sign(passphrase) - transaction.second_sign(passphrase) - transaction.transaction.signature = '42faaaf6b5b5eff5bb78c7bb2b116ecbc0a83f53445b801818b72afb34b39226646608d5e7048c12d6aedcebfc3156f035b57ca70c6a5e899b7ac2a1be163bb0' - - assert transaction.serialize(True, True, True) == 'ff011e0100000000000600000000000000023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d38096980000000000000100000000000000000000006f0182a0cc707b055322ccf6d4cb6a5aff1aeb22' diff --git a/tests/transactions/builder/test_transfer_builder.py b/tests/transactions/builder/test_transfer_builder.py new file mode 100644 index 00000000..2b6f1135 --- /dev/null +++ b/tests/transactions/builder/test_transfer_builder.py @@ -0,0 +1,39 @@ +import pytest +import json +import os + +from crypto.transactions.builder.transfer_builder import TransferBuilder +from crypto.configuration.network import set_network +from crypto.networks.devnet import Devnet + +set_network(Devnet) + + +def get_transaction_fixture(fixture_name): + fixtures_path = os.path.join( + os.path.dirname(__file__), + '../../fixtures', + f'{fixture_name}.json' + ) + with open(fixtures_path, 'r') as f: + return json.load(f) + + +def test_transfer_transaction(passphrase): + # Cargar el fixture + fixture = get_transaction_fixture('transfer') + + builder = ( + TransferBuilder() + .gas_price(fixture['data']['gasPrice']) + .nonce(fixture['data']['nonce']) + .network(fixture['data']['network']) + .gas_limit(fixture['data']['gasLimit']) + .recipient_address(fixture['data']['recipientAddress']) + .value(fixture['data']['value']) + .sign(passphrase) + ) + + assert builder.transaction.serialize().hex() == fixture['serialized'] + assert builder.transaction.data['id'] == fixture['data']['id'] + assert builder.verify() From 73322a4c3c06ac48ffd60a5083b9b67c87786a3b Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Fri, 29 Nov 2024 09:44:19 -0600 Subject: [PATCH 66/68] wip --- crypto/transactions/deserializer.py | 190 +++++++++++------- crypto/transactions/serializer.py | 143 +++++-------- .../types/abstract_transaction.py | 22 +- .../builder/test_transfer_builder.py | 2 +- 4 files changed, 183 insertions(+), 174 deletions(-) diff --git a/crypto/transactions/deserializer.py b/crypto/transactions/deserializer.py index 01395f11..677298fe 100644 --- a/crypto/transactions/deserializer.py +++ b/crypto/transactions/deserializer.py @@ -1,76 +1,130 @@ -import inspect -from binascii import hexlify, unhexlify -from importlib import import_module -from typing import Union - -from binary.unsigned_integer.reader import read_bit8, read_bit16, read_bit32, read_bit64 - -from crypto.constants import TRANSACTION_TYPES -from crypto.transactions.deserializers.base import BaseDeserializer -from crypto.transactions.transaction import Transaction - -class Deserializer(object): - serialized: bytes - - def __init__(self, serialized: Union[bytes, str]): - self.serialized = unhexlify(serialized) - - def deserialize(self) -> Transaction: - """Deserialize transaction - - Returns: - Transaction: returns transaction object - """ - - transaction = Transaction() - transaction.version = read_bit8(self.serialized, offset=1) - transaction.network = read_bit8(self.serialized, offset=2) - transaction.typeGroup = read_bit32(self.serialized, offset=3) - transaction.type = read_bit16(self.serialized, offset=7) - transaction.nonce = read_bit64(self.serialized, offset=9) - transaction.senderPublicKey = hexlify(self.serialized)[34:66+34].decode() - transaction.fee = read_bit64(self.serialized, offset=50) - - vendor_field_length = read_bit8(self.serialized, offset=58) - if vendor_field_length > 0: - vendor_field_offset = (58 + 8) * 2 - vendorField_take = vendor_field_length * 2 - transaction.vendorFieldHex = hexlify( - self.serialized - )[vendor_field_offset:vendorField_take] - - asset_offset = (58 + 1) * 2 + vendor_field_length * 2 - - handled_transaction = self._handle_transaction_type(asset_offset, transaction) - transaction.amount = handled_transaction.amount - transaction.version = handled_transaction.version - transaction.id = transaction.get_id() +# deserializer.py + +from crypto.transactions.types.abstract_transaction import AbstractTransaction +from crypto.transactions.types.transfer import Transfer +# from crypto.transactions.types.evm_call import EvmCall +# from crypto.transactions.types.vote import Vote +# from crypto.transactions.types.unvote import Unvote +# from crypto.transactions.types.validator_registration import ValidatorRegistration +# from crypto.transactions.types.validator_resignation import ValidatorResignation +from binascii import unhexlify, hexlify + +from binary.unsigned_integer.reader import read_bit8, read_bit16, read_bit32, read_bit64, read_bit256 +from binary.hex.reader import read_high + +from crypto.configuration.network import get_network +# from crypto.identity.address import address_from_hex # + + +class Deserializer: + SIGNATURE_SIZE = 64 + RECOVERY_SIZE = 1 + + def __init__(self, serialized: str): + self.serialized = unhexlify(serialized) if isinstance(serialized, str) else serialized + self.pointer = 0 + + @staticmethod + def new(serialized: str): + return Deserializer(serialized) + + def deserialize(self) -> AbstractTransaction: + data = {} + + self.deserialize_common(data) + self.deserialize_data(data) + transaction = self.guess_transaction_from_data(data) + self.deserialize_signatures(data) + + transaction.data = data + transaction.recover_sender() + + transaction.data['id'] = transaction.hash(skip_signature=False).hex() return transaction - def _handle_transaction_type(self, asset_offset: int, transaction): - """Handle deserialization for a given transaction type + def read_bytes(self, length: int) -> bytes: + result = self.serialized[self.pointer:self.pointer + length] + self.pointer += length + return result + + def deserialize_common(self, data: dict): + data['network'], _ = read_bit8(self.serialized, self.pointer) + self.pointer += 1 + + nonce, _ = read_bit64(self.serialized, self.pointer) + data['nonce'] = str(nonce) + self.pointer += 8 + + gas_price, _ = read_bit32(self.serialized, self.pointer) + data['gasPrice'] = gas_price + self.pointer += 4 + + gas_limit, _ = read_bit32(self.serialized, self.pointer) + data['gasLimit'] = gas_limit + self.pointer += 4 + + data['value'] = '0' + + def deserialize_data(self, data: dict): + # Read value (uint256) + value, _ = read_bit256(self.serialized, self.pointer) + data['value'] = str(value) + self.pointer += 32 + + # Read recipient marker and recipientAddress + recipient_marker, _ = read_bit8(self.serialized, self.pointer) + self.pointer += 1 + + if recipient_marker == 1: + recipient_address_bytes = self.read_bytes(20) # 20 bytes address + recipient_address = '0x' + hexlify(recipient_address_bytes).decode() + data['recipientAddress'] = recipient_address + + # Read payload length (uint32) + payload_length, _ = read_bit32(self.serialized, self.pointer) + self.pointer += 4 + + # Read payload as hex + payload_hex = '' + if payload_length > 0: + payload_bytes = self.read_bytes(payload_length) + payload_hex = hexlify(payload_bytes).decode() + + data['data'] = payload_hex + + def deserialize_signatures(self, data: dict): + signature_length = self.SIGNATURE_SIZE + self.RECOVERY_SIZE # in bytes + signature_bytes = self.read_bytes(signature_length) + data['signature'] = hexlify(signature_bytes).decode() + + def guess_transaction_from_data(self, data: dict) -> AbstractTransaction: + if data['value'] != '0': + return Transfer(data) + + # payload_data = self.decode_payload(data) + payload_data = None # Como AbiDecoder no está implementado + + if payload_data is None: + return Transfer(data) # Usamos Transfer por ahora - Args: - asset_offset (int): - transaction (Transaction): Transaction resource object + # function_name = payload_data['functionName'] - Returns: - Transaction: Transaction object of currently deserialized data - """ + # if function_name == AbiFunction.VOTE.value: + # return Vote(data) + # elif function_name == AbiFunction.UNVOTE.value: + # return Unvote(data) + # elif function_name == AbiFunction.VALIDATOR_REGISTRATION.value: + # return ValidatorRegistration(data) + # elif function_name == AbiFunction.VALIDATOR_RESIGNATION.value: + # return ValidatorResignation(data) + # else: + # return Transfer(data) - deserializer_name = TRANSACTION_TYPES[transaction.type] - module = import_module('crypto.transactions.deserializers.{}'.format(deserializer_name)) - for attr in dir(module): - # If attr name is `BaseDeserializer`, skip it as it's a class and also has a - # subclass of BaseDeserializer - if attr == 'BaseDeserializer': - continue + # def decode_payload(self, data: dict) -> dict: + # payload = data.get('data', '') - attribute = getattr(module, attr) - if inspect.isclass(attribute) and issubclass(attribute, BaseDeserializer): - # this attribute is actually a specific deserializer that we want to use - deserializer = attribute - break + # if payload == '': + # return None - return deserializer(self.serialized, asset_offset, transaction).deserialize() + # return AbiDecoder().decode_function_data(payload) diff --git a/crypto/transactions/serializer.py b/crypto/transactions/serializer.py index ff9c2800..4437c483 100644 --- a/crypto/transactions/serializer.py +++ b/crypto/transactions/serializer.py @@ -1,114 +1,67 @@ -import inspect from binascii import hexlify, unhexlify -from importlib import import_module - -from binary.hex.writer import write_high -from binary.unsigned_integer.writer import write_bit8, write_bit16, write_bit32, write_bit64 - +from crypto.transactions.types.abstract_transaction import AbstractTransaction from crypto.configuration.network import get_network -from crypto.constants import TRANSACTION_TYPES -from crypto.exceptions import ArkSerializerException -from crypto.transactions.serializers.base import BaseSerializer +from binary.unsigned_integer.writer import write_bit8, write_bit32, write_bit64, write_bit256 -class Serializer(object): - transaction: dict = {} - def __init__(self, transaction): +class Serializer: + def __init__(self, transaction: AbstractTransaction): if not transaction: - raise ArkSerializerException('No transaction data provided') + raise ValueError('No transaction data provided') self.transaction = transaction - def serialize_bytes(self, skip_signature: bool = True, skip_second_signature: bool = True, skip_multi_signature: bool = True) -> bytes: - """Perform AIP11 compliant serialization - - Returns: - bytes: Serialized bytes - """ - network_config = get_network() - bytes_data = bytes() - - bytes_data += write_bit8(0xff) - - bytes_data += write_bit8(self.transaction.get('version') or 0x01) - bytes_data += write_bit8(self.transaction.get('network') or network_config['version']) - bytes_data += write_bit32(self.transaction.get('typeGroup') or 0x01) - bytes_data += write_bit16(self.transaction.get('type')) - bytes_data += write_bit64(self.transaction.get('nonce') or 0x01) + @staticmethod + def new(transaction: AbstractTransaction): + return Serializer(transaction) - bytes_data += write_high(self.transaction.get('senderPublicKey')) - bytes_data += write_bit64(self.transaction.get('fee')) + @staticmethod + def get_bytes(transaction: AbstractTransaction, skip_signature: bool = False) -> bytes: + return transaction.serialize(skip_signature=skip_signature) - if self.transaction.get('vendorField'): - vendorFieldLength = len(self.transaction.get('vendorField') or '') - - bytes_data += write_bit8(vendorFieldLength) - bytes_data += self.transaction['vendorField'].encode() - elif self.transaction.get('vendorFieldHex'): - vendorField_hex_length = len(self.transaction['vendorFieldHex']) - - bytes_data += write_bit8(vendorField_hex_length / 2) - bytes_data += self.transaction['vendorFieldHex'] - else: - bytes_data += write_bit8(0x00) + def serialize(self, skip_signature: bool = False) -> bytes: + bytes_data = bytes() - bytes_data = self._handle_transaction_type(bytes_data) - bytes_data = self._handle_signature(bytes_data, skip_signature, skip_second_signature, skip_multi_signature) + bytes_data += self.serialize_common() + bytes_data += self.serialize_data() + if not skip_signature: + bytes_data += self.serialize_signatures() return bytes_data - def serialize(self, skip_signature: bool = True, skip_second_signature: bool = True, skip_multi_signature: bool = True) -> str: - """Perform AIP11 compliant serialization - - Returns: - str: Serialized string - """ - bytes_data = self.serialize_bytes(skip_signature, skip_second_signature, skip_multi_signature) - - return hexlify(bytes_data).decode() - - def _handle_transaction_type(self, bytes_data) -> bytes: - """Serialize transaction specific data (eg. validator registration) - - Args: - bytes_data (bytes): already serialized data about a transaction (eg. version, network) - - Returns: - bytes: bytes string - """ - serializer_name = TRANSACTION_TYPES[self.transaction['type']] - - module = import_module('crypto.transactions.serializers.{}'.format(serializer_name)) - for attr in dir(module): - # If attr name is `BaseSerializer`, skip it as it's a class and also has a - # subclass of BaseSerializer - if attr == 'BaseSerializer': - continue - - attribute = getattr(module, attr) - if inspect.isclass(attribute) and issubclass(attribute, BaseSerializer): - # this attribute is actually a specific serializer that we want to use - serializer = attribute - break - - return serializer(self.transaction, bytes_data).serialize() + def serialize_common(self) -> bytes: + bytes_data = bytes() + network_version = self.transaction.data.get('network', get_network()['version']) + bytes_data += write_bit8(network_version) + bytes_data += write_bit64(int(self.transaction.data['nonce'])) + bytes_data += write_bit32(int(self.transaction.data['gasPrice'])) + bytes_data += write_bit32(int(self.transaction.data['gasLimit'])) + return bytes_data - def _handle_signature(self, bytes_data, skip_signature, skip_second_signature, skip_multi_signature) -> bytes: - """Serialize signature data of the transaction + def serialize_data(self) -> bytes: + bytes_data = bytes() + # Write value as uint256 (32 bytes big-endian integer) + bytes_data += write_bit256(int(self.transaction.data['value'])) + + if 'recipientAddress' in self.transaction.data: + bytes_data += write_bit8(1) # Recipient marker + recipient_address = self.transaction.data['recipientAddress'] + # Assuming the address is in hexadecimal format without '0x' + bytes_data += unhexlify(recipient_address.replace('0x', '')) + else: + bytes_data += write_bit8(0) # No recipient - Args: - bytes_data (bytes): already serialized data + payload_hex = self.transaction.data.get('data', '') + payload_length = len(payload_hex) // 2 # Number of bytes in payload + bytes_data += write_bit32(payload_length) - Returns: - bytes: bytes string - """ - if not skip_signature and self.transaction.get('signature'): - bytes_data += unhexlify(self.transaction['signature']) + # Write payload as hex + if payload_length > 0: + bytes_data += unhexlify(payload_hex) - if not skip_second_signature and self.transaction.get('secondSignature'): - bytes_data += unhexlify(self.transaction['secondSignature']) - if not skip_second_signature and self.transaction.get('signSignature'): - bytes_data += unhexlify(self.transaction['signSignature']) - if not skip_multi_signature and self.transaction.get('signatures'): - bytes_data += unhexlify(''.join(self.transaction['signatures'])) + return bytes_data + def serialize_signatures(self) -> bytes: + bytes_data = bytes() + if 'signature' in self.transaction.data: + bytes_data += unhexlify(self.transaction.data['signature']) return bytes_data diff --git a/crypto/transactions/types/abstract_transaction.py b/crypto/transactions/types/abstract_transaction.py index cdb6f143..9b702016 100644 --- a/crypto/transactions/types/abstract_transaction.py +++ b/crypto/transactions/types/abstract_transaction.py @@ -40,13 +40,15 @@ def get_bytes(self, skip_signature: bool = False) -> bytes: def sign(self, private_key: PrivateKey): hash_ = self.hash(skip_signature=True) - signature = private_key.sign_compact(hash_) - # Extraer el recovery ID y la firma - recovery_id = signature[0] - 27 - 4 - signature_hex = signature[1:].hex() - # Añadir el recovery ID al final - signature_hex += format(recovery_id, '02x') - self.data['signature'] = signature_hex + # signature = private_key.sign_compact(hash_) + # # Extraer el recovery ID y la firma + # recovery_id = signature[0] - 27 - 4 + # signature_hex = signature[1:].hex() + # # Añadir el recovery ID al final + # signature_hex += format(recovery_id, '02x') + # self.data['signature'] = signature_hex + + return self def get_public_key(self, compact_signature): @@ -60,8 +62,8 @@ def recover_sender(self): self.data['senderAddress'] = address_from_public_key(self.data['senderPublicKey']) def verify(self) -> bool: - # @TODO: Implementar este método - return True # Retornar True temporalmente + # @TODO: Implment this method + return True # temporary def serialize(self, skip_signature: bool = False) -> bytes: return Serializer(self).serialize(skip_signature) @@ -89,5 +91,5 @@ def hash(self, skip_signature: bool) -> bytes: return b'' def get_signature(self): - # @TODO: Implementar este método + # @TODO: implement this pass diff --git a/tests/transactions/builder/test_transfer_builder.py b/tests/transactions/builder/test_transfer_builder.py index 2b6f1135..1d2a232b 100644 --- a/tests/transactions/builder/test_transfer_builder.py +++ b/tests/transactions/builder/test_transfer_builder.py @@ -20,7 +20,7 @@ def get_transaction_fixture(fixture_name): def test_transfer_transaction(passphrase): - # Cargar el fixture + # add fixture fixture = get_transaction_fixture('transfer') builder = ( From 15592d529312e6cff6cbd8b099f2340e8cdeb81a Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Fri, 29 Nov 2024 09:48:05 -0600 Subject: [PATCH 67/68] wip --- crypto/transactions/deserializer.py | 40 +++++++++---------- crypto/transactions/serializer.py | 26 +++++++----- .../types/abstract_transaction.py | 36 +++++------------ .../builder/test_transfer_builder.py | 1 - 4 files changed, 45 insertions(+), 58 deletions(-) diff --git a/crypto/transactions/deserializer.py b/crypto/transactions/deserializer.py index 677298fe..4fef67ab 100644 --- a/crypto/transactions/deserializer.py +++ b/crypto/transactions/deserializer.py @@ -1,5 +1,3 @@ -# deserializer.py - from crypto.transactions.types.abstract_transaction import AbstractTransaction from crypto.transactions.types.transfer import Transfer # from crypto.transactions.types.evm_call import EvmCall @@ -9,11 +7,14 @@ # from crypto.transactions.types.validator_resignation import ValidatorResignation from binascii import unhexlify, hexlify -from binary.unsigned_integer.reader import read_bit8, read_bit16, read_bit32, read_bit64, read_bit256 -from binary.hex.reader import read_high - -from crypto.configuration.network import get_network -# from crypto.identity.address import address_from_hex # +from binary.unsigned_integer.reader import ( + read_bit8, + read_bit32, + read_bit64, + # read_bit256, +) +# from crypto.enums.abi_function import AbiFunction # TODO: Implement or import AbiFunction +# from crypto.utils.abi_decoder import AbiDecoder # TODO: Implement or import AbiDecoder class Deserializer: @@ -22,7 +23,7 @@ class Deserializer: def __init__(self, serialized: str): self.serialized = unhexlify(serialized) if isinstance(serialized, str) else serialized - self.pointer = 0 + self.pointer = 0 @staticmethod def new(serialized: str): @@ -67,25 +68,24 @@ def deserialize_common(self, data: dict): data['value'] = '0' def deserialize_data(self, data: dict): - # Read value (uint256) - value, _ = read_bit256(self.serialized, self.pointer) + # @TODO: this should use read_bit256 + # value, _ = read_bit256(self.serialized, self.pointer) + value, _ = read_bit64(self.serialized, self.pointer) + data['value'] = str(value) self.pointer += 32 - # Read recipient marker and recipientAddress recipient_marker, _ = read_bit8(self.serialized, self.pointer) self.pointer += 1 if recipient_marker == 1: - recipient_address_bytes = self.read_bytes(20) # 20 bytes address + recipient_address_bytes = self.read_bytes(20) recipient_address = '0x' + hexlify(recipient_address_bytes).decode() data['recipientAddress'] = recipient_address - # Read payload length (uint32) payload_length, _ = read_bit32(self.serialized, self.pointer) self.pointer += 4 - # Read payload as hex payload_hex = '' if payload_length > 0: payload_bytes = self.read_bytes(payload_length) @@ -94,7 +94,7 @@ def deserialize_data(self, data: dict): data['data'] = payload_hex def deserialize_signatures(self, data: dict): - signature_length = self.SIGNATURE_SIZE + self.RECOVERY_SIZE # in bytes + signature_length = self.SIGNATURE_SIZE + self.RECOVERY_SIZE signature_bytes = self.read_bytes(signature_length) data['signature'] = hexlify(signature_bytes).decode() @@ -103,12 +103,10 @@ def guess_transaction_from_data(self, data: dict) -> AbstractTransaction: return Transfer(data) # payload_data = self.decode_payload(data) - payload_data = None # Como AbiDecoder no está implementado + payload_data = None # As AbiDecoder is not implemented if payload_data is None: - return Transfer(data) # Usamos Transfer por ahora - - # function_name = payload_data['functionName'] + return Transfer(data) # Using Transfer for now # if function_name == AbiFunction.VOTE.value: # return Vote(data) @@ -123,8 +121,8 @@ def guess_transaction_from_data(self, data: dict) -> AbstractTransaction: # def decode_payload(self, data: dict) -> dict: # payload = data.get('data', '') - + # # if payload == '': # return None - + # # return AbiDecoder().decode_function_data(payload) diff --git a/crypto/transactions/serializer.py b/crypto/transactions/serializer.py index 4437c483..974db7f1 100644 --- a/crypto/transactions/serializer.py +++ b/crypto/transactions/serializer.py @@ -1,7 +1,13 @@ -from binascii import hexlify, unhexlify +from binascii import unhexlify from crypto.transactions.types.abstract_transaction import AbstractTransaction from crypto.configuration.network import get_network -from binary.unsigned_integer.writer import write_bit8, write_bit32, write_bit64, write_bit256 +from binary.unsigned_integer.writer import ( + write_bit8, + write_bit32, + write_bit64, + # write_bit256, +) +# from crypto.utils.address import Address # TODO: Implement or import Address class Serializer: @@ -31,7 +37,7 @@ def serialize(self, skip_signature: bool = False) -> bytes: def serialize_common(self) -> bytes: bytes_data = bytes() network_version = self.transaction.data.get('network', get_network()['version']) - bytes_data += write_bit8(network_version) + bytes_data += write_bit8(int(network_version)) bytes_data += write_bit64(int(self.transaction.data['nonce'])) bytes_data += write_bit32(int(self.transaction.data['gasPrice'])) bytes_data += write_bit32(int(self.transaction.data['gasLimit'])) @@ -39,22 +45,22 @@ def serialize_common(self) -> bytes: def serialize_data(self) -> bytes: bytes_data = bytes() - # Write value as uint256 (32 bytes big-endian integer) - bytes_data += write_bit256(int(self.transaction.data['value'])) + + # @TODO: this should use write_bit256 + # bytes_data += write_bit256(int(self.transaction.data['value'])) + bytes_data += write_bit64(int(self.transaction.data['value'])) if 'recipientAddress' in self.transaction.data: - bytes_data += write_bit8(1) # Recipient marker + bytes_data += write_bit8(1) recipient_address = self.transaction.data['recipientAddress'] - # Assuming the address is in hexadecimal format without '0x' bytes_data += unhexlify(recipient_address.replace('0x', '')) else: - bytes_data += write_bit8(0) # No recipient + bytes_data += write_bit8(0) payload_hex = self.transaction.data.get('data', '') - payload_length = len(payload_hex) // 2 # Number of bytes in payload + payload_length = len(payload_hex) // 2 bytes_data += write_bit32(payload_length) - # Write payload as hex if payload_length > 0: bytes_data += unhexlify(payload_hex) diff --git a/crypto/transactions/types/abstract_transaction.py b/crypto/transactions/types/abstract_transaction.py index 9b702016..370add82 100644 --- a/crypto/transactions/types/abstract_transaction.py +++ b/crypto/transactions/types/abstract_transaction.py @@ -3,9 +3,6 @@ from crypto.configuration.network import get_network from crypto.identity.address import address_from_public_key -from crypto.transactions.serializer import Serializer -# @todo add TransactionHasher -# from crypto.utils.transaction_hasher import TransactionHasher from crypto.identity.private_key import PrivateKey @@ -18,15 +15,9 @@ def get_payload(self) -> str: return '' def decode_payload(self, data: dict) -> Optional[dict]: - if 'data' not in data: + if 'data' not in data or data['data'] == '': return None - - payload = data['data'] - - if payload == '': - return None - - # @TODO: add abidecoder + # TODO: add AbiDecoder to decode the payload return {} def refresh_payload_data(self): @@ -36,23 +27,16 @@ def get_id(self) -> str: return self.hash(skip_signature=False).hex() def get_bytes(self, skip_signature: bool = False) -> bytes: + from crypto.transactions.serializer import Serializer return Serializer.get_bytes(self, skip_signature) def sign(self, private_key: PrivateKey): hash_ = self.hash(skip_signature=True) - # signature = private_key.sign_compact(hash_) - # # Extraer el recovery ID y la firma - # recovery_id = signature[0] - 27 - 4 - # signature_hex = signature[1:].hex() - # # Añadir el recovery ID al final - # signature_hex += format(recovery_id, '02x') - # self.data['signature'] = signature_hex - - + # TODO: Implement signing logic return self def get_public_key(self, compact_signature): - # @TODO: Implementar este método + # TODO: Implement this method pass def recover_sender(self): @@ -62,10 +46,11 @@ def recover_sender(self): self.data['senderAddress'] = address_from_public_key(self.data['senderPublicKey']) def verify(self) -> bool: - # @TODO: Implment this method - return True # temporary + # TODO: Implement this method + return True def serialize(self, skip_signature: bool = False) -> bytes: + from crypto.transactions.serializer import Serializer return Serializer(self).serialize(skip_signature) def to_dict(self) -> dict: @@ -85,11 +70,10 @@ def hash(self, skip_signature: bool) -> bytes: 'recipientAddress': self.data.get('recipientAddress'), 'signature': self.data.get('signature') if not skip_signature else None, } - - # @TODO: add transaction hasher + # TODO: Implement TransactionHasher # return TransactionHasher.to_hash(hash_data, skip_signature) return b'' def get_signature(self): - # @TODO: implement this + # TODO: Implement this method pass diff --git a/tests/transactions/builder/test_transfer_builder.py b/tests/transactions/builder/test_transfer_builder.py index 1d2a232b..28258151 100644 --- a/tests/transactions/builder/test_transfer_builder.py +++ b/tests/transactions/builder/test_transfer_builder.py @@ -20,7 +20,6 @@ def get_transaction_fixture(fixture_name): def test_transfer_transaction(passphrase): - # add fixture fixture = get_transaction_fixture('transfer') builder = ( From f1efd5fa512f642a928902ea240ec46208a4f630 Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Fri, 29 Nov 2024 10:31:38 -0600 Subject: [PATCH 68/68] debug results --- tests/transactions/builder/test_transfer_builder.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/transactions/builder/test_transfer_builder.py b/tests/transactions/builder/test_transfer_builder.py index 28258151..36b5abb6 100644 --- a/tests/transactions/builder/test_transfer_builder.py +++ b/tests/transactions/builder/test_transfer_builder.py @@ -33,6 +33,10 @@ def test_transfer_transaction(passphrase): .sign(passphrase) ) + print(builder.transaction.serialize().hex()) + print(fixture['serialized']) + + assert builder.transaction.serialize().hex() == fixture['serialized'] assert builder.transaction.data['id'] == fixture['data']['id'] assert builder.verify()