From c631616b11715dc813bb0b032588c676538a6cb6 Mon Sep 17 00:00:00 2001 From: ross-spencer Date: Fri, 12 Apr 2024 14:31:57 +0200 Subject: [PATCH] Simple sign CLI and interfaces Simple signing functionality for the command line and library functions. Additional dotfiles missing from the template repo are also added. --- .codespellignore | 3 + .codespellrc | 9 ++ .editorconfig | 21 ++++ .github/workflows/linting.yml | 26 ++++ .github/workflows/unit-tests-all.yml | 64 ++++++++++ .gitignore | 141 ++++++++++++++++++++++ .markdownlint.yaml | 5 + .pre-commit-config.yaml | 46 +++++++ .pylintrc | 37 ++++++ .ruff.toml | 47 ++++++++ .vscode/settings.json | 46 +++++++ README.md | 73 ++++++++++- pyproject.toml | 78 +++--------- requirements/local.txt | 8 +- requirements/requirements.txt | 2 + sign.py | 12 ++ src/{template => simple_sign}/__init__.py | 0 src/simple_sign/sign.py | 140 +++++++++++++++++++++ src/simple_sign/version.py | 14 +++ src/template/template.py | 37 ------ template.py | 21 ---- tests/test_main.py | 8 -- tests/test_sign.py | 64 ++++++++++ 23 files changed, 768 insertions(+), 134 deletions(-) create mode 100644 .codespellignore create mode 100644 .codespellrc create mode 100644 .editorconfig create mode 100644 .github/workflows/linting.yml create mode 100644 .github/workflows/unit-tests-all.yml create mode 100644 .gitignore create mode 100644 .markdownlint.yaml create mode 100644 .pre-commit-config.yaml create mode 100644 .pylintrc create mode 100644 .ruff.toml create mode 100644 .vscode/settings.json create mode 100644 sign.py rename src/{template => simple_sign}/__init__.py (100%) create mode 100644 src/simple_sign/sign.py create mode 100644 src/simple_sign/version.py delete mode 100644 src/template/template.py delete mode 100644 template.py delete mode 100644 tests/test_main.py create mode 100644 tests/test_sign.py diff --git a/.codespellignore b/.codespellignore new file mode 100644 index 0000000..49a1e3d --- /dev/null +++ b/.codespellignore @@ -0,0 +1,3 @@ +# Ignore patterns for the codespell pre-commmit hook. + +cips diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 0000000..d110d10 --- /dev/null +++ b/.codespellrc @@ -0,0 +1,9 @@ +[codespell] +skip = + *.po, + *.ts, + tests/* +count = +quiet-level = 3 +ignore-words-list = + placeholder diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c740ef3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +# EditorConfig: https://EditorConfig.org. Provides sensible defaults for +# non vscode editors. + +# top-most EditorConfig file +root = true + +# Every file. +[*] +charset = "utf8" +end_of_line = lf +insert_final_newline = true + +indent_style = space +indent_size = 4 + +trim_trailing_whitespace = true + +# Python. (Duplicates used as placeholders) +[*.py] +indent_style = space +indent_size = 4 diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml new file mode 100644 index 0000000..1162ce2 --- /dev/null +++ b/.github/workflows/linting.yml @@ -0,0 +1,26 @@ +--- +name: "linting - all" +on: + pull_request: + push: + branches: + - "main" +jobs: + lint: + name: "linting (python)" + runs-on: "ubuntu-latest" + steps: + - name: "Check out repository" + uses: "actions/checkout@v2" + - name: "Set up Python" + uses: "actions/setup-python@v2" + with: + python-version: "3.10" + - name: "install linting tooling" + continue-on-error: true + run: | + python -m pip install --upgrade pip + python -m pip install -r requirements/local.txt ; pylint **/*.py + - name: "run linting via tox" + run: | + tox -e linting diff --git a/.github/workflows/unit-tests-all.yml b/.github/workflows/unit-tests-all.yml new file mode 100644 index 0000000..6bce0a1 --- /dev/null +++ b/.github/workflows/unit-tests-all.yml @@ -0,0 +1,64 @@ +--- +name: "unit tests - all" +on: + pull_request: + push: + branches: + - "main" +jobs: + tox: + name: "Python ${{ matrix.python-version }} -- ${{ matrix.os }} " + runs-on: ${{ matrix.os }} + continue-on-error: ${{ matrix.experimental }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + python-version: ["3.9", "3.10"] + experimental: [false] + # Include experimental or bleeding-edge releases. + # Windows is not included as it can be unreliable, e.g. + # psycopg2-binary is only released some time after a Python + # major/minor version is formally released. + # + # Uncomment below (including 'include:') when the next + # reasonable test candidate is made available: + include: + # + # Versions list: https://github.com/actions/python-versions/releases + # Example formatting: 3.11.0-alpha.1, 3.9.0-beta.8, 3.10.0-rc.3 + # + - os: ubuntu-latest + python-version: "3.11.0" + experimental: true + - os: macos-latest + python-version: "3.11.0" + experimental: true + steps: + - name: "check out repository" + uses: "actions/checkout@v2" + with: + submodules: 'true' + - name: "set up python ${{ matrix.python-version }}" + uses: "actions/setup-python@v2" + with: + python-version: "${{ matrix.python-version }}" + - name: "get pip cache dir" + id: "pip-cache" + run: | + echo "::set-output name=dir::$(pip cache dir)" + - name: "cache pip packages" + uses: "actions/cache@v2" + with: + path: "${{ steps.pip-cache.outputs.dir }}" + key: "${{ runner.os }}-pip-${{ hashFiles('**/base.txt', '**/local.txt') }}" + restore-keys: | + ${{ runner.os }}-pip- + - name: "install tox" + run: | + python -m pip install --upgrade pip + pip install tox + - name: "run tox" + env: + TOXENV: py3 + run: | + tox diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..080aff6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,141 @@ +# project specific files +__init__.py +log.py + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST +tar-src/ + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# cardano pieces +*.skey +*.vkey +*signed.json +*.addr +*.cbor diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 0000000..9632db6 --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,5 @@ +# Configurtion file for Markdown lint. Add exceptions here. +default: true + +# Exceptions, example given, MD045 +# MD012: false # no multiple blank-lines. diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..3bc95d2 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,46 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-yaml + - id: check-json + - id: check-toml + - id: end-of-file-fixer + - id: trailing-whitespace + - id: check-case-conflict +- repo: https://github.com/psf/black + rev: 23.3.0 + hooks: + - id: black +- repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + language_version: python3 +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.0.270 + hooks: + - id: ruff + args: ["--fix", "--show-fixes"] +- repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.35.0 + hooks: + - id: markdownlint +- repo: https://github.com/codespell-project/codespell + rev: v2.2.4 + hooks: + - id: codespell + args: [-I, .codespellignore] +- repo: local + hooks: + - id: pylint + name: pylint + entry: pylint + language: python + language_version: python3 + args: + [ + "-rn", # Only display messages. + "-sn", # Don't display the pylint score. + "--rcfile=.pylintrc" + ] diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..49171fd --- /dev/null +++ b/.pylintrc @@ -0,0 +1,37 @@ +# Pylint configuration. +# +# .pylintrc guide: https://github.com/PyCQA/pylint/blob/cfc393a8dff9ec09bd2fcb25857e772ae04a4991/examples/pylintrc +# + +[MAIN] +extension-pkg-whitelist= + pydantic, # binary module validation, Pydantic/Pylint recommendation. + +ignore= + LICENSE, + .pylintrc, + +ignore-patterns= + ^(.+).ini$, + ^(.+).md$, + ^(.+).sh$, + ^(.+).service$, + ^(.+).json, + ^(.+).yml, + ^(.+).yaml, + ^(.+).toml, + ^(.+).html, + ^(.+).htm, + ^(.+).svg, + ^\., + +ignore-paths= + requirements/., + tests/fixtures/vcrpy/., + Makefile, + +[MESSAGES CONTROL] + +disable = + C0301, # line-length too long, see Black documented recommendations. + C0115, # No docstring for Class. diff --git a/.ruff.toml b/.ruff.toml new file mode 100644 index 0000000..2dc8835 --- /dev/null +++ b/.ruff.toml @@ -0,0 +1,47 @@ +# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default. +select = ["E", "F"] +ignore = [ + "E501", # line-length too long, set via black. +] + +# Allow autofix for all enabled rules (when `--fix`) is provided. +fixable = ["A", "B", "C", "D", "E", "F", "G", "I", "N", "Q", "S", "T", "W", "ANN", "ARG", "BLE", "COM", "DJ", "DTZ", "EM", "ERA", "EXE", "FBT", "ICN", "INP", "ISC", "NPY", "PD", "PGH", "PIE", "PL", "PT", "PTH", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "TCH", "TID", "TRY", "UP", "YTT"] +unfixable = [] + +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".mypy_cache", + ".nox", + ".pants.d", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "venv", +] + +# Same as Black. +line-length = 88 + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +# Assume Python 3.10. +target-version = "py310" + +[mccabe] +# Unlike Flake8, default to a complexity level of 10. +max-complexity = 10 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..23f4016 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,46 @@ +{ + "editor.insertSpaces": true, + "editor.tabSize": 4, + "editor.rulers": [ + 79 + ], + "editor.detectIndentation": false, + "files.trimTrailingWhitespace": true, + "files.insertFinalNewline": true, + "python.linting.mypyEnabled": false, + "python.linting.flake8Enabled": true, + "python.linting.pylintEnabled": true, + "python.linting.lintOnSave": true, + "git.inputValidationSubjectLength": 50, + "git.inputValidationLength": 72, + "[git-commit]": { + "editor.rulers": [ + 50, + 72 + ] + }, + "[python]": { + "editor.rulers": [ + 72, + 79, + 120 + ], + "editor.formatOnSave": true, + "editor.defaultFormatter": "ms-python.black-formatter" + }, + "[go]": { + "editor.rulers": [ + 72, + 79 + ] + }, + "[markdown]": { + "editor.rulers": [80] + }, + "files.eol": "\n", + "cSpell.words": [ + "gmtime", + "levelname", + "upgrader" + ] +} diff --git a/README.md b/README.md index ef635e0..a2baff5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,75 @@ -# Template repository +# Orcfax Simple Sign -Template repository for Python projects. +Provides simple signing and verification of data following the approach +outlined in [CIP-8][CIP-8]. + +[CIP-8]: https://cips.cardano.org/cip/CIP-0008 + +The command-line application and library is intended to provide helper functions +for consistent signing and verification functions across distributed dapps and +other applications utilizing Cardano off-chain. + +## Creating a signing key + +You need a signing key and address that can then be used to verify the source +of the signed data. + +> If you hold the key, you hold the address that can be used to verify data. + +The `cardano-cli` can be used to generate a signing key. E.g. on +preview/preprod. + +```sh +cardano-cli address key-gen \ + --verification-key-file payment.vkey \ + --signing-key-file payment.skey +``` + +```sh +cardano-cli address build \ + --payment-verification-key-file payment.vkey \ + --out-file payment.addr \ + --mainnet +``` + +The key can then be given to the app with arbitrary data to be signed. + +## Basic signing and verification + +### Signing + +Example signing with `payment.skey` with addr `...`: + +```sh +python sign.py sign -d "arbitrary data" -s "$(cat payment.skey)" +``` + +Outputs: + + +```text +TODO +``` + + +### Verification + +Example verification, looking for addr `...`: + +```sh +python sign.py verify \ + -d "TODO" +``` + +Outputs: + +```python +{ + 'verified': True, + 'message': 'arbitrary data', + 'signing_address': 'addr1v896758x5jv32tdzx5tl8hftasmxd8ydlqmtxwdrpqyv9wchg8mj2' +} +``` ## Developer install diff --git a/pyproject.toml b/pyproject.toml index 3c272f3..bd8752a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,92 +1,46 @@ [project] -# For more information on package metadata see: -# -# * https://packaging.python.org/specifications/core-metadata/ -# -# For even greater information on this file, see the pypa/sampleproject: -# -# * https://github.com/pypa/sampleproject/blob/fc8d83efd5749853507be959d858629d0aaf5c6e/pyproject.toml -# -# Required. -name = "orcfax-template" +name = "simple-sign" -# Required. -# -# NB. Use suffixes such as rc1 liberally as the version, once it is on PyPi -# will be permanent. I.e. It isn't possible to replace a 1.0.0 which is not -# ready for release if it was accidentally uploaded. It would need to become -# 1.0.1 to replace 1.0.0 - but only if rc1, rc2 etc. wasn' appended at first. -# -version = "0.0.1-rc2" +dynamic = ["version"] -# Optional. -description = "A one-line description of this Python project" +description = "helper functions signing simple data using Cardano primitives" -# Optional. readme = "README.md" -# Supported python versions. Optional, but helpful. -requires-python = ">=3.9" +requires-python = ">=3.10" -# Optional. -license = {file = "LICENSE.txt"} +keywords = ["cardano", "signing"] -# Optional. -keywords = ["template", "setuptools", "development"] - -# Optional. authors = [ - {name = "A. Developer", email = "author_one@example.com" }, - {name = "A.N. Other Developer", email = "author_two@example.com" }, -] - -# Optional. E.g. individual or organisation, e.g. app-support@organization.com. -maintainers = [ - {name = "A. Project Maintainer", email = "maintainer@example.com" } + {name = "R. Spencer", email = "ross@orcfax.io" }, + {name = "George Orcfax", email = "george@orcfax.io" }, ] -# Optional. -# -# For a list of valid classifiers, see https://pypi.org/classifiers/ -# classifiers = [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", - "Topic :: Software Development :: Build Tools", - "License :: OSI Approved :: MIT License", + "License :: OSI Approved :: Apache 2.0", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3 :: Only", ] -# Optional. -# -# Packages installed by pip when the project is installed. dependencies = [ - # "package1==6.0.0", - # "package2==6.0.0", + "pycardano==0.10.0", ] -# Optional. -# [project.urls] -"Homepage" = "https://example.com/project/" -"Bug Reports" = "https://example.com/issues/" -"Funding" = "https://example.com/funding/" -"Say Thanks!" = "http://example.com/thanks/" -"Source" = "https://example.com/sourcecode/" +"Homepage" = "https://orcfax.io/" +"Bug Reports" = "https://github.com/orcfax/simple-sign/issues/" +"Source" = "https://github.com/orcfax/simple-sign/" -# Optional. -# -# Provide a command line executable called `template` which executes the -# function `main` from this package when invoked. -# [project.scripts] -template = "template.template:main" +simple-sign = "simple_sign.sign:main" [build-system] -requires = ["setuptools>=67.8.0", "wheel"] +requires = ["setuptools>=67.8.0", "wheel", "setuptools_scm[toml]>=7.1.0"] build-backend = "setuptools.build_meta" + +[tool.setuptools_scm] diff --git a/requirements/local.txt b/requirements/local.txt index 988c094..7ed06e9 100644 --- a/requirements/local.txt +++ b/requirements/local.txt @@ -2,8 +2,8 @@ -r requirements.txt pdoc3==0.10.0 -pre-commit==3.6.0 +pre-commit==3.7.0 pip-upgrader==1.4.15 -pylint==3.0.3 -pytest==8.0.0 -tox==4.12.1 +pylint==3.1.0 +pytest==8.1.1 +tox==4.14.2 diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 49c4f89..a729130 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1 +1,3 @@ # requirements for the production project. + +pycardano==0.10.0 diff --git a/sign.py b/sign.py new file mode 100644 index 0000000..6994dee --- /dev/null +++ b/sign.py @@ -0,0 +1,12 @@ +"""Entry-point for simple sign.""" + +from src.simple_sign import sign + + +def main(): + """Primary entry point for this script.""" + sign.main() + + +if __name__ == "__main__": + main() diff --git a/src/template/__init__.py b/src/simple_sign/__init__.py similarity index 100% rename from src/template/__init__.py rename to src/simple_sign/__init__.py diff --git a/src/simple_sign/sign.py b/src/simple_sign/sign.py new file mode 100644 index 0000000..a33c9d5 --- /dev/null +++ b/src/simple_sign/sign.py @@ -0,0 +1,140 @@ +"""Python template repository. + +Baseline template for future Python code related to this project. + +Replace this docstring and code below with your own code as required. +""" + +import argparse +import logging +import sys +import time +from typing import Final + +import pycardano as pyc + +try: + from src.simple_sign.version import get_version +except ModuleNotFoundError: + try: + from version import get_version + except: + from simple_sign.version import get_version + +# Set up logging. +logging.basicConfig( + format="%(asctime)-15s %(levelname)s :: %(filename)s:%(lineno)s:%(funcName)s() :: %(message)s", # noqa: E501 + datefmt="%Y-%m-%d %H:%M:%S", + level="INFO", + handlers=[ + logging.StreamHandler(), + ], +) + +# Format logs using UTC time. +logging.Formatter.converter = time.gmtime + + +logger = logging.getLogger(__name__) + + +def signature_in_license_pool(): + """Validate whether signing key matches one of those in a pool of + licenses associated with the project and return True if so. + """ + raise NotImplementedError("reading from license pool is not yet implemented") + + +def signature_in_constitution_datum_utxo(): + """Validate whether signing key matches one of those a list of + addresses in a given constitution UTxO. + """ + raise NotImplementedError("reading from datum is not yet implemented") + + +def signature_in_constitution_config(): + """Validate whether signing key matches one of those listed in a + configuration file. + """ + raise NotImplementedError("reading from config is not yet implemented") + + +def sign_with_key(data: str, signing_key: str): + """Sign with an signing key.""" + skey = pyc.SigningKey.from_json(signing_key) + vkey = pyc.VerificationKey.from_signing_key(skey) + logger.info("signing with addr: %s", pyc.Address(vkey.hash())) + return pyc.sign(data, skey) + + +def signing_handler(data: str, signing_key: str): + """Handle signing functions.""" + return sign_with_key(data, signing_key) + + +def verify_signature(data: str): + """Verify a signature with an address.""" + try: + status = pyc.verify(data) + except (TypeError, ValueError) as err: + # Message might not be invalid signed-CBOR or simply unexpected + # data. + logger.info("cannot decode message: %s'' (%s)", data, err) + return { + "verified": False, + "message": None, + "signing_address": None, + } + # Message from pycardano does not treat address as a string. + return { + "verified": status["verified"], + "message": f"{status['message']}", + "signing_address": f"{status['signing_address']}", + } + + +def verify_handler(data: str): + """Verify input data.""" + return verify_signature(data) + + +def main() -> None: + """Primary entry point for this script. + + Useful article on sub-commands (which are strangely harder than they should be): + + ```text + https://dev.to/taikedz/ive-parked-my-side-projects-3o62 + ``` + + """ + arg_sign: Final[str] = "sign" + arg_verify: Final[str] = "verify" + arg_version: Final[str] = "version" + parser = argparse.ArgumentParser( + prog="simple signer", + description="provides helper functions signing simple data using Cardano primitives", + epilog="for more information visit https://orcfax.io/", + ) + subparsers = parser.add_subparsers(dest="cmd") + subparsers.add_parser(arg_sign) + verify = subparsers.add_parser(arg_verify) + sign = subparsers.add_parser(arg_sign) + subparsers.add_parser(arg_version) + verify.add_argument("-d", "--data", type=str, help="data to verify") + sign.add_argument("-d", "--data", type=str, help="data to sign") + sign.add_argument("-s", "--signing_key", type=str, help="signing key") + args = parser.parse_args() + if not args.cmd: + parser.print_usage() + sys.exit() + if args.cmd == arg_sign: + print(signing_handler(args.data, args.signing_key)) + if args.cmd == arg_verify: + print(verify_handler(args.data)) + if args.cmd == arg_version: + print(f"simple-sign version: {get_version()}") + + +if __name__ == "__main__": + main() diff --git a/src/simple_sign/version.py b/src/simple_sign/version.py new file mode 100644 index 0000000..ef93a4a --- /dev/null +++ b/src/simple_sign/version.py @@ -0,0 +1,14 @@ +"""Simple sign version.""" + +from typing import Final +from importlib.metadata import PackageNotFoundError, version + +def get_version(): + """Return information about the version of this application.""" + __version__ = "0.0.0-dev" + try: + __version__ = version("simple-sign") + except PackageNotFoundError: + # package is not installed + pass + return __version__ diff --git a/src/template/template.py b/src/template/template.py deleted file mode 100644 index 6e59d81..0000000 --- a/src/template/template.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Python template repository. - -Baseline template for future Python code related to this project. - -Replace this docstring and code below with your own code as required. -""" - -import logging -import time - -# Set up logging. -logging.basicConfig( - format="%(asctime)-15s %(levelname)s :: %(filename)s:%(lineno)s:%(funcName)s() :: %(message)s", # noqa: E501 - datefmt="%Y-%m-%d %H:%M:%S", - level="INFO", - handlers=[ - logging.StreamHandler(), - ], -) - -# Format logs using UTC time. -logging.Formatter.converter = time.gmtime - - -logger = logging.getLogger(__name__) - - -def main() -> None: - """Primary entry point for this script.""" - - logger.info( - "hello world!" - ) # logging is lowercase, and in other cases should be informative. - - -if __name__ == "__main__": - main() diff --git a/template.py b/template.py deleted file mode 100644 index fb4ba69..0000000 --- a/template.py +++ /dev/null @@ -1,21 +0,0 @@ -"""Python template repository. - -Baseline template for future Python code related to this project. - -This script provides a user facing entry point for code in src/template -which should eventually be replaced with the new code this template is -being used for. - -Replace this docstring and code below with your own code as required. -""" - -from src.template import template - - -def main(): - """Primary entry point for this script.""" - template.main() - - -if __name__ == "__main__": - main() diff --git a/tests/test_main.py b/tests/test_main.py deleted file mode 100644 index 7c45bed..0000000 --- a/tests/test_main.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Placeholder tests.""" - -from src.template.template import main - - -def test_none(): - """Ensure the main function for the template repository exists.""" - assert main diff --git a/tests/test_sign.py b/tests/test_sign.py new file mode 100644 index 0000000..8b0bcbd --- /dev/null +++ b/tests/test_sign.py @@ -0,0 +1,64 @@ +"""Tests for simple sign. + +Creating new test signatures: + +```sh +cardano-cli address key-gen \ + --verification-key-file /tmp/payment.vkey \ + --signing-key-file /tmp/payment.skey +``` + +```sh +cardano-cli address build \ + --payment-verification-key-file /tmp/payment.vkey \ + --out-file /tmp/payment1.addr \ + --testnet-magic 1 +``` + +""" + + +from src.simple_sign.sign import sign_with_key, verify_signature + + +def test_sign(): + """Ensure the signing function works as anticipated. + + NB. parameterize these tests once this library has greater use within + the stack. + """ + + skey = """ + { + "type": "PaymentSigningKeyShelley_ed25519", + "description": "Payment Signing Key", + "cborHex": "5820d22cabb9787df7bdf223b71d7be8cfc1e1f89a1154e635259530264d7be1263e" + } + """ + m1 = "123" + m2 = "plain-text-string" + m3 = "{'test': 'data'}" + s1 = sign_with_key(data=m1, signing_key=skey) + s2 = sign_with_key(data=m2, signing_key=skey) + s3 = sign_with_key(data=m3, signing_key=skey) + assert verify_signature(s1).get("message") == m1 + assert verify_signature(s2).get("message") == m2 + assert verify_signature(s3).get("message") == m3 + + +def test_verify(): + """Ensure that the verify function works as anticipated.""" + + unsigned_cbor_data = "a46c626c6f636b5f6865696768741a068a119a66736f75726365674d696e537761706761646472657373786761646472317a38736e7a376334393734767a647078753635727570686c337a6a64767478773873747266326332746d716e787a6636673838326e3673613267786e6b34326865617675377564646c356a646c30656b746635663230346d6d63377333796b75663965666565647382a36466656564684144412d46414354647574786f784230303463343461643766323930616237653464326533363933306434373262316563613162393761366535393830353265393831323738383364333433613638233067616d6f756e7473a2686c6f76656c6163651b0000004d295541ce784f61333933313639316635633465363564303163343239653437336430646432346335316166646236646166383865363332613663316535312e366637323633363636313738373436663662363536651b000008520f46ea48a36466656564674144412d574d54647574786f784235393734333531313064333431383364626665623862663133623432333234363239346663313266633139396362316539383135393764303366616666326434233067616d6f756e7473a2686c6f76656c6163651b000002dc088bd67a785931643766333362643233643835653161323564383764383666616334663139396333313937613266376166656236363261306633346531652e37373666373236633634366436663632363936633635373436663662363536651b000008bba11bb36f" + assert verify_signature(unsigned_cbor_data) == { + "verified": False, + "message": None, + "signing_address": None, + } + + cbor_data = "84584da301276761646472657373581d61458df11b6eeb14e3ee18bdc0d3fb8e2c2c5fd355e149d3a21d3a0627045820f9d8454265d02f54c7f658972189f6278a81c87762e911665603834bee9d31b6a166686173686564f45902957b22626c6f636b5f686569676874223a203130393731313737302c2022736f75726365223a20224d696e53776170222c202261646472657373223a202261646472317a38736e7a376334393734767a647078753635727570686c337a6a64767478773873747266326332746d716e787a6636673838326e3673613267786e6b34326865617675377564646c356a646c30656b746635663230346d6d63377333796b756639222c20226665656473223a205b7b2266656564223a20224144412d46414354222c20227574786f223a2022303034633434616437663239306162376534643265333639333064343732623165636131623937613665353938303532653938313237383833643334336136382330222c2022616d6f756e7473223a207b226c6f76656c616365223a203333313430353933353035342c202261333933313639316635633465363564303163343239653437336430646432346335316166646236646166383865363332613663316535312e36663732363336363631373837343666366236353665223a20393134383533363634363231367d7d2c207b2266656564223a20224144412d574d54222c20227574786f223a2022353937343335313130643334313833646266656238626631336234323332343632393466633132666331393963623165393831353937643033666166663264342330222c2022616d6f756e7473223a207b226c6f76656c616365223a20333134343035393434323831302c202231643766333362643233643835653161323564383764383666616334663139396333313937613266376166656236363261306633346531652e3737366637323663363436643666363236393663363537343666366236353665223a20393630313935343835333734337d7d5d7d58407c04f01d0cabe082eba26b5755e3d5dc6f815676fea67b4747c3ee572d7235cc3f2813fde7358af7f8b7588e81454070372d31b6609e30666ef602fa96d41e09" + assert verify_signature(cbor_data) == { + "verified": True, + "message": '{"block_height": 109711770, "source": "MinSwap", "address": "addr1z8snz7c4974vzdpxu65ruphl3zjdvtxw8strf2c2tmqnxzf6g882n6sa2gxnk42heavu7uddl5jdl0ektf5f204mmc7s3ykuf9", "feeds": [{"feed": "ADA-FACT", "utxo": "004c44ad7f290ab7e4d2e36930d472b1eca1b97a6e598052e98127883d343a68#0", "amounts": {"lovelace": 331405935054, "a3931691f5c4e65d01c429e473d0dd24c51afdb6daf88e632a6c1e51.6f7263666178746f6b656e": 9148536646216}}, {"feed": "ADA-WMT", "utxo": "597435110d34183dbfeb8bf13b423246294fc12fc199cb1e981597d03faff2d4#0", "amounts": {"lovelace": 3144059442810, "1d7f33bd23d85e1a25d87d86fac4f199c3197a2f7afeb662a0f34e1e.776f726c646d6f62696c65746f6b656e": 9601954853743}}]}', + "signing_address": "addr1v9zcmugmdm43fclwrz7up5lm3ckzch7n2hs5n5azr5aqvfcxmstta", + }