diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..3cf63bc --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,70 @@ +# 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, sex characteristics, 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 [ZKPlus team](https://zk-plus.github.io/about/contact). 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 [jessie Squire's code of conduct](https://github.com/jessesquires/.github/blob/main/CONTRIBUTING.md#book-code-of-conduct) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..f30956c --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,189 @@ +# Contributing Guidelines + +*Pull requests, bug reports, and all other forms of contribution are welcomed and highly encouraged!* :octocat: + +### Contents + +- [Contributing Guidelines](#contributing-guidelines) + - [Contents](#contents) + - [:book: Code of Conduct](#book-code-of-conduct) + - [:bulb: Asking Questions](#bulb-asking-questions) + - [:inbox\_tray: Opening an Issue](#inbox_tray-opening-an-issue) + - [:lock: Reporting Security Issues](#lock-reporting-security-issues) + - [:beetle: Bug Reports and Other Issues](#beetle-bug-reports-and-other-issues) + - [:love\_letter: Feature Requests](#love_letter-feature-requests) + - [:mag: Triaging Issues](#mag-triaging-issues) + - [:repeat: Submitting Pull Requests](#repeat-submitting-pull-requests) + - [:memo: Writing Commit Messages](#memo-writing-commit-messages) + - [:white\_check\_mark: Code Review](#white_check_mark-code-review) + - [:nail\_care: Coding Style](#nail_care-coding-style) + - [:medal\_sports: Certificate of Origin](#medal_sports-certificate-of-origin) + - [No Brown M\&M's](#no-brown-mms) + - [:pray: Credits](#pray-credits) + +> **This guide serves to set clear expectations for everyone involved with the project so that we can improve it together while also creating a welcoming space for everyone to participate. Following these guidelines will help ensure a positive experience for contributors and maintainers.** + +## :book: Code of Conduct + +Please review our [Code of Conduct](https://github.com/ZK-Plus/ZnaKes/blob/main/CODE_OF_CONDUCT.md). It is in effect at all times. We expect it to be honored by everyone who contributes to this project. Acting like an asshole will not be tolerated. + +## :bulb: Asking Questions + +In short, GitHub issues are not the appropriate place to debug your specific project, but should be reserved for filing bugs and feature requests. + +## :inbox_tray: Opening an Issue + +Before [creating an issue](https://github.com/ZK-Plus/ZnaKes/issues), check if you are using the latest version of the project. If you are not up-to-date, see if updating fixes your issue first. + +### :lock: Reporting Security Issues + +This is an experimental project and should not be used in production environments. + +### :beetle: Bug Reports and Other Issues + +A great way to contribute to the project is to send a detailed issue when you encounter a problem. We always appreciate a well-written, thorough bug report. :v: + +In short, since you are most likely a developer, **provide a ticket that you would like to receive**. + +- **Review the documentation** before opening a new issue. + +- **Do not open a duplicate issue!** Search through existing issues to see if your issue has previously been reported. If your issue exists, comment with any additional information you have. You may simply note "I have this problem too", which helps prioritize the most common problems and requests. + +- **Prefer using [reactions](https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/)**, not comments, if you simply want to "+1" an existing issue. + +- **Fully complete the provided issue template.** The bug report template requests all the information we need to quickly and efficiently address your issue. Be clear, concise, and descriptive. Provide as much information as you can, including steps to reproduce, stack traces, compiler errors, library versions, OS versions, and screenshots (if applicable). + +- **Use [GitHub-flavored Markdown](https://help.github.com/en/github/writing-on-github/basic-writing-and-formatting-syntax).** Especially put code blocks and console outputs in backticks (```). This improves readability. + +## :love_letter: Feature Requests + +Feature requests are welcome! While we will consider all requests, we cannot guarantee your request will be accepted. We want to avoid [feature creep](https://en.wikipedia.org/wiki/Feature_creep). Your idea may be great, but also out-of-scope for the project. If accepted, we cannot make any commitments regarding the timeline for implementation and release. However, you are welcome to submit a pull request to help! + +- **Do not open a duplicate feature request.** Search for existing feature requests first. If you find your feature (or one very similar) previously requested, comment on that issue. + +- **Fully complete the provided issue template.** The feature request template asks for all necessary information for us to begin a productive conversation. + +- Be precise about the proposed outcome of the feature and how it relates to existing features. Include implementation details if possible. + +## :mag: Triaging Issues + +You can triage issues which may include reproducing bug reports or asking for additional information, such as version numbers or reproduction instructions. Any help you can provide to quickly resolve an issue is very much appreciated! + +## :repeat: Submitting Pull Requests + +We **love** pull requests! Before [forking the repo](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) and [creating a pull request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/proposing-changes-to-your-work-with-pull-requests) for non-trivial changes, it is usually best to first open an issue to discuss the changes, or discuss your intended approach for solving the problem in the comments for an existing issue. + +For most contributions, after your first pull request is accepted and merged, you will be [invited to the project](https://help.github.com/en/github/setting-up-and-managing-your-github-user-account/inviting-collaborators-to-a-personal-repository) and given **push access**. :tada: + +*Note: All contributions will be licensed under the project's license.* + +- **Smaller is better.** Submit **one** pull request per bug fix or feature. A pull request should contain isolated changes pertaining to a single bug fix or feature implementation. **Do not** refactor or reformat code that is unrelated to your change. It is better to **submit many small pull requests** rather than a single large one. Enormous pull requests will take enormous amounts of time to review, or may be rejected altogether. + +- **Coordinate bigger changes.** For large and non-trivial changes, open an issue to discuss a strategy with the maintainers. Otherwise, you risk doing a lot of work for nothing! + +- **Prioritize understanding over cleverness.** Write code clearly and concisely. Remember that source code usually gets written once and read often. Ensure the code is clear to the reader. The purpose and logic should be obvious to a reasonably skilled developer, otherwise you should add a comment that explains it. + +- **Follow existing coding style and conventions.** Keep your code consistent with the style, formatting, and conventions in the rest of the code base. When possible, these will be enforced with a linter. Consistency makes it easier to review and modify in the future. + +- **Include test coverage.** Add unit tests or UI tests when possible. Follow existing patterns for implementing tests. + +- **Update the example project** if one exists to exercise any new functionality you have added. + +- **Add documentation.** Document your changes with code doc comments or in existing guides. + +- **Update the CHANGELOG** for all enhancements and bug fixes. Include the corresponding issue number if one exists, and your GitHub username. (example: "- Fixed crash in profile view. #123 @jessesquires") + +- **Use the repo's default branch.** Branch from and [submit your pull request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork) to the repo's default branch. Usually this is `main`, but it could be `dev`, `develop`, or `master`. + +- **[Resolve any merge conflicts](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/resolving-a-merge-conflict-on-github)** that occur. + +- **Promptly address any CI failures**. If your pull request fails to build or pass tests, please push another commit to fix it. + +- When writing comments, use properly constructed sentences, including punctuation. + +- Use spaces, not tabs. + +## :memo: Writing Commit Messages + +Please follow [Angular commit style](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#commit-message-format). + + +``` +(optional scope): short summary in present tense + +(optional body: explains motivation for the change) + +(optional footer: note BREAKING CHANGES here, and issues to be closed) +``` + +`type` refers to the kind of change made and is usually one of: + + feat: A new feature. + + fix: A bug fix. + + docs: Documentation changes. + + style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc). + + refactor: A code change that neither fixes a bug nor adds a feature. + + perf: A code change that improves performance. + + test: Changes to the test framework. + + build: Changes to the build process or tools. + +scope is an optional keyword that provides context for where the change was made. It can be anything relevant to your package or development workflow (e.g., it could be the module or function name affected by the change). + +For more information on commit message refer to the [site](https://py-pkgs.org/07-releasing-versioning.html#automatic-version-bumping) + +## :white_check_mark: Code Review + +- **Review the code, not the author.** Look for and suggest improvements without disparaging or insulting the author. Provide actionable feedback and explain your reasoning. + +- **You are not your code.** When your code is critiqued, questioned, or constructively criticized, remember that you are not your code. Do not take code review personally. + +- **Always do your best.** No one writes bugs on purpose. Do your best, and learn from your mistakes. + +- Kindly note any violations to the guidelines specified in this document. + +## :nail_care: Coding Style + +Consistency is the most important. Following the existing style, formatting, and naming conventions of the file you are modifying and of the overall project. Failure to do so will result in a prolonged review process that has to focus on updating the superficial aspects of your code, rather than improving its functionality and performance. + +For example, if all private properties are prefixed with an underscore `_`, then new ones you add should be prefixed in the same way. Or, if methods are named using camelcase, like `thisIsMyNewMethod`, then do not diverge from that by writing `this_is_my_new_method`. You get the idea. If in doubt, please ask or search the codebase for something similar. + +When possible, style and format will be enforced with a linter. + +## :medal_sports: Certificate of Origin + +*Developer's Certificate of Origin 1.1* + +By making a contribution to this project, I certify that: + +> 1. The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or +> 1. The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or +> 1. The contribution was provided directly to me by some other person who certified (1), (2) or (3) and I have not modified it. +> 1. I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. + +## [No Brown M&M's](https://en.wikipedia.org/wiki/Van_Halen#Contract_riders) + +If you are reading this, bravo dear user and (hopefully) contributor for making it this far! You are awesome. :100: + +To confirm that you have read this guide and are following it as best as possible, **include this emoji at the top** of your issue or pull request: :black_heart: `:black_heart:` + +## :pray: Credits + +Written by [@jessesquires](https://github.com/jessesquires). + +**Please feel free to adopt this guide in your own projects. Fork it wholesale or remix it for your needs.** + +*Many of the ideas and prose for the statements in this document were based on or inspired by work from the following communities:* + +- [Alamofire](https://github.com/Alamofire/Alamofire/blob/master/CONTRIBUTING.md) +- [CocoaPods](https://github.com/CocoaPods/CocoaPods/blob/master/CONTRIBUTING.md) +- [Docker](https://github.com/moby/moby/blob/master/CONTRIBUTING.md) +- [Linux](https://elinux.org/Developer_Certificate_Of_Origin) + +*We commend them for their efforts to facilitate collaboration in their projects.* diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..0afd952 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,40 @@ + + +# **Blank Issue Report** + +## **Describe the issue** + + +* + +--- + +### **Media prove** + + +--- + +### **Your environment** + + + +* OS: +* Python version: +* Pip version: + +--- + +### **Additional context** + + +* diff --git a/.github/ISSUE_TEMPLATE/1-bug-report.md b/.github/ISSUE_TEMPLATE/1-bug-report.md new file mode 100644 index 0000000..4d6ab31 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1-bug-report.md @@ -0,0 +1,78 @@ +--- +name: "🐞 Bug Report" +about: "Report an issue to help the project improve." +title: "[Bug] " +labels: "Type: Bug" + + +--- + +# **🐞 Bug Report** + +## **Describe the bug** + + +* + +--- + +### **Is this a regression?** + + + +--- + +### **To Reproduce** + + + + + +1. +2. +3. +4. + +--- + +### **Expected behaviour** + + +* + +--- + +### **Media prove** + + +--- + +### **Your environment** + + + +* OS: +* Python version: +* Pip version: + +--- + +### **Additional context** + + +* + + diff --git a/.github/ISSUE_TEMPLATE/2-failing-test.md b/.github/ISSUE_TEMPLATE/2-failing-test.md new file mode 100644 index 0000000..2b47c47 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2-failing-test.md @@ -0,0 +1,41 @@ +--- +name: "💉 Failing Test" +about: "Report failing tests or CI jobs." +title: "[Test] " +labels: "Type: Test" + + +--- + +# **💉 Failing Test** + +## **Which jobs/test(s) are failing** + + +* + +--- + +## **Reason for failure/description** + + +--- + +### **Media prove** + + +--- + +### **Additional context** + + +* + + diff --git a/.github/ISSUE_TEMPLATE/3-docs-bug.md b/.github/ISSUE_TEMPLATE/3-docs-bug.md new file mode 100644 index 0000000..170ea83 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/3-docs-bug.md @@ -0,0 +1,60 @@ +--- +name: "📚 Documentation or README.md issue report" +about: "Report an issue in the project's documentation or README.md file." +title: "" +labels: "Documentation" + + +--- +# **📚 Documentation Issue Report** + +## **Describe the bug** + + +* + +--- + +### **To Reproduce** + + + + + +1. +2. +3. +4. + +--- + +### **Media prove** + + +--- + +## **Describe the solution you'd like** + + +* + +--- + +### **Additional context** + + +* + + diff --git a/.github/ISSUE_TEMPLATE/4-feature-request.md b/.github/ISSUE_TEMPLATE/4-feature-request.md new file mode 100644 index 0000000..9638748 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/4-feature-request.md @@ -0,0 +1,45 @@ +--- +name: "🚀🆕 Feature Request" +about: "Suggest an idea or possible new feature for this project." +title: "" +labels: "Type: Feature" + + +--- + +# **🚀 Feature Request** + +## **Is your feature request related to a problem? Please describe.** + + +* + +--- + +## **Describe the solution you'd like** + + +* + +--- + +## **Describe alternatives you've considered** + + +* + +--- + +### **Additional context** + + +* + + diff --git a/.github/ISSUE_TEMPLATE/5-enhancement-request.md b/.github/ISSUE_TEMPLATE/5-enhancement-request.md new file mode 100644 index 0000000..aed25c2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/5-enhancement-request.md @@ -0,0 +1,45 @@ +--- +name: "🚀➕ Enhancement Request" +about: "Suggest an enhancement for this project. Improve an existing feature" +title: "" +labels: "Type: Enhancement" + + +--- + +# **🚀 Enhancement Request** + +## **Is your enhancement request related to a problem? Please describe.** + + +* + +--- + +## **Describe the solution you'd like** + + +* + +--- + +## **Describe alternatives you've considered** + + +* + +--- + +### **Additional context** + + +* + + diff --git a/.github/ISSUE_TEMPLATE/6-security-report.md b/.github/ISSUE_TEMPLATE/6-security-report.md new file mode 100644 index 0000000..fcb7990 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/6-security-report.md @@ -0,0 +1,99 @@ +--- +name: "⚠️ Security Report" +about: "Report an issue to help the project improve." +title: "" +labels: "Type: Security" + + +--- + + + +# **⚠️ Security Report** + +## **Describe the security issue** + + +* + +--- + +### **To Reproduce** + + + + + +1. +2. +3. +4. + +--- + +### **Expected behaviour** + + +* + +--- + +### **Media prove** + + +--- + +### **Your environment** + + + +* OS: +* Python version: +* Pip version: + +--- + +### **Additional context** + + +* diff --git a/.github/ISSUE_TEMPLATE/7-question-support.md b/.github/ISSUE_TEMPLATE/7-question-support.md new file mode 100644 index 0000000..1ad3ed4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/7-question-support.md @@ -0,0 +1,28 @@ +--- +name: "❓ Question or Support Request" +about: "Questions and requests for support." +title: "" +labels: "Type: Question" + + +--- + +# **❓ Question or Support Request** + +## **Describe your question or ask for support.** + + +* + + diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..4cb113f --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,29 @@ +# **Name of PR** + + + +## **Description** + + + +* + +--- + +### **Additional context** + + + +* + + diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml new file mode 100644 index 0000000..ca0ed18 --- /dev/null +++ b/.github/workflows/build-and-publish.yml @@ -0,0 +1,34 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +# GitHub recommends pinning actions to a commit SHA. +# To get a newer version, you will need to update the SHA. +# You can also reference a tag or branch, but the action may change without warning. + +name: Build and Upload Python Package + +on: + release: + types: [published] + +jobs: + release-build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + - name: Build package + run: python -m build + - name: Publish package + uses: pypa/gh-action-pypi-publish@v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml new file mode 100644 index 0000000..cee7bad --- /dev/null +++ b/.github/workflows/code-coverage.yml @@ -0,0 +1,24 @@ +name: Workflow for Codecov +on: [push, pull_request] +jobs: + run: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Python 3.10 + uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install pytest pytest-cov + pip install -r requirements.txt + - name: Run tests and collect coverage + run: pytest --cov znakes + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 diff --git a/.github/workflows/test-and-lint.yml b/.github/workflows/test-and-lint.yml new file mode 100644 index 0000000..c9fdff9 --- /dev/null +++ b/.github/workflows/test-and-lint.yml @@ -0,0 +1,40 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: Python tests and lint + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + run: | + pytest diff --git a/.gitignore b/.gitignore index a98fc2b..75ab9bd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,62 @@ include -lib share -src/.ipynb_checkpoints *.ipynb *.pyc +*.ipynb_checkpoints + .vscode .idea -.ipynb_checkpoints env zokrates_args +.DS_Store + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Django stuff: +*.log +*.pot + +# Sphinx documentation +docs/_build/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6749e0b..cf96948 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,4 @@ +default_stages: [commit] repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.1.0 @@ -15,14 +16,14 @@ repos: - id: debug-statements - id: requirements-txt-fixer - id: flake8 - args: [., --count, --select=E901,E999,F821,F822,F823, --show-source, --statistics] - repo: https://github.com/stefandeml/pre_commit_hooks rev: 507a6c4e4bdb5ffc7d35c1227c177e7a9bb86965 hooks: - id: detect_tab - id: unittest -- repo: https://github.com/ambv/black - rev: 19.3b0 +- repo: https://github.com/lumapps/commit-message-validator + rev: af6ccfc300388bbac591fdd52467cee58d5bd918 hooks: - - id: black - language_version: python3.6 + - id: commit-message-validator + stages: [commit-msg] + args: [--allow-temp] diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..dae8676 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,21 @@ +# Changelog + + + + + +## v0.1.0 (10/01/2024) + +- First release of `ZnaKes` diff --git a/README.md b/README.md index dc9411a..3c51218 100644 --- a/README.md +++ b/README.md @@ -1,158 +1,66 @@ -# ZoKrates pyCrypto + + -This repository contains accompanying crypto application code for the zkSNARKs toolbox [ZoKrates](https://github.com/Zokrates/ZoKrates). -_This is a proof-of-concept implementation. It has not been tested for production._ +# ZnaKes +![python-version](./assets/python-version-badge.svg) +[![codecov](https://codecov.io/gh/ZK-Plus/ZnaKes/graph/badge.svg?token=70C58SFGGK)](https://codecov.io/gh/ZK-Plus/ZnaKes) +[![License: LGPL v3](https://img.shields.io/badge/license-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) +[![FLAKE8](https://img.shields.io/badge/code%20style-flake8-orange.svg)](https://flake8.pycqa.org/en/3.7.7/) -## Install - -Make sure you are running a python 3 runtime. +A one-stop client library which facilitates the creation of arguments for zero-knowledge Proofs. ZnaKes provide an easy interface to generate zk-friendly crypto primitives necessary in efficient circuits developed with popular tools such as ZoKrates, Circom, Noir and so on... -```bash -git clone https://github.com/Zokrates/pycrypto.git -pip install -r requirements.txt -``` +:warning: _This is a proof-of-concept implementation. It has not been tested for production._ -## Example +This repository code is primarily based from [ZoKrates Pycrypto](https://github.com/Zokrates/pycrypto) for the zkSNARKs toolbox [ZoKrates](https://github.com/Zokrates/ZoKrates). +Nonetheless, we plan of adding more primitives support by other tools as well. +Some of these primitives are: -### Compute SNARK-friendly Pedersen hash -Let's create a simple demo, called `demo.py`: -```python -from zokrates_pycrypto.gadgets.pedersenHasher import PedersenHasher - -preimage = bytes.fromhex("1616") -# create an instance with personalisation string -hasher = PedersenHasher(b"test") -# hash payload -digest = hasher.hash_bytes(preimage) -print(digest) -# x:2685288813799964008676827085163841323150845457335242286797566359029072666741, -# y:3621301112689898657718575625160907319236763714743560759856749092648347440543 - -# write ZoKrates DSL code to disk -path = "pedersen.code" -hasher.write_dsl_code(path) - -# write witness arguments to disk -path = "pedersen_witness.txt" -witness = hasher.gen_dsl_witness_bytes(preimage) -with open(path, "w+") as f: - f.write(" ".join(witness)) -``` +- Poseidon, mimc and pedersen hashes. +- EdDSA signatures for multiple curves (`BN254`, `BLS12_381`...) +- ... and more! -We can now can run this python script via: -```bash -python demo.py -``` -which should create the ZoKrates DSL code file `pedersen.code`, as well as a file which contains the witness `pedersen_witness.txt`. +## Install -Make sure you have the `zokrates` executable in the same folder. Then run the following command to compile the SNARK-circuit: -```bash -./zokrates compile -i pedersen.code -``` +Make sure you are running a python 3 runtime. -We can now conpute the witness: ```bash -`cat zokrates_witness.txt | ./zokrates compute-witness` - -Witness: - -~out_1 3621301112689898657718575625160907319236763714743560759856749092648347440543 -~out_0 2685288813799964008676827085163841323150845457335242286797566359029072666741 +git clone https://github.com/ZK-Plus/ZnaKes.git && cd ZnaKes +pip install -r requirements.txt ``` -As you can easily verify we get the same pedersen hash point for the Python and ZoKrates implementation. +## Example ### Create and verify Eddsa signature -Let's create a simple demo, called `demo.py`: +Let's create a simple demo, called `demo.py`, in which we sign a hashed message with the BabyJubJub curve: ```python import hashlib -from zokrates_pycrypto.eddsa import PrivateKey, PublicKey -from zokrates_pycrypto.field import FQ -from zokrates_pycrypto.utils import write_signature_for_zokrates_cli +from znakes.curves import BabyJubJub +from znakes.eddsa import PrivateKey, PublicKey if __name__ == "__main__": raw_msg = "This is my secret message" msg = hashlib.sha512(raw_msg.encode("utf-8")).digest() - # sk = PrivateKey.from_rand() - # Seeded for debug purpose - key = FQ(1997011358982923168928344992199991480689546837621580239342656433234255379025) - sk = PrivateKey(key) + sk = PrivateKey.from_rand() sig = sk.sign(msg) pk = PublicKey.from_private(sk) is_verified = pk.verify(sig, msg) print(is_verified) - - path = 'zokrates_inputs.txt' - write_signature_for_zokrates_cli(pk, sig, msg, path) -``` - -We can now can run this python script via: - -```bash -python demo.py -``` - -which should create a file called `zokrates_inputs.txt`. - -We can now create a small ZoKrates DSL file which wraps the existing `verifyEddsa` function in the standard library. - -``` -from "ecc/babyjubjubParams" import BabyJubJubParams -import "signatures/verifyEddsa.code" as verifyEddsa -import "ecc/babyjubjubParams.code" as context - -def main(private field[2] R, private field S, field[2] A, u32[8] M0, u32[8] M1) -> (bool): - - BabyJubJubParams context = context() - - bool isVerified = verifyEddsa(R, S, A, M0, M1, context) - - return isVerified -```` - -After compiling this file we can now pass our input arguments into witness generation: - -`cat zokrates_inputs.txt | ./zokrates compute-witness` - -## CLI Usage - -`pycrypto` also provides a simple command-line interface to make it easy to integrate the used crypto primitives into your existing application code. - -Some examples: - -### Compute SNARK-friendly Pedersen hash -```bash -python cli.py hash 3755668da8deabd8cafbe1c26cda5a837ed5f832665c5ef94725f6884054d9083755668da8deabd8cafbe1c26cda5a837ed5f832665c5ef94725f6884054d908 -``` -where the first argument denotes the preimage as a hexstring. - -### Create and verify an EdDSA signature -```bash -python cli.py keygen -# => 37e334c51386a5c92152f592ef264b82ad52cf2bbfb6cee1c363e67be97732a ab466cd8924518f07172c0f8c695c60f77c11357b461d787ef31864a163f3995 -# Private and public key - -python cli.py sig-gen 37e334c51386a5c92152f592ef264b82ad52cf2bbfb6cee1c363e67be97732a 11dd22 -# => 172a1794976d7d0272148c4be3b7ad74fd3a82376cd5995fc4d274e3593c0e6c 24e96be628208a9800336d23bd31318d8a9b95bc9bd8f6f01cae207c05062523 -# R and S element of EdDSA signature - -python cli.py sig-verify ab466cd8924518f07172c0f8c695c60f77c11357b461d787ef31864a163f3995 11dd22 172a1794976d7d0272148c4be3b7ad74fd3a82376cd5995fc4d274e3593c0e6c 24e96be628208a9800336d23bd31318d8a9b95bc9bd8f6f01cae207c05062523 -# => True ``` ## Contributing -We happily welcome contributions. You can either pick an existing issue, or reach out on [Gitter](https://gitter.im/ZoKrates/Lobby). +We happily welcome contributions. You can either pick an existing issue or create a new issue. Before that make sure you have read our [CODE_OF_CONDUCT](.github/CODE_OF_CONDUCT.md) and [CONTRIBUTION GUIDELINES](.github/CONTRIBUTING.md) -Unless you explicitly state otherwise, any contribution you intentionally submit for inclusion in the work shall be licensed as above, without any additional terms or conditions. +Please note that your submited contributions shall be licensed as below, without any additional terms or conditions. ### Setup First install the development packages via `pip install -r requirements-dev.txt`. @@ -162,6 +70,12 @@ You can install it via `pip install pre-commit`. Then you just need to call `pre-commit install`. +## Acknowledgements + +- [ZoKrates dev tem](https://github.com/Zokrates/ZoKrates/graphs/contributors) for providing a great starting point for this project and for the awesome tool ZoKrates. +- [jesse squires](https://github.com/jessesquires/.github) for the great `CONTRIBUTING.md` and `CODE_OF_CONDUCT` guidelines. +- [Josee9988](https://github.com/Josee9988/project-template) for the amazing issue templates. + ## License This repo is released under the GNU Lesser General Public License v3. diff --git a/assets/icon.jpg b/assets/icon.jpg new file mode 100644 index 0000000..b1ee285 Binary files /dev/null and b/assets/icon.jpg differ diff --git a/assets/python-version-badge.svg b/assets/python-version-badge.svg new file mode 100644 index 0000000..8855e36 --- /dev/null +++ b/assets/python-version-badge.svg @@ -0,0 +1 @@ +pythonpython3.9, 3.10, 3.11, 3.123.9, 3.10, 3.11, 3.12 diff --git a/cli.py b/cli.py deleted file mode 100644 index c45c928..0000000 --- a/cli.py +++ /dev/null @@ -1,167 +0,0 @@ -import argparse -import sys -from zokrates_pycrypto.gadgets.pedersenHasher import PedersenHasher -from zokrates_pycrypto.babyjubjub import Point -from zokrates_pycrypto.eddsa import PrivateKey, PublicKey -from zokrates_pycrypto.field import FQ - - -def main(): - parser = argparse.ArgumentParser(description="pycrypto command-line interface") - subparsers = parser.add_subparsers(dest="subparser_name") - - # pedersen hash subcommand - pedersen_parser = subparsers.add_parser( - "hash", - help="Compute a 256bit Pedersen hash. Preimage size is set to 512bit as default", - ) - pedersen_parser.add_argument( - "preimage", nargs=1, help="Provide preimage as hex string" - ) - pedersen_parser.add_argument( - "-s", "--size", type=int, help="Define message size in bits", default=64 - ) - pedersen_parser.add_argument( - "-p", "--personalisation", help="Provide personalisation string", default="test" - ) - - # batch pedersen hash subcommand - pedersen_hasher_parser = subparsers.add_parser( - "batch_hasher", - help="Efficiently compute multiple Pedersen hashes. Support for stdin and interactive promt", - ) - - pedersen_hasher_parser.add_argument( - "-s", "--size", type=int, help="Define message size in bits", default=64 - ) - pedersen_hasher_parser.add_argument( - "-p", "--personalisation", help="Provide personalisation string", default="test" - ) - - # keygen subcommand - keygen_parser = subparsers.add_parser( - "keygen", - help="Returns space separated hex-string for a random private/public keypair on BabyJubJub curve", - ) - keygen_parser.add_argument( - "-p", - "--from_private", - help="Provide existing private key as hex string (64 chars)", - ) - - # eddsa signature generation subcommand - sig_gen_parser = subparsers.add_parser( - "sig-gen", - help="Returns eddsa signature as space separated hex string. Private key and message needs to be provided", - ) - sig_gen_parser.add_argument( - "private_key", nargs=1, help="Provide private key as hexstring (64chars)" - ) - - sig_gen_parser.add_argument( - "message", nargs=1, help="Provide message as hex string" - ) - - # EdDSA signature verify subcommand - sig_verify_parser = subparsers.add_parser( - "sig-verify", help="Verifies a EdDSA signaure. Returns boolean flag for success" - ) - sig_verify_parser.add_argument( - "public_key", nargs=1, help="Provide public key as hex string (64 chars)" - ) - sig_verify_parser.add_argument( - "message", nargs=1, help="Provide message as hex string" - ) - sig_verify_parser.add_argument( - "signature", - nargs=2, - help="Provide signaure as space separated hex sting (2x 64 chars)", - ) - - args = parser.parse_args() - subparser_name = args.subparser_name - - if subparser_name == "hash": - preimage = bytes.fromhex(args.preimage[0]) - if len(preimage) != args.size: - raise ValueError( - "Bad length for preimage: {} vs {}".format(len(preimage), args.size) - ) - - personalisation = args.personalisation.encode("ascii") - point = PedersenHasher(personalisation).hash_bytes(preimage) - digest = point.compress() - - assert len(digest.hex()) == 32 * 2 # check for correct length - print("Hash digest", file=sys.stderr) - print(digest.hex()) - - elif subparser_name == "batch_hasher": - personalisation = args.personalisation.encode("ascii") - ph = PedersenHasher(personalisation) - try: - while True: - x = input() - if x == "exit": - sys.exit(0) - preimage = bytes.fromhex(x) - if len(preimage) != args.size: - raise ValueError( - "Bad length for preimage: {} vs {}".format(len(preimage), 64) - ) - point = ph.hash_bytes(preimage) - digest = point.compress() - assert len(digest.hex()) == 32 * 2 # check for correct length - print(digest.hex()) - except EOFError: - pass - - elif subparser_name == "keygen": - if args.from_private: - fe = FQ(int(args.from_private[0])) - sk = PrivateKey(fe) - else: - sk = PrivateKey.from_rand() - pk = PublicKey.from_private(sk) - - pk_hex = pk.p.compress().hex() - sk_hex = hex(sk.fe.n)[2:] - - print("PrivateKey PublicKey", file=sys.stderr) - print("{} {}".format(sk_hex, pk_hex)) - - elif subparser_name == "sig-gen": - sk_hex = int(args.private_key[0], 16) - sk = PrivateKey(FQ(sk_hex)) - msg = bytes.fromhex(args.message[0]) - - (r, s) = sk.sign(msg) - s_hex = hex(s)[2:] - r_hex = r.compress().hex() - - print("Signature_R Signature_S", file=sys.stderr) - print("{} {}".format(r_hex, s_hex)) - - elif subparser_name == "sig-verify": - r_hex, s_hex = args.signature[0], args.signature[1] - msg = bytes.fromhex(args.message[0]) - pk_hex = args.public_key[0] - - pk = PublicKey(Point.decompress(bytes.fromhex(pk_hex))) - r = Point.decompress(bytes.fromhex(r_hex)) - s = FQ(int(s_hex, 16)) - - success = pk.verify((r, s), msg) - if success: - sys.exit(0) - else: - sys.exit("Could not verfiy signature") - - else: - raise NotImplementedError( - "Sub-command not implemented: {}".format(subparser_name) - ) - - -if __name__ == "__main__": - main() diff --git a/demo.py b/demo.py new file mode 100644 index 0000000..72c8cd7 --- /dev/null +++ b/demo.py @@ -0,0 +1,20 @@ +import hashlib + +from znakes.curves import JubJub +from znakes.eddsa import PrivateKey, PublicKey +from znakes.utils import write_signature_for_zokrates_cli + +if __name__ == "__main__": + + raw_msg = "This is my secret message" + msg = hashlib.sha512(raw_msg.encode("utf-8")).digest() + + sk = PrivateKey.from_rand(JubJub) + sig = sk.sign(msg) + + pk = PublicKey.from_private(sk) + is_verified = pk.verify(sig, msg) + print(is_verified) + write_signature_for_zokrates_cli(pk, sig, msg, "zokrates_inputs.txt") + + \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..08e71a5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,35 @@ +[build-system] +requires = [ + "hatchling>=1.8.0", + "hatch-semver", +] +build-backend = "hatchling.build" + +[project] +name = "ZnaKes" +dynamic = [ + "version" +] +authors = [ + { name="alvaro-alonso", email="aa@ise.tu-berlin.de" }, +] +description = "A python client library to easily generate arguments for zero-knowledge proofs (ZKPs)" +readme = "README.md" +requires-python = ">=3.8" +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Topic :: Education", + "Topic :: Security :: Cryptography", + "Operating System :: OS Independent", + "Intended Audience :: Developers" +] + +[project.urls] +Homepage = "https://github.com/ZK-Plus/ZnaKes" +Issues = "https://github.com/ZK-Plus/ZnaKes/issues" + +[tool.hatch.version] +path = "znakes/__about__.py" +validate-bump = true +scheme = "semver" diff --git a/requirements-dev.txt b/requirements-dev.txt index fbca653..8a8ddfc 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,13 +6,17 @@ black==19.3b0 Click==7.0 entrypoints==0.3 flake8==3.7.7 +hatch==1.9.1 +hatchling==1.21.0 isort==4.3.15 lazy-object-proxy==1.3.1 mccabe==0.6.1 +pybadges==3.0.1 pycodestyle==2.5.0 pyflakes==2.1.1 pylint==2.3.1 +pytest==7.4.4 +pytest-cov==4.1.0 six==1.12.0 toml==0.10.0 -typed-ast==1.3.2 wrapt==1.11.1 diff --git a/tests/babyjubjub_test.py b/tests/babyjubjub_test.py index 26d0ec5..6e664c9 100644 --- a/tests/babyjubjub_test.py +++ b/tests/babyjubjub_test.py @@ -2,19 +2,18 @@ from os import urandom -from zokrates_pycrypto.field import FQ -from zokrates_pycrypto.babyjubjub import Point -from zokrates_pycrypto.babyjubjub import JUBJUB_E, JUBJUB_C, JUBJUB_L +from znakes.fields import BN128Field as FQ +from znakes.curves import BabyJubJub class TestJubjub(unittest.TestCase): def _point_g(self): - return Point.generator() + return BabyJubJub.generator() def _point_g_dbl(self): x = 17324563846726889236817837922625232543153115346355010501047597319863650987830 y = 20022170825455209233733649024450576091402881793145646502279487074566492066831 - return Point(FQ(x), FQ(y)) + return BabyJubJub(FQ(x), FQ(y)) # Hardcoded for now till we have automatic test generation for ZoKrates test framework def _fe_rnd(self): @@ -27,7 +26,7 @@ def test_double_via_add(self): def test_cyclic(self): G = self._point_g() - self.assertEqual(G.mult(JUBJUB_E + 1), G) + self.assertEqual(G.mult(BabyJubJub.JUBJUB_E + 1), G) def test_mult_2(self): G = self._point_g() @@ -35,7 +34,7 @@ def test_mult_2(self): self.assertEqual(G_mult2, self._point_g_dbl()) def test_lower_order_p(self): - lp = Point( + lp = BabyJubJub( FQ( 4342719913949491028786768530115087822524712248835451589697801404893164183326 ), @@ -43,9 +42,9 @@ def test_lower_order_p(self): 4826523245007015323400664741523384119579596407052839571721035538011798951543 ), ) - lp_c = lp.mult(JUBJUB_C) - self.assertEqual(lp_c, Point.infinity()) - lp_l = lp.mult(JUBJUB_L) + lp_c = lp.mult(BabyJubJub.JUBJUB_C) + self.assertEqual(lp_c, BabyJubJub.infinity()) + lp_l = lp.mult(BabyJubJub.JUBJUB_L) self.assertEqual(lp_l, lp) def test_multiplicative(self): @@ -54,8 +53,8 @@ def test_multiplicative(self): A = G.mult(a) B = G.mult(b) - ab = (a.n * b.n) % JUBJUB_E # 7006652 - AB = G.mult(ab) + ab = a.n * b.n % BabyJubJub.JUBJUB_E # 7006652 + AB = G.mult(FQ(ab)) self.assertEqual(A.mult(b), AB) self.assertEqual(B.mult(a), AB) @@ -74,8 +73,8 @@ def test_associativity(self): def test_identities(self): G = self._point_g() - self.assertEqual(G + Point.infinity(), G) - self.assertEqual(G + G.neg(), Point.infinity()) + self.assertEqual(G + BabyJubJub.infinity(), G) + self.assertEqual(G + G.neg(), BabyJubJub.infinity()) if __name__ == "__main__": diff --git a/tests/eddsa_test.py b/tests/eddsa_test.py index a87ae6b..e5deeb5 100644 --- a/tests/eddsa_test.py +++ b/tests/eddsa_test.py @@ -1,19 +1,49 @@ import unittest from os import urandom -from zokrates_pycrypto.field import FQ -from zokrates_pycrypto.babyjubjub import Point -from zokrates_pycrypto.eddsa import PublicKey, PrivateKey +from znakes.curves import BabyJubJub, JubJub +from znakes.eddsa import PublicKey, PrivateKey class TestEdDSA(unittest.TestCase): - def test_signverify(self): + def test_signverify_babyjubjub(self): # Hardcoded for now till we have automatic test generation for ZoKrates test framework - key = FQ( - 1997011358982923168928344992199991480689546837621580239342656433234255379025 - ) + key = 1997011358982923168928344992199991480689546837621580239342656433234255379025 - sk = PrivateKey(key) + sk = PrivateKey(key, curve=BabyJubJub) + msg = urandom(32) + sig = sk.sign(msg) + + pk = PublicKey.from_private(sk) + self.assertTrue(pk.verify(sig, msg)) + + def test_signverify_jubjub(self): + # Hardcoded for now till we have automatic test generation for ZoKrates test framework + key = 1997011358982923168928344992199991480689546837621580239342656433234255379025 + + sk = PrivateKey(key, curve=JubJub) + msg = urandom(32) + sig = sk.sign(msg) + + pk = PublicKey.from_private(sk) + self.assertTrue(pk.verify(sig, msg)) + + def test_random_signverify_babyjubjub(self): + # Hardcoded for now till we have automatic test generation for ZoKrates test framework + key = 1997011358982923168928344992199991480689546837621580239342656433234255379025 + + sk = PrivateKey.from_rand(curve=BabyJubJub) + msg = urandom(32) + sig = sk.sign(msg) + + pk = PublicKey.from_private(sk) + self.assertTrue(pk.verify(sig, msg)) + + def test_random_signverify_jubjub(self): + # Hardcoded for now till we have automatic test generation for ZoKrates test framework + key = 1997011358982923168928344992199991480689546837621580239342656433234255379025 + + sk = PrivateKey.from_rand(curve=JubJub) msg = urandom(32) sig = sk.sign(msg) diff --git a/tests/field_test.py b/tests/field_test.py new file mode 100644 index 0000000..c80f1bc --- /dev/null +++ b/tests/field_test.py @@ -0,0 +1,99 @@ +import unittest + +from znakes.fields import FQ, BN128Field, BLS12_381Field + + +class TestField(unittest.TestCase): + + class F11(FQ): + FIELD = 11 + + def setUp(self): + self.zero = self.F11(0) + self.one = self.F11(1) + self.two = self.F11(2) + self.three = self.F11(3) + self.five = self.F11(5) + self.eight = self.F11(8) + + def test_zero(self): + zero = self.F11(11) + self.assertEqual(zero, self.zero) + + def test_one(self): + one = self.F11(12) + self.assertEqual(one, self.one) + + def test_equality(self): + self.assertEqual(self.one, self.F11(1)) + self.assertNotEqual(self.two, self.three) + self.assertNotEqual(BN128Field(1), BLS12_381Field(1)) + + def test_cyclic(self): + self.assertEqual(self.F11(12), self.one) + self.assertEqual(self.F11(23), self.one) + + def test_neg(self): + self.assertEqual(self.F11(-0), self.zero) + self.assertEqual(self.F11(-1), self.F11(10)) + self.assertEqual(-self.one, self.F11(10)) + self.assertEqual(-self.three, self.eight) + + def test_sum(self): + self.assertEqual(self.zero + self.one, self.one) + self.assertEqual(self.three + self.zero, self.three) + + self.assertEqual(self.three + self.five, self.eight) + self.assertEqual(self.five + self.three, self.eight) + + self.assertEqual(self.five + self.eight, self.two) + self.assertEqual(self.F11(12) + self.F11(23), self.two) + + def test_sub(self): + self.assertEqual(self.one - self.zero, self.one) + self.assertEqual(self.three - self.zero, self.three) + + self.assertEqual(self.three - self.five, self.F11(9)) + self.assertEqual(self.five - self.three, self.two) + + self.assertEqual(self.F11(12) - self.F11(23), self.zero) + + def test_mult(self): + self.assertEqual(self.zero * self.one, self.zero) + self.assertEqual(self.eight * self.one, self.eight) + self.assertEqual(self.three * self.three, self.F11(9)) + self.assertEqual(self.three * self.five, self.F11(4)) + + def test_div(self): + self.assertEqual(self.zero / self.three, self.zero) + self.assertEqual(self.eight / self.one, self.eight) + self.assertEqual(self.F11(9) / self.three, self.three) + + def test_inv(self): + self.assertEqual(self.zero.inv(), self.zero) + self.assertEqual(self.one.inv(), self.one) + self.assertEqual(self.eight.inv() * self.eight, self.one) + + def test_power(self): + self.assertEqual(self.zero ** 10, self.zero) + self.assertEqual(self.one ** 10, self.one) + self.assertEqual(self.three ** 0, self.one) + self.assertEqual(self.three ** 1, self.three) + self.assertEqual(self.three ** 3, self.five) + + def test_associativity(self): + res1 = (self.three + self.five) + self.eight + res2 = self.three + (self.five + self.eight) + self.assertEqual(res1, res2) + + res1 = (self.three * self.five) * self.eight + res2 = self.three * (self.five * self.eight) + self.assertEqual(res1, res2) + + def test_distributivity(self): + res1 = (self.three + self.five) * self.eight + res2 = (self.three * self.eight) + (self.five * self.eight) + self.assertEqual(res1, res2) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/jubjub_test.py b/tests/jubjub_test.py new file mode 100644 index 0000000..c276f08 --- /dev/null +++ b/tests/jubjub_test.py @@ -0,0 +1,79 @@ +import unittest + +from os import urandom + +from znakes.fields import BLS12_381Field as FQ +from znakes.curves import JubJub + + +JUBJUB_C = JubJub.JUBJUB_C +JUBJUB_E = JubJub.JUBJUB_E + + +class TestJubjub(unittest.TestCase): + def _point_g(self): + return JubJub.generator() + + # Hardcoded for now till we have automatic test generation for ZoKrates test framework + def _fe_rnd(self): + return [FQ(1234), FQ(5678), FQ(7890)] + + def test_double(self): + G = self._point_g() + G_times_2 = G.mult(2) + G_dbl = G.add(G) + self.assertEqual(G_times_2, G_dbl) + + # test taken form: https://github.com/gtank/jubjub/blob/main/jubjub_test.go#L47 + def test_cyclic(self): + G = self._point_g() + scalar = 6554484396890773809930967563523245729705921265872317281365359162392183254199 + self.assertEqual(G.mult(JUBJUB_C).mult(scalar), JubJub.infinity()) + + # TODO: find values for JubJub + # def test_lower_order_p(self): + # lp = JubJub( + # FQ( + # 4342719913949491028786768530115087822524712248835451589697801404893164183326 + # ), + # FQ( + # 4826523245007015323400664741523384119579596407052839571721035538011798951543 + # ), + # ) + # lp_c = lp.mult(JUBJUB_C) + # self.assertEqual(lp_c, JubJub.infinity()) + # lp_l = lp.mult(JUBJUB_L) + # self.assertEqual(lp_l, lp) + + def test_multiplicative(self): + G = self._point_g() + a, b, _ = self._fe_rnd() + A = G.mult(a) + B = G.mult(b) + + ab = (a.n * b.n) % JUBJUB_E # 7006652 + AB = G.mult(FQ(ab)) + self.assertEqual(A.mult(b), AB) + self.assertEqual(B.mult(a), AB) + + def test_multiplicative_associativity(self): + G = self._point_g() + + a, b, c = self._fe_rnd() + + res1 = G.mult(a).mult(b).mult(c) + res2 = G.mult(b).mult(c).mult(a) + res3 = G.mult(c).mult(a).mult(b) + + self.assertEqual(res1, res2) + self.assertEqual(res2, res3) + self.assertEqual(res1, res3) + + def test_identities(self): + G = self._point_g() + self.assertEqual(G + JubJub.infinity(), G) + self.assertEqual(G + G.neg(), JubJub.infinity()) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/pedersenHasher_test.py b/tests/pedersenHasher_test.py index 3b7a1ab..18c16c5 100644 --- a/tests/pedersenHasher_test.py +++ b/tests/pedersenHasher_test.py @@ -3,8 +3,8 @@ from os import urandom from random import randint -from zokrates_pycrypto.babyjubjub import Point, FQ -from zokrates_pycrypto.gadgets.pedersenHasher import PedersenHasher as P +from znakes.curves import BabyJubJub, BN128Field as FQ +from znakes.gadgets.pedersenHasher import PedersenHasher as P class TestPedersenHash(unittest.TestCase): @@ -20,7 +20,7 @@ def test_zcash(self): def test_hash_scalars_known(self): self.assertEqual( P(b"test").hash_scalars(267), - Point( + BabyJubJub( FQ( 6790798216812059804926342266703617627640027902964190490794793207272357201212 ), @@ -34,7 +34,7 @@ def test_hash_scalars_known(self): P(b"test").hash_scalars( 6453482891510615431577168724743356132495662554103773572771861111634748265227 ), - Point( + BabyJubJub( FQ( 6545697115159207040330446958704617656199928059562637738348733874272425400594 ), @@ -48,7 +48,7 @@ def test_hash_scalars_known(self): P(b"test").hash_scalars( 21888242871839275222246405745257275088548364400416034343698204186575808495616 ), - Point( + BabyJubJub( FQ( 16322787121012335146141962340685388833598805940095898416175167744309692564601 ), @@ -61,7 +61,7 @@ def test_hash_scalars_known(self): def test_hash_bytes_known(self): self.assertEqual( P(b"test").hash_bytes(b"abc"), - Point( + BabyJubJub( FQ( 9869277320722751484529016080276887338184240285836102740267608137843906399765 ), @@ -73,7 +73,7 @@ def test_hash_bytes_known(self): self.assertEqual( P(b"test").hash_bytes(b"abcdefghijklmnopqrstuvwx"), - Point( + BabyJubJub( FQ( 3966548799068703226441887746390766667253943354008248106643296790753369303077 ), @@ -88,7 +88,7 @@ def test_hash_bits_known(self): P(b"EdDSA_Verify.RAM").hash_bits( "101100110011111001100100101100010100011010100100001011101001000100100000001111101101111001001010111011101101011010010101101101101000000010000000101010110100011110101110111100111100011110110011100101011000000000110101111001110000101011011110100100011110010000110111010011000001000100101100101111001100100010110101100010001000000101111011011010010011110001110111101011110001111111100010010000110101000001010111000111011110111010010010000101110000011001111000101010001101100000110111111110011001110101011000110010111111000101001100010001011011101010101011101010110000111100101000000110011000011001101000001010110110010000110101011111100010111011100110111101110111011001001110100100110010100111001000001010101010010100010100101101000010100010000111110101111000101110" ), - Point( + BabyJubJub( FQ( 16391910732431349989910402670442677728780476741314399751389577385062806845560 ), diff --git a/znakes/__about__.py b/znakes/__about__.py new file mode 100644 index 0000000..3dc1f76 --- /dev/null +++ b/znakes/__about__.py @@ -0,0 +1 @@ +__version__ = "0.1.0" diff --git a/zokrates_pycrypto/__init__.py b/znakes/__init__.py similarity index 100% rename from zokrates_pycrypto/__init__.py rename to znakes/__init__.py diff --git a/zokrates_pycrypto/babyjubjub.py b/znakes/curves.py similarity index 54% rename from zokrates_pycrypto/babyjubjub.py rename to znakes/curves.py index d3689be..b02f8bb 100644 --- a/zokrates_pycrypto/babyjubjub.py +++ b/znakes/curves.py @@ -7,26 +7,82 @@ based on: https://github.com/HarryR/ethsnarks """ -from collections import namedtuple -from .field import FQ, inv, field_modulus +from abc import ABC, abstractmethod +from .fields import FQ, BN128Field, BLS12_381Field from .numbertheory import square_root_mod_prime, SquareRootError -# order of the field -JUBJUB_Q = field_modulus -# order of the curve -JUBJUB_E = 21888242871839275222246405745257275088614511777268538073601725287587578984328 -JUBJUB_C = 8 # Cofactor -JUBJUB_L = JUBJUB_E // JUBJUB_C # C*L == E -JUBJUB_A = 168700 # Coefficient A -JUBJUB_D = 168696 # Coefficient D - def is_negative(v): - assert isinstance(v, FQ) + assert isinstance(v, FQ), f"given type: {type(v)}" return v.n < (-v).n -class Point(namedtuple("_Point", ("x", "y"))): +class EdwardsCurve(ABC): + FIELD_TYPE: type + JUBJUB_Q: int + JUBJUB_E: int + JUBJUB_C: FQ + JUBJUB_L: FQ + JUBJUB_A: FQ + JUBJUB_D: FQ + + def __init__(self, x: FQ, y: FQ): + assert type(x) == type(y) and type(x) == self.FIELD_TYPE + self.x = x + self.y = y + + @classmethod + @property + @abstractmethod + def FIELD_TYPE(cls): + raise NotImplementedError + + @classmethod + @property + @abstractmethod + def JUBJUB_Q(cls): + raise NotImplementedError + + @classmethod + @property + @abstractmethod + def JUBJUB_E(cls): + raise NotImplementedError + + @classmethod + @property + @abstractmethod + def JUBJUB_C(cls): + raise NotImplementedError + + @classmethod + @property + @abstractmethod + def JUBJUB_L(cls): + raise NotImplementedError + + @classmethod + @property + @abstractmethod + def JUBJUB_A(cls): + raise NotImplementedError + + @classmethod + @property + @abstractmethod + def JUBJUB_D(cls): + raise NotImplementedError + + @classmethod + @abstractmethod + def generator(cls): + raise NotImplementedError + + @staticmethod + @abstractmethod + def infinity(): + raise NotImplementedError + def valid(self): """ Satisfies the relationship @@ -34,23 +90,23 @@ def valid(self): """ xsq = self.x * self.x ysq = self.y * self.y - return (JUBJUB_A * xsq) + ysq == (1 + JUBJUB_D * xsq * ysq) + return (self.JUBJUB_A * xsq) + ysq == (self.FIELD_TYPE(1) + self.JUBJUB_D * xsq * ysq) def add(self, other): - assert isinstance(other, Point) + assert isinstance(other, type(self)) if self.x == 0 and self.y == 0: return other (u1, v1) = (self.x, self.y) (u2, v2) = (other.x, other.y) - u3 = (u1 * v2 + v1 * u2) / (FQ.one() + JUBJUB_D * u1 * u2 * v1 * v2) - v3 = (v1 * v2 - JUBJUB_A * u1 * u2) / (FQ.one() - JUBJUB_D * u1 * u2 * v1 * v2) - return Point(u3, v3) + u3 = (u1 * v2 + v1 * u2) / (self.FIELD_TYPE(1) + self.JUBJUB_D * u1 * u2 * v1 * v2) + v3 = (v1 * v2 - self.JUBJUB_A * u1 * u2) / (self.FIELD_TYPE(1) - self.JUBJUB_D * u1 * u2 * v1 * v2) + return type(self)(u3, v3) def mult(self, scalar): if isinstance(scalar, FQ): scalar = scalar.n p = self - a = self.infinity() + a = type(self).infinity() i = 0 while scalar != 0: if (scalar & 1) != 0: @@ -64,20 +120,10 @@ def neg(self): """ Twisted Edwards Curves, BBJLP-2008, section 2 pg 2 """ - return Point(-self.x, self.y) - - @classmethod - def generator(cls): - x = 16540640123574156134436876038791482806971768689494387082833631921987005038935 - y = 20819045374670962167435360035096875258406992893633759881276124905556507972311 - return Point(FQ(x), FQ(y)) - - @staticmethod - def infinity(): - return Point(FQ(0), FQ(1)) + return type(self)(-self.x, self.y) def __str__(self): - return "x: {}, y:{}".format(*self) + return "x: {}, y:{}".format(self.x, self.y) def __eq__(self, other): return self.x == other.x and self.y == other.y @@ -103,12 +149,12 @@ def from_x(cls, x): y^2 = ((a * x^2) / (d * x^2 - 1)) - (1 / (d * x^2 - 1)) For every x coordinate, there are two possible points: (x, y) and (x, -y) """ - assert isinstance(x, FQ) + assert isinstance(x, cls.FIELD_TYPE) xsq = x * x - ax2 = JUBJUB_A * xsq - dxsqm1 = inv(JUBJUB_D * xsq - 1, JUBJUB_Q) - ysq = dxsqm1 * (ax2 - 1) - y = FQ(square_root_mod_prime(int(ysq), JUBJUB_Q)) + ax2 = cls.JUBJUB_A * xsq + dxsqm1 = (cls.JUBJUB_D * xsq - cls.FIELD_TYPE(1)).inv() + ysq = dxsqm1 * (ax2 - cls.FIELD_TYPE(1)) + y = square_root_mod_prime(int(ysq), cls.JUBJUB_Q) return cls(x, y) @classmethod @@ -116,12 +162,12 @@ def from_y(cls, y, sign=None): """ x^2 = (y^2 - 1) / (d * y^2 - a) """ - assert isinstance(y, FQ) + assert isinstance(y, cls.FIELD_TYPE) ysq = y * y - lhs = ysq - 1 - rhs = JUBJUB_D * ysq - JUBJUB_A + lhs = ysq - cls.FIELD_TYPE(1) + rhs = cls.JUBJUB_D * ysq - cls.JUBJUB_A xsq = lhs / rhs - x = FQ(square_root_mod_prime(int(xsq), JUBJUB_Q)) + x = cls.FIELD_TYPE(square_root_mod_prime(int(xsq), cls.JUBJUB_Q)) if sign is not None: # Used for compress & decompress if (x.n & 1) != sign: @@ -134,7 +180,7 @@ def from_y(cls, y, sign=None): @classmethod def from_hash(cls, entropy): """ - HashToPoint (or Point.from_hash) + HashToEdwardsCurve (or EdwardsCurve.from_hash) Parameters: entropy (bytes): input entropy provided as byte array @@ -156,20 +202,20 @@ def from_hash(cls, entropy): assert isinstance(entropy, bytes) entropy = sha256(entropy).digest() entropy_as_int = int.from_bytes(entropy, "big") - y = FQ(entropy_as_int) + y = cls.FIELD_TYPE(entropy_as_int) while True: try: p = cls.from_y(y) except SquareRootError: - y += 1 + y += cls.FIELD_TYPE(1) continue # Multiply point by cofactor, ensures it's on the prime-order subgroup - p = p * JUBJUB_C + p = p * cls.JUBJUB_C # Verify point is on prime-ordered sub-group - if (p * JUBJUB_L) != Point.infinity(): - raise RuntimeError("Point not on prime-ordered subgroup") + if (p * cls.JUBJUB_L) != type(p).infinity(): + raise RuntimeError("EdwardsCurve not on prime-ordered subgroup") return p @@ -205,4 +251,56 @@ def decompress(cls, point): y = int.from_bytes(point, "big") sign = y >> 255 y &= (1 << 255) - 1 - return cls.from_y(FQ(y), sign) + return cls.from_y(cls.FIELD_TYPE(y), sign) + + +# values taken from: https://github.com/barryWhiteHat/baby_jubjub +class BabyJubJub(EdwardsCurve): + FIELD_TYPE = BN128Field + # order of the field + JUBJUB_Q = BN128Field.FIELD + # order of the curve + JUBJUB_E = 21888242871839275222246405745257275088614511777268538073601725287587578984328 + JUBJUB_C = BN128Field(8) # Cofactor + JUBJUB_L = BN128Field(JUBJUB_E) / JUBJUB_C # C*L == E + JUBJUB_A = BN128Field(168700) # Coefficient A + JUBJUB_D = BN128Field(168696) # Coefficient D + + def __init__(self, x: BN128Field, y: BN128Field): + super().__init__(x, y) + + @classmethod + def generator(cls): + x = 16540640123574156134436876038791482806971768689494387082833631921987005038935 + y = 20819045374670962167435360035096875258406992893633759881276124905556507972311 + return cls(BN128Field(x), BN128Field(y)) + + @staticmethod + def infinity(): + return BabyJubJub(BN128Field(0), BN128Field(1)) + + +# values taken from: https://github.com/daira/jubjub +class JubJub(EdwardsCurve): + FIELD_TYPE = BLS12_381Field + # order of the field + JUBJUB_Q = BLS12_381Field.FIELD + # order of the curve + JUBJUB_E = 52435875175126190479447740508185965837647370126978538250922873299137466033592 # C*L == E + JUBJUB_C = BLS12_381Field(8) # Cofactor + JUBJUB_L = BLS12_381Field(6554484396890773809930967563523245729705921265872317281365359162392183254199) + JUBJUB_A = BLS12_381Field(-1) # Coefficient A + JUBJUB_D = BLS12_381Field(19257038036680949359750312669786877991949435402254120286184196891950884077233) # Coefficient D + + def __init__(self, x: BLS12_381Field, y: BLS12_381Field): + super().__init__(x, y) + + @classmethod + def generator(cls): + x = 11076627216317271660298050606127911965867021807910416450833192264015104452986 + y = 44412834903739585386157632289020980010620626017712148233229312325549216099227 + return cls(BLS12_381Field(x), BLS12_381Field(y)) + + @staticmethod + def infinity(): + return JubJub(BLS12_381Field(0), BLS12_381Field(1)) diff --git a/znakes/eddsa.py b/znakes/eddsa.py new file mode 100644 index 0000000..001540d --- /dev/null +++ b/znakes/eddsa.py @@ -0,0 +1,127 @@ +""" +This module implements EdDSA (https://en.wikipedia.org/wiki/EdDSA) signing and verification + +1) the signer has two secret values: + + * k = Secret key + * r = Per-(message,key) nonce + +2) the signer provides the verifier with their public key: + + * A = k*B + +3) the signer provides a signature consisting of two values: + + * R = Point, image of `r*B` + * s = Image of `r + (k*t)` + +The value `t` denotes the common reference string used by both parties: + * t = H(R, A, M) +where H() denotes a cryptographic hash function, SHA256 in this implementation. + +The nonce `r` is a random secret, and protects the value `s` from revealing the +signers secret key. + +4) the verifier can check the following statement: + `S*B = R + t*A` + +For further information see: https://eprint.iacr.org/2015/677.pdf +based on: https://github.com/HarryR/ethsnarks +""" + +import hashlib +from collections import namedtuple +from math import ceil, log2 +from os import urandom +from abc import ABCMeta + +from .curves import EdwardsCurve, BabyJubJub, JubJub +from .fields import FQ, BN128Field, BLS12_381Field +from .utils import to_bytes + + +class PrivateKey: + """ + Wraps field element + """ + def __init__(self, sk: int, curve: ABCMeta): + if curve == BabyJubJub: + field = BN128Field + elif curve == JubJub: + field = BLS12_381Field + else: + raise ValueError('Edwardscurve not supported') + self.curve = curve + self.fe = field(sk) + + @classmethod + def from_rand(cls, curve: ABCMeta): + mod = curve.JUBJUB_L.n + # nbytes = ceil(ceil(log2(mod)) / 8) + 1 + nbytes = ceil(ceil(log2(mod)) / 8) + rand_n = int.from_bytes(urandom(nbytes), "little") + return cls(rand_n, curve) + + def sign(self, msg, B: EdwardsCurve = None): + "Returns the signature (R,S) for a given private key and message." + B = B or self.curve.generator() + + A = PublicKey.from_private(self) # A = kB + + M = msg + r = A.hash_to_scalar(self.fe, M) # r = H(k,M) mod L + R = B.mult(r) # R = rB + + # Bind the message to the nonce, public key and message + hRAM = A.hash_to_scalar(R, A.point, M) + key_field = self.fe.n + S = (r + (key_field * hRAM)) % self.curve.JUBJUB_E # r + (H(R,A,M) * k) + + return (R, S) + +class PublicKey: + """ + Wraps edwards point + """ + def __init__(self, point: EdwardsCurve): + assert issubclass(type(point), EdwardsCurve) + self.point = point + self.curve = type(point) + + @classmethod + def from_private(cls, sk: PrivateKey, B=None): + "Returns public key for a private key. B denotes the group generator" + assert isinstance(sk, PrivateKey) and issubclass(type(sk.fe), FQ) + curve = sk.curve + if B: + assert type(B) == type(curve) + B = B or curve.generator() + A = B.mult(sk.fe) + return cls(A) + + def verify(self, sig, msg, B=None): + B = B or self.curve.generator() + + R, S = sig + M = msg + A = self.point + + lhs = B.mult(S) + + hRAM = self.hash_to_scalar(R, A, M) + rhs = R + (A.mult(hRAM)) + + return lhs == rhs + + + def hash_to_scalar(self, *args): + """ + Hash the key and message to create `r`, the blinding factor for this signature. + If the same `r` value is used more than once, the key for the signature is revealed. + + Note that we take the entire 256bit hash digest as input for the scalar multiplication. + As the group is only of size JUBJUB_E (<256bit) we allow wrapping around the group modulo. + """ + p = b"".join(to_bytes(_) for _ in args) + digest = hashlib.sha256(p).digest() + return int(digest.hex(), 16) % self.curve.JUBJUB_E # mod JUBJUB_E here for optimized implementation diff --git a/znakes/fields.py b/znakes/fields.py new file mode 100644 index 0000000..fa42369 --- /dev/null +++ b/znakes/fields.py @@ -0,0 +1,98 @@ +""" +This code is copied from https://github.com/ethereum/py_ecc/blob/master/py_ecc/bn128/bn128_curve.py +Author is Vitalik Buterin. +Unfortunately the FIELD modulus is not generic in this implementation, hence we had to copy the file. +All changes from our side are denoted with #CHANGE. +""" + +from __future__ import absolute_import + +from abc import ABC, abstractmethod + + +# A class for FIELD elements in FQ. Wrap a number in this class, +# and it becomes a FIELD element. +class FQ(ABC): + FIELD = int + n: int + + @classmethod + @property + @abstractmethod + def FIELD(cls): + raise NotImplementedError + + def __init__(self, val: int) -> None: + assert isinstance(val, int) + self.n = val % self.FIELD + + def __assert_field(self, other: "FQ"): + assert isinstance(other, FQ) + assert self.FIELD == other.FIELD + + def __int__(self): + return self.n + + def __add__(self, other: "FQ") -> "FQ": + self.__assert_field(other) + return type(self)(self.n + other.n) + + def __mul__(self, other: "FQ") -> "FQ": + self.__assert_field(other) + return type(self)(self.n * other.n) + + def __sub__(self, other: "FQ") -> "FQ": + self.__assert_field(other) + return type(self)(self.n - other.n) + + def __truediv__(self, other: "FQ") -> "FQ": + self.__assert_field(other) + return self * other.inv() + + def __pow__(self, other: int) -> "FQ": + if other == 0: + return type(self)(1) + elif other == 1: + return self + elif other % 2 == 0: + return (self * self) ** (other // 2) + else: + return ((self * self) ** int(other // 2)) * self + + def __eq__( + self, other: "FQ" + ) -> bool: # type:ignore # https://github.com/python/mypy/issues/2783 # noqa: E501 + if isinstance(other, FQ): + return self.n == other.n and self.FIELD == other.FIELD + else: + return False + + def __ne__( + self, other: "FQ" + ) -> bool: # type:ignore # https://github.com/python/mypy/issues/2783 # noqa: E501 + return not self == other + + def __neg__(self) -> "FQ": + return type(self)(-self.n) + + def __repr__(self) -> str: + return repr(self.n) + + def inv(self) -> "FQ": + if self.n == 0: + return type(self)(0) + lm, hm = 1, 0 + low, high = self.n % self.FIELD, self.FIELD + while low > 1: + r = high // low + nm, new = hm - lm * r, high - low * r + lm, low, hm, high = nm, new, lm, low + return type(self)(lm) + + +class BN128Field(FQ): + FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617 + + +class BLS12_381Field(FQ): + FIELD = 52435875175126190479447740508185965837690552500527637822603658699938581184513 diff --git a/zokrates_pycrypto/gadgets/__init__.py b/znakes/gadgets/__init__.py similarity index 100% rename from zokrates_pycrypto/gadgets/__init__.py rename to znakes/gadgets/__init__.py diff --git a/zokrates_pycrypto/gadgets/pedersenHasher.py b/znakes/gadgets/pedersenHasher.py similarity index 96% rename from zokrates_pycrypto/gadgets/pedersenHasher.py rename to znakes/gadgets/pedersenHasher.py index 5768a48..cd34f05 100644 --- a/zokrates_pycrypto/gadgets/pedersenHasher.py +++ b/znakes/gadgets/pedersenHasher.py @@ -3,8 +3,8 @@ from math import floor, log2 from struct import pack -from ..babyjubjub import Point, JUBJUB_L, JUBJUB_C -from ..field import FQ +from ..curves import BabyJubJub +from ..fields import BN128Field as FQ WINDOW_SIZE_BITS = 2 # Size of the pre-computed look-up table @@ -14,7 +14,7 @@ def pedersen_hash_basepoint(name, i): Create a base point for use with the windowed Pedersen hash function. The name and sequence numbers are used as a unique identifier. - Then HashToPoint is run on the name+seq to get the base point. + Then HashToEdwardsCurve is run on the name+seq to get the base point. """ if not isinstance(name, bytes): if isinstance(name, str): @@ -26,7 +26,7 @@ def pedersen_hash_basepoint(name, i): if len(name) > 28: raise ValueError("Name too long") data = b"%-28s%04X" % (name, i) - return Point.from_hash(data) + return BabyJubJub.from_hash(data) def windows_to_dsl_array(windows): @@ -96,7 +96,7 @@ def __hash_windows(self, windows, witness): if witness: return windows_to_dsl_array(windows) - result = Point.infinity() + result = BabyJubJub.infinity() for (g, window) in zip(self.generators, windows): segment = g * ((window & 0b11) + 1) if window > 0b11: diff --git a/zokrates_pycrypto/numbertheory.py b/znakes/numbertheory.py similarity index 100% rename from zokrates_pycrypto/numbertheory.py rename to znakes/numbertheory.py diff --git a/zokrates_pycrypto/utils.py b/znakes/utils.py similarity index 92% rename from zokrates_pycrypto/utils.py rename to znakes/utils.py index 94ce92a..9fdf376 100644 --- a/zokrates_pycrypto/utils.py +++ b/znakes/utils.py @@ -1,7 +1,7 @@ from bitstring import BitArray -from .babyjubjub import Point -from .field import FQ +from .curves import EdwardsCurve +from .fields import FQ import hashlib @@ -9,7 +9,7 @@ def to_bytes(*args): "Returns byte representation for objects used in this module." result = b"" for M in args: - if isinstance(M, Point): + if isinstance(M, EdwardsCurve): result += to_bytes(M.x) # result += to_bytes(M.y) elif isinstance(M, FQ): @@ -30,7 +30,7 @@ def to_bytes(*args): def write_signature_for_zokrates_cli(pk, sig, msg, path): "Writes the input arguments for verifyEddsa in the ZoKrates stdlib to file." sig_R, sig_S = sig - args = [sig_R.x, sig_R.y, sig_S, pk.p.x.n, pk.p.y.n] + args = [sig_R.x, sig_R.y, sig_S, pk.point.x.n, pk.point.y.n] args = " ".join(map(str, args)) M0 = msg.hex()[:64] @@ -53,7 +53,7 @@ def pprint_hex_as_256bit(n, h): def pprint_point(n, p): "Takes a variable name and curve point and returns Zokrates assignment statement." - x, y = p + x, y = p.x, p.y return "field[2] {} = [{}, {}] \n".format(n, x, y) diff --git a/zokrates_inputs.txt b/zokrates_inputs.txt new file mode 100644 index 0000000..baefe9f --- /dev/null +++ b/zokrates_inputs.txt @@ -0,0 +1 @@ +34447792987282659365761597972717728360572980490871459312286337313069481686981 5039489385779586464014678750834763525469837680048282742624492890153053968061 45914481514916287065509252353231787689584860054352686331997448774730479811501 42292624373983263331103349716850210388552074626710054328139942260264232291443 23051959298980289391849938317855738719500599241239385491207151286970159962540 3814687126 4207057211 2301474087 1696421512 1054042432 4114589074 2402006685 2358319779 2636307903 771130895 3338794104 910337493 3941248527 2566242658 3403499691 2178970740 \ No newline at end of file diff --git a/zokrates_pycrypto/eddsa.py b/zokrates_pycrypto/eddsa.py deleted file mode 100644 index 316ea34..0000000 --- a/zokrates_pycrypto/eddsa.py +++ /dev/null @@ -1,113 +0,0 @@ -""" -This module implements EdDSA (https://en.wikipedia.org/wiki/EdDSA) signing and verification - -1) the signer has two secret values: - - * k = Secret key - * r = Per-(message,key) nonce - -2) the signer provides the verifier with their public key: - - * A = k*B - -3) the signer provides a signature consisting of two values: - - * R = Point, image of `r*B` - * s = Image of `r + (k*t)` - -The value `t` denotes the common reference string used by both parties: - * t = H(R, A, M) -where H() denotes a cryptographic hash function, SHA256 in this implementation. - -The nonce `r` is a random secret, and protects the value `s` from revealing the -signers secret key. - -4) the verifier can check the following statement: - `S*B = R + t*A` - -For further information see: https://eprint.iacr.org/2015/677.pdf -based on: https://github.com/HarryR/ethsnarks -""" - -import hashlib -from collections import namedtuple -from math import ceil, log2 -from os import urandom - -from .babyjubjub import JUBJUB_E, JUBJUB_L, JUBJUB_Q, Point -from .field import FQ -from .utils import to_bytes - - -class PrivateKey(namedtuple("_PrivateKey", ("fe"))): - """ - Wraps field element - """ - - @classmethod - # FIXME: ethsnarks creates keys > 32bytes. Create issue. - def from_rand(cls): - mod = JUBJUB_L - # nbytes = ceil(ceil(log2(mod)) / 8) + 1 - nbytes = ceil(ceil(log2(mod)) / 8) - rand_n = int.from_bytes(urandom(nbytes), "little") - return cls(FQ(rand_n)) - - def sign(self, msg, B=None): - "Returns the signature (R,S) for a given private key and message." - B = B or Point.generator() - - A = PublicKey.from_private(self) # A = kB - - M = msg - r = hash_to_scalar(self.fe, M) # r = H(k,M) mod L - R = B.mult(r) # R = rB - - # Bind the message to the nonce, public key and message - hRAM = hash_to_scalar(R, A, M) - key_field = self.fe.n - S = (r + (key_field * hRAM)) % JUBJUB_E # r + (H(R,A,M) * k) - - return (R, S) - - -class PublicKey(namedtuple("_PublicKey", ("p"))): - """ - Wraps edwards point - """ - - @classmethod - def from_private(cls, sk, B=None): - "Returns public key for a private key. B denotes the group generator" - B = B or Point.generator() - if not isinstance(sk, PrivateKey): - sk = PrivateKey(sk) - A = B.mult(sk.fe) - return cls(A) - - def verify(self, sig, msg, B=None): - B = B or Point.generator() - - R, S = sig - M = msg - A = self.p - - lhs = B.mult(S) - - hRAM = hash_to_scalar(R, A, M) - rhs = R + (A.mult(hRAM)) - - return lhs == rhs - - -def hash_to_scalar(*args): - """ - Hash the key and message to create `r`, the blinding factor for this signature. - If the same `r` value is used more than once, the key for the signature is revealed. - - Note that we take the entire 256bit hash digest as input for the scalar multiplication. - As the group is only of size JUBJUB_E (<256bit) we allow wrapping around the group modulo. - """ - p = b"".join(to_bytes(_) for _ in args) - digest = hashlib.sha256(p).digest() - return int(digest.hex(), 16) # mod JUBJUB_E here for optimized implementation diff --git a/zokrates_pycrypto/field.py b/zokrates_pycrypto/field.py deleted file mode 100644 index 64be17d..0000000 --- a/zokrates_pycrypto/field.py +++ /dev/null @@ -1,134 +0,0 @@ -""" -This code is copied from https://github.com/ethereum/py_ecc/blob/master/py_ecc/bn128/bn128_curve.py -Author is Vitalik Buterin. -Unfortunately the field modulus is not generic in this implementation, hence we had to copy the file. -All changes from our side are denoted with #CHANGE. -""" - -from __future__ import absolute_import - -from typing import cast, List, Tuple, Sequence, Union - - -# The prime modulus of the field -# field_modulus = 21888242871839275222246405745257275088696311157297823662689037894645226208583 -field_modulus = ( - 21888242871839275222246405745257275088548364400416034343698204186575808495617 -) -# CHANGE: Changing the modulus to the embedded curve - -# See, it's prime! -assert pow(2, field_modulus, field_modulus) == 2 - -# The modulus of the polynomial in this representation of FQ12 -# FQ12_MODULUS_COEFFS = (82, 0, 0, 0, 0, 0, -18, 0, 0, 0, 0, 0) # Implied + [1] -# FQ2_MODULUS_COEFFS = (1, 0) -# CHANGE: No need for extended in this case - -# Extended euclidean algorithm to find modular inverses for -# integers -def inv(a: int, n: int) -> int: - if a == 0: - return 0 - lm, hm = 1, 0 - num = a if isinstance(a, int) else a.n - low, high = num % n, n - while low > 1: - r = high // low - nm, new = hm - lm * r, high - low * r - lm, low, hm, high = nm, new, lm, low - return lm % n - - -IntOrFQ = Union[int, "FQ"] - - -# A class for field elements in FQ. Wrap a number in this class, -# and it becomes a field element. -class FQ(object): - n = None # type: int - - def __init__(self, val: IntOrFQ) -> None: - if isinstance(val, FQ): - self.n = val.n - else: - self.n = val % field_modulus - assert isinstance(self.n, int) - - def __add__(self, other: IntOrFQ) -> "FQ": - on = other.n if isinstance(other, FQ) else other - return FQ((self.n + on) % field_modulus) - - def __mul__(self, other: IntOrFQ) -> "FQ": - on = other.n if isinstance(other, FQ) else other - return FQ((self.n * on) % field_modulus) - - def __rmul__(self, other: IntOrFQ) -> "FQ": - return self * other - - def __radd__(self, other: IntOrFQ) -> "FQ": - return self + other - - def __rsub__(self, other: IntOrFQ) -> "FQ": - on = other.n if isinstance(other, FQ) else other - return FQ((on - self.n) % field_modulus) - - def __sub__(self, other: IntOrFQ) -> "FQ": - on = other.n if isinstance(other, FQ) else other - return FQ((self.n - on) % field_modulus) - - def __div__(self, other: IntOrFQ) -> "FQ": - on = other.n if isinstance(other, FQ) else other - assert isinstance(on, int) - return FQ(self.n * inv(on, field_modulus) % field_modulus) - - def __truediv__(self, other: IntOrFQ) -> "FQ": - return self.__div__(other) - - def __rdiv__(self, other: IntOrFQ) -> "FQ": - on = other.n if isinstance(other, FQ) else other - assert isinstance(on, int), on - return FQ(inv(self.n, field_modulus) * on % field_modulus) - - def __rtruediv__(self, other: IntOrFQ) -> "FQ": - return self.__rdiv__(other) - - def __pow__(self, other: int) -> "FQ": - if other == 0: - return FQ(1) - elif other == 1: - return FQ(self.n) - elif other % 2 == 0: - return (self * self) ** (other // 2) - else: - return ((self * self) ** int(other // 2)) * self - - def __eq__( - self, other: IntOrFQ - ) -> bool: # type:ignore # https://github.com/python/mypy/issues/2783 # noqa: E501 - if isinstance(other, FQ): - return self.n == other.n - else: - return self.n == other - - def __ne__( - self, other: IntOrFQ - ) -> bool: # type:ignore # https://github.com/python/mypy/issues/2783 # noqa: E501 - return not self == other - - def __neg__(self) -> "FQ": - return FQ(-self.n) - - def __repr__(self) -> str: - return repr(self.n) - - def __int__(self) -> int: - return self.n - - @classmethod - def one(cls) -> "FQ": - return cls(1) - - @classmethod - def zero(cls) -> "FQ": - return cls(0)