Showing preview only (378K chars total). Download the full file or copy to clipboard to get everything.
Repository: ofek/coincurve
Branch: master
Commit: 7829b29c08eb
Files: 65
Total size: 358.0 KB
Directory structure:
gitextract_tbfrxx9v/
├── .codecov.yml
├── .conda/
│ ├── environment-dev.yml
│ └── environment.yml
├── .gemini/
│ └── config.yaml
├── .gitattributes
├── .github/
│ ├── FUNDING.yml
│ ├── dependabot.yml
│ ├── scripts/
│ │ └── install-macos-build-deps.sh
│ └── workflows/
│ ├── build.yml
│ ├── docs.yml
│ ├── verify_conda_build.yml
│ └── verify_shared_build.yml
├── .gitignore
├── .linkcheckerrc
├── CMakeLists.txt
├── LICENSE-APACHE
├── LICENSE-MIT
├── NOTICE
├── README.md
├── cm_library_c_binding/
│ ├── CMakeLists.txt
│ └── build.py
├── cm_library_cffi_headers/
│ ├── CMakeLists.txt
│ └── compose_cffi_headers.py
├── cm_python_module/
│ └── CMakeLists.txt
├── cm_vendored_library/
│ └── CMakeLists.txt
├── cmake/
│ ├── SetCrossCompilerGithubActions.cmake
│ ├── SetDefaultVendoredLibrary.cmake
│ ├── SetSystemLibIfExists.cmake
│ ├── UnsetVendoredLibraryOptions.cmake
│ ├── UpdateVendoredLibraryOptions.cmake
│ └── VerifyPythonModule.cmake
├── docs/
│ ├── .snippets/
│ │ ├── abbrs.txt
│ │ └── links.txt
│ ├── api.md
│ ├── assets/
│ │ └── css/
│ │ └── custom.css
│ ├── benchmarks.md
│ ├── history.md
│ ├── index.md
│ ├── install.md
│ └── users.md
├── hatch.toml
├── hatch_build.py
├── mkdocs.yml
├── pyproject.toml
├── ruff.toml
├── ruff_defaults.toml
├── scripts/
│ ├── README.md
│ └── bench.py
├── src/
│ └── coincurve/
│ ├── __init__.py
│ ├── context.py
│ ├── der.py
│ ├── ecdsa.py
│ ├── flags.py
│ ├── keys.py
│ ├── py.typed
│ ├── types.py
│ └── utils.py
└── tests/
├── __init__.py
├── conftest.py
├── data/
│ ├── ecdsa_sig.json
│ └── pubkey.json
├── test_ecdsa.py
├── test_flags.py
├── test_keys.py
└── test_utils.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .codecov.yml
================================================
comment:
layout: "diff, files"
behavior: default
coverage:
range: 50..100
round: down
precision: 2
status:
project:
default:
target: '70'
patch:
default:
target: '70'
================================================
FILE: .conda/environment-dev.yml
================================================
name: coincurve-with-conda-dev
channels:
- conda-forge
- defaults
dependencies:
- cffi >=1.3.0
- cmake
- libsecp256k1
- pkg-config
- pytest
- pytest-benchmark
- python =3.12
- tox-conda
================================================
FILE: .conda/environment.yml
================================================
name: coincurve-with-conda
channels:
- conda-forge
- defaults
dependencies:
- libsecp256k1
- cffi >=1.3.0
- asn1crypto
================================================
FILE: .gemini/config.yaml
================================================
# https://developers.google.com/gemini-code-assist/docs/customize-gemini-behavior-github
have_fun: false
code_review:
pull_request_opened:
summary: false
code_review: false
================================================
FILE: .gitattributes
================================================
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
================================================
FILE: .github/FUNDING.yml
================================================
github:
- ofek
custom:
- https://ofek.dev/donate/
- https://paypal.me/ofeklev
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
================================================
FILE: .github/scripts/install-macos-build-deps.sh
================================================
#!/bin/bash
set -ex
# update brew
brew update
# Update openssl if necessary
brew outdated openssl || brew upgrade openssl
# Install packages needed to build lib-secp256k1
for pkg in pkg-config; do
brew list $pkg > /dev/null || brew install $pkg
brew outdated --quiet $pkg || brew upgrade $pkg
done
================================================
FILE: .github/workflows/build.yml
================================================
name: build
on:
push:
tags:
- v*
branches:
- master
pull_request:
branches:
- master
concurrency:
group: build-${{ github.head_ref }}
cancel-in-progress: true
env:
PYTHON_VERSION: '3.13'
COINCURVE_IGNORE_SYSTEM_LIB: 'ON'
COINCURVE_SECP256K1_STATIC: 'ON'
COINCURVE_CROSS_HOST: ''
CIBW_ENVIRONMENT_PASS_LINUX: >
COINCURVE_IGNORE_SYSTEM_LIB
COINCURVE_SECP256K1_STATIC
COINCURVE_CROSS_HOST
CIBW_BEFORE_ALL_MACOS: ./.github/scripts/install-macos-build-deps.sh
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: >
python -c
"from coincurve import PrivateKey;
a=PrivateKey();
b=PrivateKey();
assert a.ecdh(b.public_key.format())==b.ecdh(a.public_key.format())
" &&
python -m pytest {project}
CIBW_SKIP: >
pp*
jobs:
test:
name: Test Python ${{ matrix.python-version }} on ${{ startsWith(matrix.os, 'macos-') && 'macOS' || startsWith(matrix.os, 'windows-') && 'Windows' || 'Linux' }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install system dependencies
if: runner.os == 'macOS'
run: ./.github/scripts/install-macos-build-deps.sh
- name: Install Hatch
uses: pypa/hatch@install
- name: Run static analysis
run: hatch fmt --check
- name: Check types
run: hatch run types:check
- name: Run tests
run: hatch test --python ${{ matrix.python-version }} --cover-quiet --randomize
- name: Create coverage report
run: hatch run hatch-test.py${{ matrix.python-version }}:coverage xml
- name: Upload coverage data
uses: actions/upload-artifact@v4
with:
name: coverage-${{ matrix.os }}-${{ matrix.python-version }}
path: coverage.xml
- name: Install uv
uses: astral-sh/setup-uv@v5
- name: Benchmark
run: uv run --python-preference system scripts/bench.py
coverage:
name: Upload coverage
needs:
- test
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- name: Download coverage data
uses: actions/download-artifact@v5
with:
pattern: coverage-*
path: coverage_data
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
directory: coverage_data
use_oidc: true
linux-wheels-x86_64:
name: Build Linux wheels for x86-64
needs:
- test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Build wheels
uses: pypa/cibuildwheel@v2.23
- uses: actions/upload-artifact@v4
with:
name: artifact-linux-wheels-x86_64
path: wheelhouse/*.whl
if-no-files-found: error
macos-wheels-x86_64:
name: Build macOS wheels for x86-64
needs:
- test
runs-on: macos-15-intel
steps:
- uses: actions/checkout@v5
- name: Build wheels
uses: pypa/cibuildwheel@v3.2.0
env:
CIBW_ARCHS_MACOS: x86_64
CIBW_SKIP: "cp314t-*"
MACOSX_DEPLOYMENT_TARGET: 10.13
- uses: actions/upload-artifact@v4
with:
name: artifact-macos-wheels-x86_64
path: wheelhouse/*.whl
if-no-files-found: error
macos-wheels-arm64:
name: Build macOS wheels for ARM64
needs:
- test
runs-on: macos-latest
steps:
- uses: actions/checkout@v5
- name: Build wheels
uses: pypa/cibuildwheel@v2.23
- uses: actions/upload-artifact@v4
with:
name: artifact-macos-wheels-arm64
path: wheelhouse/*.whl
if-no-files-found: error
windows-wheels-x86_64:
name: Build Windows wheels for x86-64
needs:
- test
runs-on: windows-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Build wheels
uses: pypa/cibuildwheel@v2.23
env:
CIBW_ARCHS_WINDOWS: 'AMD64'
CIBW_BEFORE_ALL: choco install -y --no-progress --no-color cmake>=3.28
- uses: actions/upload-artifact@v4
with:
name: artifact-windows-wheels-x86_64
path: wheelhouse/*.whl
if-no-files-found: error
windows-wheels-arm64:
name: Build Windows wheels for ARM64
needs:
- test
runs-on: windows-latest
steps:
- uses: actions/checkout@v5
- name: Build wheels
uses: pypa/cibuildwheel@v2.23
env:
COINCURVE_CROSS_HOST: 'arm64'
CIBW_ARCHS_WINDOWS: 'ARM64'
CIBW_BEFORE_ALL: choco install -y --no-progress --no-color cmake>=3.28
- uses: actions/upload-artifact@v4
with:
name: artifact-windows-wheels-arm64
path: wheelhouse/*.whl
if-no-files-found: error
sdist:
name: Build source distribution
needs:
- test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Install build dependencies
run: python -m pip install build
- name: Build source distribution
run: python -m build --sdist
- uses: actions/upload-artifact@v4
with:
name: artifact-sdist
path: dist/*
if-no-files-found: error
linux-wheels-arm64:
name: Build Linux wheels for ARM64
needs:
- test
runs-on: ubuntu-latest
if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.event.ref, 'refs/tags'))
steps:
- uses: actions/checkout@v5
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: arm64
- name: Build wheels
uses: pypa/cibuildwheel@v2.23
env:
CIBW_ARCHS_LINUX: aarch64
- uses: actions/upload-artifact@v4
with:
name: artifact-linux-wheels-arm64
path: wheelhouse/*.whl
if-no-files-found: error
publish:
name: Publish release
needs:
- linux-wheels-x86_64
- macos-wheels-x86_64
- macos-wheels-arm64
- windows-wheels-x86_64
- windows-wheels-arm64
- sdist
- linux-wheels-arm64
runs-on: ubuntu-latest
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v5
with:
pattern: artifact-*
merge-multiple: true
path: dist
- run: ls -l dist
- name: Push build artifacts to PyPI
uses: pypa/gh-action-pypi-publish@v1.13.0
with:
skip-existing: true
================================================
FILE: .github/workflows/docs.yml
================================================
name: docs
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
# Fetch all history for applying timestamps to every page
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Install Hatch
uses: pypa/hatch@install
- name: Check documentation
run: hatch run docs:build-check
- name: Build documentation
run: hatch run docs:build
- uses: actions/upload-artifact@v4
with:
name: documentation
path: site
publish:
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
needs:
- build
steps:
- uses: actions/download-artifact@v5
with:
name: documentation
path: site
- uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./site
commit_message: ${{ github.event.head_commit.message }}
# Write .nojekyll at the root, see:
# https://help.github.com/en/github/working-with-github-pages/about-github-pages#static-site-generators
enable_jekyll: false
# Only deploy if there were changes
allow_empty_commit: false
================================================
FILE: .github/workflows/verify_conda_build.yml
================================================
name: conda_build
on:
push:
tags:
- v*
branches:
- master
pull_request:
branches:
- master
concurrency:
group: build_conda-${{ github.head_ref }}
cancel-in-progress: true
env:
PYTHON_VERSION: '3.12'
COINCURVE_UPSTREAM_REF: __no_upstream__
COINCURVE_IGNORE_SYSTEM_LIB: 'OFF'
COINCURVE_SECP256K1_STATIC: 'OFF'
COINCURVE_CROSS_HOST: ''
CIBW_ENVIRONMENT_PASS_LINUX: >
COINCURVE_UPSTREAM_REF
COINCURVE_IGNORE_SYSTEM_LIB
COINCURVE_SECP256K1_STATIC
COINCURVE_CROSS_HOST
CIBW_BEFORE_ALL_MACOS: ./.github/scripts/install-macos-build-deps.sh
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: >
python -c
"from coincurve import PrivateKey;
a=PrivateKey();
b=PrivateKey();
assert a.ecdh(b.public_key.format())==b.ecdh(a.public_key.format())
" &&
python -m pytest {project}
CIBW_SKIP: >
pp*
jobs:
test:
name: Test with Conda libsecp256k1
runs-on: ubuntu-latest
defaults:
run:
shell: bash -el {0}
steps:
- uses: actions/checkout@v5
- name: Install Miniconda
uses: conda-incubator/setup-miniconda@v3
with:
environment-file: ./.conda/environment-dev.yml
activate-environment: coincurve-with-conda
python-version: ${{ env.PYTHON_VERSION }}
auto-activate-base: false
- name: Install Hatch
uses: pypa/hatch@install
- name: Run static analysis
run: hatch fmt --check
- name: Check types
run: hatch run types:check
- name: Run tests
run: LD_LIBRARY_PATH=$CONDA_PREFIX/lib hatch test
- name: Install uv
uses: astral-sh/setup-uv@v5
- name: Benchmark
run: LD_LIBRARY_PATH=$CONDA_PREFIX/lib uv run --python-preference system scripts/bench.py
linux-wheels-x86_64:
name: Build Linux wheels
needs:
- test
runs-on: ubuntu-latest
defaults:
run:
shell: bash -el {0}
steps:
- uses: actions/checkout@v5
- name: Install Miniconda
uses: conda-incubator/setup-miniconda@v3
with:
environment-file: ./.conda/environment-dev.yml
activate-environment: coincurve-with-conda
python-version: ${{ env.PYTHON_VERSION }}
auto-activate-base: false
- name: Build sdist & wheel
run: |
conda install python-build
python -m build --outdir conda_dist
- name: Test wheel in a clean environment
run: |
conda install pip
pip install conda_dist/*.whl
python -m pytest tests
================================================
FILE: .github/workflows/verify_shared_build.yml
================================================
name: shared_build
on:
push:
tags:
- v*
branches:
- master
pull_request:
branches:
- master
concurrency:
group: build_shared-${{ github.head_ref }}
cancel-in-progress: true
env:
PYTHON_VERSION: '3.12'
COINCURVE_IGNORE_SYSTEM_LIB: '1'
# Only 'SHARED' is recognized, any other string means 'not SHARED'
COINCURVE_SECP256K1_BUILD: 'SHARED'
CIBW_ENVIRONMENT_PASS_LINUX: >
COINCURVE_IGNORE_SYSTEM_LIB
COINCURVE_SECP256K1_BUILD
CIBW_BEFORE_ALL_MACOS: ./.github/scripts/install-macos-build-deps.sh
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: >
python -c
"from coincurve import PrivateKey;
a=PrivateKey();
b=PrivateKey();
assert a.ecdh(b.public_key.format())==b.ecdh(a.public_key.format())
" &&
python -m pytest {project}
CIBW_TEST_SKIP: "*-macosx_arm64"
CIBW_SKIP: >
pp*
jobs:
test:
name: Test latest Python
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install system dependencies
if: runner.os == 'macOS'
run: ./.github/scripts/install-macos-build-deps.sh
- name: Install Hatch
uses: pypa/hatch@install
- name: Run static analysis
run: hatch fmt --check
- name: Check types
run: hatch run types:check
- name: Run tests
run: hatch test
- name: Install uv
uses: astral-sh/setup-uv@v5
- name: Benchmark
run: uv run --python-preference system scripts/bench.py
linux-wheels-x86_64:
name: Build Linux wheels for x86-64
needs:
- test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Build wheels
uses: pypa/cibuildwheel@v2.23
macos-wheels-x86_64:
name: Build macOS wheels for x86-64
needs:
- test
runs-on: macos-15-intel
steps:
- uses: actions/checkout@v5
- name: Build wheels
uses: pypa/cibuildwheel@v3.2.0
env:
CIBW_ARCHS_MACOS: x86_64
CIBW_SKIP: "cp314t-*"
MACOSX_DEPLOYMENT_TARGET: 10.13
macos-wheels-arm64:
name: Build macOS wheels for ARM64
needs:
- test
runs-on: macos-latest
steps:
- uses: actions/checkout@v5
- name: Build wheels
uses: pypa/cibuildwheel@v2.23
windows-wheels-x86_64:
name: Build Windows wheels for x86-64
needs:
- test
runs-on: windows-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Build wheels
uses: pypa/cibuildwheel@v2.23
env:
CIBW_ARCHS_WINDOWS: 'AMD64'
CIBW_BEFORE_ALL: choco install -y --no-progress --no-color cmake>=3.28
windows-wheels-arm64:
name: Build Windows wheels for ARM64
needs:
- test
runs-on: windows-latest
steps:
- uses: actions/checkout@v5
- name: Build wheels
uses: pypa/cibuildwheel@v2.23
env:
COINCURVE_CROSS_HOST: 'arm64'
CIBW_ARCHS_WINDOWS: 'ARM64'
CIBW_BEFORE_ALL: choco install -y --no-progress --no-color cmake>=3.28
================================================
FILE: .gitignore
================================================
*.log
*.pyc
/.cache
/.coverage
/.eggs
/.idea
/.mypy_cache
/.tox
/coincurve.egg-info
/build
/dist
/site
================================================
FILE: .linkcheckerrc
================================================
[AnchorCheck]
================================================
FILE: CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.26)
project(${SKBUILD_PROJECT_NAME}
VERSION ${SKBUILD_PROJECT_VERSION}
LANGUAGES C
)
# Path to custom CMake functions
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
# Name of the vendored library CMake target
include(SetDefaultVendoredLibrary)
SetDefaultVendoredLibrary()
set(CFFI_INPUT_LIBRARY ${VENDORED_LIBRARY_CMAKE_TARGET})
# Set the output directories for the generated C code and headers
set(CFFI_C_CODE_DIR ${PROJECT_BINARY_DIR}/_gen_c_file)
set(CFFI_C_CODE "${CFFI_INPUT_LIBRARY}_cffi_bindings.c")
set(CFFI_HEADERS_DIR ${PROJECT_BINARY_DIR}/_gen_cffi_headers)
# Shared object that wraps the CFFI binding of the vendored library
set(CFFI_OUTPUT_LIBRARY "_${VENDORED_LIBRARY_PKG_CONFIG}")
# Setting python for the host system (before change in CMAKE_SYSTEM_PROCESSOR)
find_package(Python 3 REQUIRED COMPONENTS Interpreter Development.Module Development.SABIModule)
include(SetCrossCompilerGithubActions)
SetCrossCompilerGithubActions()
# Add the subdirectories. Append CONDA to the PKG_CONFIG_PATH
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
set(PKG_CONFIG_ARGN "--dont-define-prefix")
endif()
find_package(PkgConfig REQUIRED)
# Set VENDORED_AS_SYSTEM_LIB to true if the vendored library is installed as a system library
include(SetSystemLibIfExists)
SetSystemLibIfExists()
add_subdirectory(cm_vendored_library)
add_subdirectory(cm_library_cffi_headers)
add_subdirectory(cm_library_c_binding)
add_subdirectory(cm_python_module)
# Configure installation of the shared library ${CFFI_OUTPUT_LIBRARY} in the package
install(TARGETS ${CFFI_OUTPUT_LIBRARY} LIBRARY DESTINATION ${SKBUILD_PLATLIB_DIR}/${SKBUILD_PROJECT_NAME})
================================================
FILE: LICENSE-APACHE
================================================
Copyright 2017 Ofek Lev
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
================================================
FILE: LICENSE-MIT
================================================
MIT License
Copyright (c) 2017 Ofek Lev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: NOTICE
================================================
This package is dual-licensed under MIT or Apache-2.0.
The final distribution includes the following compiled artifacts:
* `_cffi_backend` shared library from the CFFI project (https://github.com/python-cffi/cffi) which is licensed under the MIT license. See `coincurve-X.Y.Z.dist-info/licenses/LICENSE-cffi` for the license text.
* Code from the libsecp256k1 project (https://github.com/bitcoin-core/secp256k1) which is licensed under the MIT license.
================================================
FILE: README.md
================================================
# coincurve
| | |
| --- | --- |
| CI/CD | [](https://github.com/ofek/coincurve/actions/workflows/build.yml) [](https://codecov.io/github/ofek/coincurve) |
| Docs | [](https://github.com/ofek/coincurve/actions/workflows/docs.yml) |
| Package | [](https://pypi.org/project/coincurve/) [](https://pypi.org/project/coincurve/) [](https://pypi.org/project/coincurve/) |
| Meta | [](https://github.com/ofek/dep-sync) [](https://github.com/astral-sh/ruff) [](https://github.com/python/mypy) [](https://spdx.org/licenses/) [](https://github.com/sponsors/ofek) |
-----
This library provides well-tested Python bindings for [libsecp256k1](https://github.com/bitcoin-core/secp256k1), the heavily optimized C library
used by [Bitcoin Core](https://github.com/bitcoin/bitcoin) for operations on the elliptic curve [secp256k1](https://en.bitcoin.it/wiki/Secp256k1).
Feel free to read the [documentation](https://ofek.dev/coincurve/)!
## Users
- [Ethereum](https://ethereum.org)
- [LBRY](https://lbry.com)
- [libp2p](https://libp2p.io)
and [many more](https://ofek.dev/coincurve/users/)!
## License
`coincurve` is distributed under the terms of any of the following licenses:
- [MIT](https://spdx.org/licenses/MIT.html)
- [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)
================================================
FILE: cm_library_c_binding/CMakeLists.txt
================================================
# create folder for _gen code
file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/_gen_c_file)
if (NOT CFFI_C_CODE)
set(CFFI_C_CODE _cffi_c_code.c)
endif()
if (NOT CFFI_C_CODE_DIR)
set(CFFI_C_CODE_DIR "${PROJECT_BINARY_DIR}/_gen_c_file")
endif()
# Copy the build.py file to the build directory
file(COPY ${CMAKE_CURRENT_LIST_DIR}/build.py DESTINATION ${CFFI_C_CODE_DIR})
include(VerifyPythonModule)
VerifyPythonModule(cffi ${Python_EXECUTABLE})
if (VENDORED_HEADERS_DIR)
set(_static_build 'ON')
else()
message(STATUS "CFFI C-file is built for a SHARED system library")
set(_static_build 'OFF')
endif()
# Generate the CFFI source file
add_custom_command(
OUTPUT ${CFFI_C_CODE_DIR}/${CFFI_C_CODE}
COMMAND ${Python_EXECUTABLE} ${CFFI_C_CODE_DIR}/build.py
${CFFI_HEADERS_DIR}
${CFFI_C_CODE_DIR}/${CFFI_C_CODE} ${_static_build}
MAIN_DEPENDENCY ${CMAKE_CURRENT_LIST_DIR}/build.py
DEPENDS headers-for-cffi
WORKING_DIRECTORY ${CFFI_C_CODE_DIR}
COMMENT "Generating CFFI source file"
)
add_custom_target(cffi-c-binding ALL DEPENDS ${CFFI_C_CODE_DIR}/${CFFI_C_CODE})
add_dependencies(cffi-c-binding headers-for-cffi)
================================================
FILE: cm_library_c_binding/build.py
================================================
from __future__ import annotations
import argparse
import logging
import os
from typing import NamedTuple
from cffi import FFI
logging.basicConfig(level=logging.INFO)
here = os.path.dirname(os.path.abspath(__file__))
class Source(NamedTuple):
h: str
include: str
def gather_sources_from_directory(directory: str) -> list[Source]:
"""
Gather source files from a given directory.
:param directory: The directory where source files are located.
:return: A list of Source namedtuples.
"""
sources = []
for filename in os.listdir(directory):
if filename.endswith(".h"):
include_line = f"#include <{filename}>"
sources.append(Source(filename, include_line))
return sorted(sources)
define_static_lib = """
#if defined(_WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
# define SECP256K1_STATIC 1
#endif
"""
def mk_ffi(
directory: str,
sources: list[Source],
static_lib: bool = False, # noqa: FBT001, FBT002
name: str = "_libsecp256k1",
) -> FFI:
"""
Create an FFI object.
:param sources: A list of Source namedtuples.
:param static_lib: Whether to generate a static lib in Windows.
:param name: The name of the FFI object.
:return: An FFI object.
"""
_ffi = FFI()
code = [define_static_lib] if static_lib else []
logging.info(" Static %s...", static_lib)
for source in sources:
with open(os.path.join(directory, source.h), encoding="utf-8") as h:
logging.info(" Including %s...", source.h)
c_header = h.read()
_ffi.cdef(c_header)
code.append(source.include)
code.append("#define PY_USE_BUNDLED")
_ffi.set_source(name, "\n".join(code))
return _ffi
if __name__ == "__main__":
logging.info("Starting CFFI build process...")
parser = argparse.ArgumentParser(description="Generate C code using CFFI.")
parser.add_argument("headers_dir", help="Path to the header files.", type=str)
parser.add_argument("c_file", help="Generated C code filename.", type=str)
parser.add_argument("static_lib", help="Generate static lib in Windows.", default="0N", type=str)
args = parser.parse_args()
modules = gather_sources_from_directory(args.headers_dir)
ffi = mk_ffi(args.headers_dir, modules, args.static_lib == "ON")
ffi.emit_c_code(args.c_file)
vendor_cffi = os.environ.get("COINCURVE_VENDOR_CFFI", "1") == "1"
if vendor_cffi:
with open(args.c_file, encoding="utf-8") as f:
source = f.read()
expected_text = 'PyImport_ImportModule("_cffi_backend")'
if expected_text not in source:
msg = f"{expected_text} not found in {args.c_file}"
raise ValueError(msg)
new_source = source.replace(expected_text, 'PyImport_ImportModule("coincurve._cffi_backend")')
with open(args.c_file, "w", encoding="utf-8") as f:
f.write(new_source)
logging.info(" Generated C code: %s", args.c_file)
================================================
FILE: cm_library_cffi_headers/CMakeLists.txt
================================================
# create folder for _gen code
file(MAKE_DIRECTORY ${CFFI_HEADERS_DIR})
macro(generate_cffi_header src_header cffi_header cffi_dir)
add_custom_command(
OUTPUT ${cffi_dir}/${cffi_header}
COMMAND ${Python_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/compose_cffi_headers.py
${src_header} ${cffi_header} ${cffi_dir}
MAIN_DEPENDENCY ${CMAKE_CURRENT_LIST_DIR}/compose_cffi_headers.py
DEPENDS ${src_header}
WORKING_DIRECTORY ${cffi_dir}
)
add_custom_target(${cffi_header} ALL DEPENDS ${cffi_dir}/${cffi_header})
endmacro()
# Extract files from full path of src_headers
if(VENDORED_HEADERS_DIR)
file(GLOB src_headers ${VENDORED_HEADERS_DIR}/*.h)
elseif(VENDORED_AS_SYSTEM_LIB_FOUND)
message(WARNING "Using system library ${VENDORED_LIBRARY_CMAKE_TARGET}. The list of headers is set to:"
" <system_include>/*${VENDORED_LIBRARY_CMAKE_TARGET}/*.h")
file(GLOB src_headers ${VENDORED_AS_SYSTEM_LIB_INCLUDE_DIRS}/*${VENDORED_LIBRARY_CMAKE_TARGET}*.h)
message(STATUS " Generating CFFI header for ${src_headers}")
else()
message(FATAL_ERROR "Headers for CFFI cannot be found. Exiting")
endif()
add_custom_target(headers-for-cffi)
foreach(src_header ${src_headers})
get_filename_component(cffi_header ${src_header} NAME)
get_filename_component(src_header_dir ${src_header} DIRECTORY)
generate_cffi_header(${src_header_dir} ${cffi_header} ${CFFI_HEADERS_DIR})
add_dependencies(headers-for-cffi ${cffi_header})
endforeach()
================================================
FILE: cm_library_cffi_headers/compose_cffi_headers.py
================================================
import argparse
import logging
import os
import re
import sys
logging.basicConfig(level=logging.ERROR)
def remove_c_comments_emptylines(text):
text = re.sub(r"/\*.*?\*/", "", text, flags=re.DOTALL) # Remove multi-line comments
text = re.sub(r"//.*", "", text) # Remove single-line comments
return re.sub(r"\n\s*\n+", "\n", text) # Remove empty lines
def remove_c_includes(lines):
return [line for line in lines if not re.match(r"^\s*#include\s", line)]
def remove_special_defines(lines, defines):
return [line for line in lines if not any(f"#define {define}" in line for define in defines)]
def apply_cffi_defines_syntax(lines):
return [re.sub(r"#\s*define\s+(\w+).*", r"#define \1 ...", line) for line in lines]
def remove_c_ifdef(lines):
processed_lines = []
# The first #if is the multi-inclusion guard
ifdef_count = -1
for line in lines:
stripped_line = line.rstrip()
if re.match(r"^#\s*(if|el|endif)", stripped_line):
stripped_line = stripped_line.replace(" ", "")
ifdef_count += stripped_line.count("#if") - stripped_line.count("#endif")
continue
if ifdef_count == 0:
processed_lines.append(stripped_line)
elif ifdef_count < 0 and line != lines[-1]:
msg = "Unbalanced #if/#endif preprocessor directives."
raise ValueError(msg)
return processed_lines
def concatenate_c_defines(lines):
buffer = []
processed_lines = []
in_define = False
for line in lines:
stripped_line = line.rstrip()
if (re.match(r"#\s*define", stripped_line) or in_define) and stripped_line.endswith("\\"):
in_define = True
buffer.append(
re.sub(r"#\s*define", "#define", stripped_line).rstrip("\\").strip()
) # Normalize #define and remove trailing backslash
continue # Skip the rest of the loop to avoid resetting the buffer
if in_define:
buffer.append(stripped_line)
processed_lines.append(" ".join(buffer))
buffer = [] # Reset the buffer for the next definition
in_define = False
continue # Skip the rest of the loop to avoid adding the line again
processed_lines.append(stripped_line)
return processed_lines
def remove_deprecated_functions(lines, deprecation):
buffer = []
processed_lines = []
in_struct = False
in_define = False
brace_count = 0
for line in lines:
stripped_line = line.rstrip()
if re.match(r"#\s*define", stripped_line) or in_define:
in_define = bool(stripped_line.endswith("\\"))
processed_lines.append(stripped_line)
continue
if stripped_line.startswith("struct") or re.match(r"typedef\s+struct", stripped_line) or in_struct:
in_struct = True
processed_lines.append(stripped_line)
brace_count += stripped_line.count("{") - stripped_line.count("}")
if brace_count == 0: # End of struct block
in_struct = False
continue
buffer.append(stripped_line)
# Check for the end of a function declaration
if stripped_line.endswith(";") and not in_struct:
# Extend if not DEPRECATED
if not any(d in " ".join(buffer) for d in deprecation):
processed_lines.extend(buffer)
buffer = [] # Reset the buffer for the next definition
return processed_lines
def remove_function_attributes(lines, attributes):
processed_lines = []
for line in lines:
stripped_line = line.rstrip()
for attribute, replacement in attributes.items():
# Attributes can be functions with (...), so using regular expression
# Remove the definition
if re.search(rf"#\s*define\s+{attribute}(\(.*\))?\b", stripped_line):
stripped_line = None
break
if re.search(rf"\b{attribute}(\(.*\))?\b", stripped_line):
stripped_line = re.sub(rf"\b{attribute}(\(.*\))?", f"{replacement}", stripped_line)
stripped_line = stripped_line.replace(" ;", ";")
stripped_line = stripped_line.replace(" ", " ")
if stripped_line:
processed_lines.append(stripped_line)
return processed_lines
def remove_header_guard(lines, keywords):
processed_lines = []
for line in lines:
stripped_line = line.rstrip()
for keyword in keywords:
if re.search(rf"#\s*define\s+{keyword}.*_H\b", stripped_line):
continue
processed_lines.append(stripped_line)
return processed_lines
def concatenate_c_struct(lines):
buffer = []
processed_lines = []
in_struct = False
brace_count = 0
for line in lines:
stripped_line = line.strip()
if stripped_line.startswith("struct") or re.match(r"typedef\s+struct", stripped_line) or in_struct:
in_struct = True
brace_count += stripped_line.count("{") - stripped_line.count("}")
buffer.append(stripped_line)
if brace_count == 0: # End of struct block
processed_lines.append(" ".join(buffer).strip())
buffer = [] # Reset the buffer for the next definition
in_struct = False
continue # Skip the rest of the loop to avoid adding the line again
processed_lines.append(stripped_line)
return processed_lines
def make_header_cffi_compliant(src_header_dir, src_header, cffi_dir):
with open(os.path.join(src_header_dir, src_header), encoding="utf-8") as f:
text = remove_c_comments_emptylines(f.read())
lines = text.split("\n")
lines = remove_c_includes(lines)
lines = remove_c_ifdef(lines)
lines = concatenate_c_defines(lines)
lines = remove_deprecated_functions(lines, ["DEPRECATED"])
lines = remove_header_guard(lines, ["SECP256K1"])
lines = remove_function_attributes(
lines,
{
"SECP256K1_API": "extern",
"SECP256K1_WARN_UNUSED_RESULT": "",
"SECP256K1_DEPRECATED": "",
"SECP256K1_ARG_NONNULL": "",
},
)
lines = remove_special_defines(
lines,
[
# Deprecated flags
"SECP256K1_CONTEXT_VERIFY",
"SECP256K1_CONTEXT_SIGN",
"SECP256K1_FLAGS_BIT_CONTEXT_VERIFY",
"SECP256K1_FLAGS_BIT_CONTEXT_SIGN",
# Testing flags
"SECP256K1_CONTEXT_DECLASSIFY",
"SECP256K1_FLAGS_BIT_CONTEXT_DECLASSIFY",
# Not for direct use - That may not mean to remove them!
# 'SECP256K1_FLAGS_TYPE_MASK',
# 'SECP256K1_FLAGS_TYPE_CONTEXT',
# 'SECP256K1_FLAGS_TYPE_COMPRESSION',
# 'SECP256K1_FLAGS_BIT_COMPRESSION',
# Not supported
"SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC",
"SECP256K1_SCHNORRSIG_EXTRAPARAMS",
],
)
lines = apply_cffi_defines_syntax(lines)
logging.info(" Writing: %s in %s", src_header, cffi_dir)
output_filename = os.path.join(cffi_dir, src_header)
with open(output_filename, "w", encoding="utf-8") as f_out:
f_out.write("\n".join(lines))
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Process a header file.")
parser.add_argument("src_header_dir", type=str, help="The path to the header file to be processed.")
parser.add_argument("cffi_header", type=str, help="The path where the compliant header will be written.")
parser.add_argument("cffi_dir", type=str, help="The path where the compliant header will be written.", default=".")
args = parser.parse_args()
# Verify args are valid
if not os.path.isdir(args.src_header_dir):
logging.error("Error: Directory: %s not found.", args.src_header_dir)
sys.exit(1)
if not os.path.isdir(args.cffi_dir):
logging.error("Error: Directory: %s not found.", args.cffi_dir)
sys.exit(1)
if not os.path.isfile(os.path.join(args.src_header_dir, args.cffi_header)):
logging.error("Error: %s not found in %s.", args.cffi_header, args.src_header_dir)
sys.exit(1)
make_header_cffi_compliant(args.src_header_dir, args.cffi_header, args.cffi_dir)
================================================
FILE: cm_python_module/CMakeLists.txt
================================================
# Create the shared library from the CFFI binding and the static library from ${CFFI_INPUT_LIBRARY}
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
Python_add_library(${CFFI_OUTPUT_LIBRARY} MODULE USE_SABI 3.8 "${CFFI_C_CODE_DIR}/${CFFI_C_CODE}")
else()
set(Python_SOABI ${SKBUILD_SOABI})
Python_add_library(${CFFI_OUTPUT_LIBRARY} MODULE WITH_SOABI "${CFFI_C_CODE_DIR}/${CFFI_C_CODE}")
target_compile_definitions(${CFFI_OUTPUT_LIBRARY} PRIVATE Py_LIMITED_API)
endif()
set_source_files_properties("${CFFI_C_CODE_DIR}/${CFFI_C_CODE}" PROPERTIES GENERATED 1)
# Detect whether the vendored library is a system library or not
if (PROJECT_IGNORE_SYSTEM_LIB OR NOT VENDORED_AS_SYSTEM_LIB_FOUND)
# The build-type seems to be defined as 'MODULE', which creates issues with missing variables
# for CMake: (This only happens on Windows though ...)
set(CMAKE_MODULE_LINKER_FLAGS_COVERAGE "")
add_dependencies(${CFFI_OUTPUT_LIBRARY} ${CFFI_INPUT_LIBRARY})
add_dependencies(${CFFI_OUTPUT_LIBRARY} cffi-c-binding)
target_include_directories(${CFFI_OUTPUT_LIBRARY} PUBLIC ${VENDORED_HEADERS_DIR})
# Link the vendored library to the output library
# https://docs.python.org/3/c-api/stable.html#limited-c-api
target_link_libraries(${CFFI_OUTPUT_LIBRARY} PRIVATE ${CFFI_INPUT_LIBRARY})
elseif(VENDORED_AS_SYSTEM_LIB_FOUND)
message(STATUS "Vendored system library found: ${VENDORED_AS_SYSTEM_LIB_LIBRARIES}")
target_include_directories(${CFFI_OUTPUT_LIBRARY} PRIVATE ${VENDORED_AS_SYSTEM_LIB_INCLUDE_DIRS})
add_dependencies(${CFFI_OUTPUT_LIBRARY} cffi-c-binding)
# On windows, using the LDFLAGS field creates /libpath... secp256k1.lib (correct), but at a later stage
# /libpath is converted to \libpath and fails to be interpreted as a flag by the linker
# This may be an issue with libsecp256k1.pc, i.e. wrong slash used that triggers the slash conversion
target_link_libraries(${CFFI_OUTPUT_LIBRARY} PRIVATE ${VENDORED_AS_SYSTEM_LIB_LIBRARIES})
string(REPLACE "/libpath:" "" VENDORED_AS_SYSTEM_LIB_LIBRARY_DIRS ${VENDORED_AS_SYSTEM_LIB_LIBRARY_DIRS})
string(REPLACE "/LIBPATH:" "" VENDORED_AS_SYSTEM_LIB_LIBRARY_DIRS ${VENDORED_AS_SYSTEM_LIB_LIBRARY_DIRS})
target_link_directories(${CFFI_OUTPUT_LIBRARY} PRIVATE ${VENDORED_AS_SYSTEM_LIB_LIBRARY_DIRS})
else()
message(FATAL_ERROR "Vendored library not found.")
endif()
# Add platform-specific definitions
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_compile_definitions(${CFFI_OUTPUT_LIBRARY} PUBLIC "IS_LINUX")
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
target_compile_definitions(${CFFI_OUTPUT_LIBRARY} PUBLIC "IS_MACOS")
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
target_compile_definitions(${CFFI_OUTPUT_LIBRARY} PUBLIC "IS_WINDOWS")
endif()
================================================
FILE: cm_vendored_library/CMakeLists.txt
================================================
if (PROJECT_IGNORE_SYSTEM_LIB OR NOT VENDORED_AS_SYSTEM_LIB_FOUND)
# Note that this could also be handled by: ExternalProject_Add
# However, FetchContent is a more flexible way to handle this
# https://cmake.org/cmake/help/latest/module/ExternalProject.html
# https://cmake.org/cmake/help/latest/module/FetchContent.html
include(GNUInstallDirs)
include(FetchContent)
# Set ULR based upon CMake definitions
if (NOT VENDORED_UPSTREAM_URL)
set(VENDORED_UPSTREAM_URL "https://github.com/bitcoin-core/secp256k1/archive")
endif()
if (NOT VENDORED_UPSTREAM_REF)
message(STATUS "VENDORED_UPSTREAM_REF not set, using default value.")
set(VENDORED_UPSTREAM_REF "1ad5185cd42c0636104129fcc9f6a4bf9c67cc40")
endif()
if (NOT VENDORED_UPSTREAM_SHA)
message(STATUS "VENDORED_UPSTREAM_SHA not set, using default value.")
set(VENDORED_UPSTREAM_SHA "ba34be4319f505c5766aa80b99cfa696cbb2993bfecf7d7eb8696106c493cb8c")
endif()
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
# -fPIC is needed since we will link it from a shared object
if (VENDORED_LIBRARY_STATIC_BUILD)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
endif()
include(UpdateVendoredLibraryOptions)
UpdateVendoredLibraryOptions("VENDORED_OPTION" "${VENDORED_LIBRARY_OPTION_PREFIX}")
FetchContent_Declare(
vendored_library
URL "${VENDORED_UPSTREAM_URL}/${VENDORED_UPSTREAM_REF}.tar.gz"
URL_HASH "SHA256=${VENDORED_UPSTREAM_SHA}"
)
FetchContent_MakeAvailable(vendored_library)
if (NOT IS_DIRECTORY ${vendored_library_SOURCE_DIR}/include)
message(FATAL_ERROR "The system library: ${VENDORED_LIBRARY_PKG_CONFIG} was not found OR")
message(FATAL_ERROR "The IGNORE_SYSTEM_LIB flag was not set (${PROJECT_IGNORE_SYSTEM_LIB}) OR")
message(FATAL_ERROR "The vendored library was not installed correctly (<src>/include does not exists). Exiting")
else()
set(VENDORED_HEADERS_DIR "${vendored_library_SOURCE_DIR}/include" CACHE PATH "Path to the vendored headers")
# Avoid spurious warnings when building the vendored library
unset(VENDORED_UPSTREAM_URL PARENT_SCOPE)
unset(VENDORED_UPSTREAM_REF PARENT_SCOPE)
unset(VENDORED_UPSTREAM_SHA PARENT_SCOPE)
endif()
else()
include(UnsetVendoredLibraryOptions)
UnsetVendoredLibraryOptions("VENDORED_OPTION")
unset(VENDORED_LIBRARY_OPTION_PREFIX)
unset(VENDORED_LIBRARY_STATIC_BUILD)
unset(VENDORED_UPSTREAM_URL)
unset(VENDORED_UPSTREAM_REF)
unset(VENDORED_UPSTREAM_SHA)
endif()
================================================
FILE: cmake/SetCrossCompilerGithubActions.cmake
================================================
function(SetCrossCompilerGithubActions)
# Cross-compilation options: This is setup for Github/Actions runners
# For Linux, we use cibuildwheel to build the wheels, which uses Docker
if (PROJECT_CROSS_COMPILE_TARGET AND CMAKE_SYSTEM_NAME STREQUAL "Darwin")
set(AUTOMATIC_OSX_TARGETS "armv7" "armv7s" "arm64" "arm64e" "x86_64")
if ("${PROJECT_CROSS_COMPILE_TARGET}" IN_LIST AUTOMATIC_OSX_TARGETS)
set(CMAKE_OSX_ARCHITECTURES ${PROJECT_CROSS_COMPILE_TARGET})
else()
message(FATAL_ERROR "Cross-compilation target not supported: >${PROJECT_CROSS_COMPILE_TARGET}< (${AUTOMATIC_OSX_TARGETS})")
endif()
elseif (PROJECT_CROSS_COMPILE_TARGET AND CMAKE_SYSTEM_NAME STREQUAL "Windows")
set(AUTOMATIC_WINDOWS_TARGETS "AMD64" "x86" "arm64")
if ("${PROJECT_CROSS_COMPILE_TARGET}" IN_LIST AUTOMATIC_WINDOWS_TARGETS)
# Cross-compilation for Windows host system:
set(CMAKE_SYSTEM_PROCESSOR ${PROJECT_CROSS_COMPILE_TARGET})
set(CMAKE_LIBRARY_ARCHITECTURE ${PROJECT_CROSS_COMPILE_TARGET})
else()
message(FATAL_ERROR "Cross-compilation target not supported: >${PROJECT_CROSS_COMPILE_TARGET}< (${AUTOMATIC_WINDOWS_TARGETS})")
endif()
endif()
endfunction()
================================================
FILE: cmake/SetDefaultVendoredLibrary.cmake
================================================
function (SetDefaultVendoredLibrary)
if (DEFINED VENDORED_LIBRARY_CMAKE_TARGET)
set(VENDORED_LIBRARY_CMAKE_TARGET ${VENDORED_LIBRARY_CMAKE_TARGET})
else()
set(VENDORED_LIBRARY_CMAKE_TARGET "secp256k1")
endif()
set(VENDORED_LIBRARY_CMAKE_TARGET ${VENDORED_LIBRARY_CMAKE_TARGET} PARENT_SCOPE)
if (DEFINED VENDORED_LIBRARY_PKG_CONFIG)
set(VENDORED_LIBRARY_PKG_CONFIG ${VENDORED_LIBRARY_PKG_CONFIG})
else()
set(VENDORED_LIBRARY_PKG_CONFIG "lib${VENDORED_LIBRARY_CMAKE_TARGET}")
endif()
set(VENDORED_LIBRARY_PKG_CONFIG ${VENDORED_LIBRARY_PKG_CONFIG} PARENT_SCOPE)
if (DEFINED VENDORED_LIBRARY_PKG_CONFIG_VERSION)
set(VENDORED_LIBRARY_PKG_CONFIG_VERSION ${VENDORED_LIBRARY_PKG_CONFIG_VERSION})
else()
set(VENDORED_LIBRARY_PKG_CONFIG_VERSION "0.4.1")
endif()
set(VENDORED_LIBRARY_PKG_CONFIG_VERSION ${VENDORED_LIBRARY_PKG_CONFIG_VERSION} PARENT_SCOPE)
endfunction()
================================================
FILE: cmake/SetSystemLibIfExists.cmake
================================================
function (SetSystemLibIfExists)
set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH};$ENV{CONDA_PREFIX}/Library/lib/pkgconfig;$ENV{CONDA_PREFIX}/lib/pkgconfig")
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
set(PKG_CONFIG_EXECUTABLE "${PKG_CONFIG_EXECUTABLE};--msvc-syntax;--dont-define-prefix")
endif()
pkg_check_modules(VENDORED_AS_SYSTEM_LIB IMPORTED_TARGET GLOBAL ${VENDORED_LIBRARY_PKG_CONFIG}>=${VENDORED_LIBRARY_PKG_CONFIG_VERSION})
endfunction()
================================================
FILE: cmake/UnsetVendoredLibraryOptions.cmake
================================================
function (UnsetVendoredLibraryOptions _prefix)
get_cmake_property(_vars VARIABLES)
string (REGEX MATCHALL "(^|;)${_prefix}[A-Za-z0-9_]*" _matchedVars "${_vars}")
foreach (_var ${_matchedVars})
unset (${_var} PARENT_SCOPE)
endforeach()
endfunction()
================================================
FILE: cmake/UpdateVendoredLibraryOptions.cmake
================================================
function (UpdateVendoredLibraryOptions _prefix _newPrefix)
get_cmake_property(_vars VARIABLES)
string (REGEX MATCHALL "(^|;)${_prefix}[A-Za-z0-9_]*" _matchedVars "${_vars}")
foreach (_var ${_matchedVars})
string (REGEX REPLACE "^${_prefix}" "${_newPrefix}" _newVar ${_var})
set(${_newVar} ${${_var}} PARENT_SCOPE)
unset (${_var} PARENT_SCOPE)
endforeach()
endfunction()
================================================
FILE: cmake/VerifyPythonModule.cmake
================================================
# Verify CFFI python module is available
function(VerifyPythonModule module python_executable)
find_package(Python3 REQUIRED COMPONENTS Interpreter)
execute_process(
COMMAND ${python_executable} -c "import ${module}"
ERROR_VARIABLE _error
OUTPUT_QUIET
ERROR_STRIP_TRAILING_WHITESPACE
)
if(_error)
message(FATAL_ERROR "${module} is required to build coincurve")
endif()
endfunction()
================================================
FILE: docs/.snippets/abbrs.txt
================================================
*[ECDH]: Elliptic-curve Diffie–Hellman
*[PyPI]: Python Package Index
================================================
FILE: docs/.snippets/links.txt
================================================
[Bitcoin Core]: https://github.com/bitcoin/bitcoin
[ECDH]: https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman
[RFC 6979]: https://tools.ietf.org/html/rfc6979
[libsecp256k1]: https://github.com/bitcoin-core/secp256k1
[secp256k1]: https://en.bitcoin.it/wiki/Secp256k1
================================================
FILE: docs/api.md
================================================
# Developer Interface
-----
All objects are available directly under the root namespace `coincurve`.
::: coincurve.verify_signature
::: coincurve.PrivateKey
options:
members:
- __init__
- sign
- sign_recoverable
- sign_schnorr
- ecdh
- add
- multiply
- to_int
- to_hex
- to_pem
- to_der
- from_int
- from_hex
- from_pem
- from_der
::: coincurve.PublicKey
options:
members:
- __init__
- verify
- format
- point
- combine
- add
- multiply
- combine_keys
- from_signature_and_message
- from_secret
- from_valid_secret
- from_point
::: coincurve.PublicKeyXOnly
options:
members:
- __init__
- verify
- format
- tweak_add
- from_secret
- from_valid_secret
================================================
FILE: docs/assets/css/custom.css
================================================
/* Brighter links for dark mode */
[data-md-color-scheme=slate] {
/* https://github.com/squidfunk/mkdocs-material/blob/9.1.2/src/assets/stylesheets/main/_colors.scss#L91-L92 */
--md-typeset-a-color: var(--md-primary-fg-color--light);
}
/* FiraCode https://github.com/tonsky/FiraCode */
code { font-family: 'Fira Code', monospace; }
@supports (font-variation-settings: normal) {
code { font-family: 'Fira Code VF', monospace; }
}
/* https://github.com/squidfunk/mkdocs-material/issues/1522 */
.md-typeset h5 {
color: var(--md-default-fg-color);
text-transform: none;
}
/* Indentation. */
div.doc-contents:not(.first) {
padding-left: 25px;
border-left: .05rem solid var(--md-typeset-table-color);
}
/* Mark external links as such. */
a.external::after,
a.autorefs-external::after {
/* https://primer.style/octicons/arrow-up-right-24 */
mask-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18.25 15.5a.75.75 0 00.75-.75v-9a.75.75 0 00-.75-.75h-9a.75.75 0 000 1.5h7.19L6.22 16.72a.75.75 0 101.06 1.06L17.5 7.56v7.19c0 .414.336.75.75.75z"></path></svg>');
-webkit-mask-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18.25 15.5a.75.75 0 00.75-.75v-9a.75.75 0 00-.75-.75h-9a.75.75 0 000 1.5h7.19L6.22 16.72a.75.75 0 101.06 1.06L17.5 7.56v7.19c0 .414.336.75.75.75z"></path></svg>');
content: ' ';
display: inline-block;
vertical-align: middle;
position: relative;
height: 1em;
width: 1em;
background-color: currentColor;
}
a.external:hover::after,
a.autorefs-external:hover::after {
background-color: var(--md-accent-fg-color);
}
================================================
FILE: docs/benchmarks.md
================================================
# Benchmarks
-----
## Setup
Download [Hatch](https://hatch.pypa.io/latest/install/) or [UV](https://docs.astral.sh/uv/getting-started/installation/) in order to run the benchmarks as follows:
```
[hatch|uv] run scripts/bench.py
```
## Results
| Library | Key generation | Signing | Verification | Key export | Key import |
| --- | --- | --- | --- | --- | --- |
| coincurve v21.0.0 | 33.4 | 52.8 | 59.0 | 12.6 | 39.4 |
| [fastecdsa](https://github.com/AntonKueltz/fastecdsa) v3.0.1 | 1319.6 | 1449.5 | 1160.4 | 1402.9 | 15.5 |
!!! note
- the timings are in microseconds
- signing and verification use a 16 KiB message
- the Python version used for the benchmarks is 3.13.x
================================================
FILE: docs/history.md
================================================
# History
-----
Important changes are emphasized.
## Unreleased
## 21.0.0
- **Breaking:** Drop support for Python 3.8
- Add support for Python 3.13
- Remove all runtime dependencies (`cffi` & `asn1crypto`)
- Add `COINCURVE_VENDOR_CFFI` environment variable to control vendoring of the `_cffi_backend` module
- Minor performance improvement by removing use of formatted string constants
- Upgrade [libsecp256k1][] to version 0.6.0
## 20.0.0
- **Breaking:** CMake is now a build dependency; this is only a breaking change for redistributors as building with standard Python packaging tools will automatically use the CMake that is available on PyPI
- **Breaking:** Stop building wheels for Windows 32-bit
- Build wheels for Windows ARM64
- Upgrade [libsecp256k1][] to version 0.5.0
## 19.0.1
- Fix regression in Windows wheels
## 19.0.0
- **Breaking:** Drop support for Python 3.7
- Add support for Python 3.12
- Upgrade [libsecp256k1][] to version 0.4.1
## 18.0.0
- Support Schnorr signatures
- Add support for Python 3.11
- Upgrade [libsecp256k1][] to the latest available version
## 17.0.0
- **Breaking:** Drop support for Python 3.6
- Fix wheels for Apple M1
- Upgrade [libsecp256k1][] to the latest available version
## 16.0.0
- Wheels for Apple Silicon and musl linux (Alpine)
- No wheels for PyPy until the build system is fixed
## 15.0.1
- Fix the `combine` method of `PublicKey`
## 15.0.0
- **Breaking:** Drop support for Python 2
- **Breaking:** Binary wheels for CPython require version 19.3 or later of ``pip`` to install
- Build AArch64 binary wheels for Linux
- Build binary wheels for PyPy3.6 7.3.3 & PyPy3.7 7.3.3 on Linux
- Upgrade [libsecp256k1][] to the latest available version
- Upgrade libgmp to the latest available version
- Introduce `COINCURVE_UPSTREAM_REF` environment variable to select an alternative [libsecp256k1][] version when building from source
- Support PEP 561 type hints
- Added support for supplying a custom nonce to `PrivateKey.sign_recoverable`
## 14.0.0
**IMPORTANT: This will be the final release that supports Python 2.**
- **New:** Binary wheels for Python 3.9
- **Breaking:** Drop support for Python 3.5
- Fetch [libsecp256k1][] source if the system installation lacks ECDH support
- Fix innocuous `setuptools` warning when building from source
- Switch CI/CD to GitHub Actions
## 13.0.0
- **New:** Binary wheels for Python 3.8
- Support building on OpenBSD
- Improve handling of PEM private key deserialization
- Improve ECDH documentation
- Improvements from [libsecp256k1][] master
## 12.0.0
- **New:** Binary wheels on Linux for PyPy3.6 v7.1.1-beta
- **New:** Binary wheels on macOS for Python 3.8.0-alpha.3
- **New:** Binary wheels on Linux are now also built with the new [manylinux2010](https://www.python.org/dev/peps/pep-0571) spec for 64-bit platforms
- Improvements from [libsecp256k1][] master
## 11.0.0
- Fix some linking scenarios by placing bundled [libsecp256k1][] dir first in path
- Allow override of system [libsecp256k1][] with environment variable
- Add benchmarks
- Use Codecov to track coverage
- Use black for code formatting
## 10.0.0
- Support tox for testing
- Compatibility with latest [libsecp256k1][] ECDH API
- Make libgmp optional when building from source
## 9.0.0
- Fixed wheels for macOS
- **Breaking:** Drop support for 32-bit macOS
## 8.0.2
- No longer package tests
## 8.0.0
- **New:** Binary wheels for Python 3.7
- **Changed:** Binary wheels on macOS for Python 3.5 now use Homebrew
Python for compilation due to new security requirements
- Make build system support new GitHub & PyPI security requirements
- Improvements from [libsecp256k1][] master
## 7.1.0
- Pin version of [libsecp256k1][]
- Improve docs
## 7.0.0
- Improvements from [libsecp256k1][] master
- Fix build script
## 6.0.0
- Resolved #6. You can choose to use this or remain on `5.2.0`. This will only be a temporary change, see 3e93480b3e38c6b9beb0bc2de83bc3630fc74c46.
## 5.2.0
- Added support for supplying a custom nonce to `PrivateKey.sign`
## 5.1.0
- Added `PublicKey.combine_keys` class method
- Improvements to documentation
## 5.0.1
- Fixed an issue where secret validation would occasionally erroneously error
on user-provided secrets (secrets not generated by Coincurve itself) if there
were not exactly 256 bits of entropy. See #5.
## 5.0.0
- **Breaking:** Coincurve is now dual-licensed under the terms of `MIT` and `Apache-2.0`
- Performance improvements from [libsecp256k1][] master
- Improvements to documentation.
## 4.5.1
- First public stable release
================================================
FILE: docs/index.md
================================================
# coincurve
| | |
| --- | --- |
| CI/CD | [{ loading=lazy }](https://github.com/ofek/coincurve/actions/workflows/build.yml) [{ loading=lazy }](https://codecov.io/github/ofek/coincurve) |
| Docs | [{ loading=lazy }](https://github.com/ofek/coincurve/actions/workflows/docs.yml) |
| Package | [{ loading=lazy }](https://pypi.org/project/coincurve/) [{ loading=lazy }](https://pypi.org/project/coincurve/) [{ loading=lazy }](https://pypi.org/project/coincurve/) |
| Meta | [{ loading=lazy }](https://github.com/ofek/dep-sync) [{ loading=lazy }](https://github.com/astral-sh/ruff) [{ loading=lazy }](https://github.com/python/mypy) [{ loading=lazy }](https://spdx.org/licenses/) [{ loading=lazy }](https://github.com/sponsors/ofek) |
-----
This library provides well-tested Python bindings for [libsecp256k1][], the heavily optimized
C library used by [Bitcoin Core][] for operations on the elliptic curve [secp256k1][].
## Features
- Fastest available implementation (more than 10x faster than OpenSSL)
- Clean, easy to use API
- Frequent updates from the development version of [libsecp256k1][]
- Linux, macOS, and Windows all have binary packages for multiple architectures
- Deterministic signatures as specified by [RFC 6979][]
- Non-malleable signatures (lower-S form) by default
- Secure, non-malleable [ECDH][] implementation
## Users
- [Ethereum](https://ethereum.org)
- [LBRY](https://lbry.com)
- [libp2p](https://libp2p.io)
and [many more](users.md)!
## License
`coincurve` is distributed under the terms of any of the following licenses:
- [MIT](https://spdx.org/licenses/MIT.html)
- [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)
## Navigation
Desktop readers can use keyboard shortcuts to navigate.
| Keys | Action |
| --- | --- |
| <ul><li><kbd>,</kbd> (comma)</li><li><kbd>p</kbd></li></ul> | Navigate to the "previous" page |
| <ul><li><kbd>.</kbd> (period)</li><li><kbd>n</kbd></li></ul> | Navigate to the "next" page |
| <ul><li><kbd>/</kbd></li><li><kbd>s</kbd></li></ul> | Display the search modal |
================================================
FILE: docs/install.md
================================================
# Installation
-----
`coincurve` is available on PyPI and can be installed with [pip](https://pip.pypa.io):
```
pip install coincurve
```
## Wheel
Binary wheels are available for most platforms and require at least version `19.3` of pip to install.
| | | | | |
| --- | --- | --- | --- | --- |
| | macOS | Windows | Linux (glibc) | Linux (musl) |
| CPython 3.9 | <ul><li>x86_64</li><li>ARM64</li></ul> | <ul><li>x86_64</li><li>ARM64</li></ul> | <ul><li>x86_64</li><li>i686</li><li>AArch64</li></ul> | <ul><li>x86_64</li><li>i686</li><li>AArch64</li></ul> |
| CPython 3.10 | <ul><li>x86_64</li><li>ARM64</li></ul> | <ul><li>x86_64</li><li>ARM64</li></ul> | <ul><li>x86_64</li><li>i686</li><li>AArch64</li></ul> | <ul><li>x86_64</li><li>i686</li><li>AArch64</li></ul> |
| CPython 3.11 | <ul><li>x86_64</li><li>ARM64</li></ul> | <ul><li>x86_64</li><li>ARM64</li></ul> | <ul><li>x86_64</li><li>i686</li><li>AArch64</li></ul> | <ul><li>x86_64</li><li>i686</li><li>AArch64</li></ul> |
| CPython 3.12 | <ul><li>x86_64</li><li>ARM64</li></ul> | <ul><li>x86_64</li><li>ARM64</li></ul> | <ul><li>x86_64</li><li>i686</li><li>AArch64</li></ul> | <ul><li>x86_64</li><li>i686</li><li>AArch64</li></ul> |
| CPython 3.13 | <ul><li>x86_64</li><li>ARM64</li></ul> | <ul><li>x86_64</li><li>ARM64</li></ul> | <ul><li>x86_64</li><li>i686</li><li>AArch64</li></ul> | <ul><li>x86_64</li><li>i686</li><li>AArch64</li></ul> |
## Source
If you are on a platform without support for pre-compiled wheels, you will need certain system packages in order to build from source.
A few environment variables influence the build:
- `COINCURVE_UPSTREAM_REF` - This is the Git reference of [libsecp256k1][] to use rather than the (frequently updated) default.
- `COINCURVE_IGNORE_SYSTEM_LIB` - The presence of this will force fetching of [libsecp256k1][] even if it's already detected at the system level.
- `COINCURVE_VENDOR_CFFI` - Setting this to anything other than `1` (the default) prevents vendoring of the `_cffi_backend` module. Re-distributors should make sure to require `cffi` as a runtime dependency when disabling this.
!!! tip
To avoid installing the binary wheels on compatible distributions, use the `--no-binary` option.
```
pip install coincurve --no-binary coincurve
```
### Alpine
```
sudo apk add autoconf automake build-base libffi-dev libtool pkgconfig python3-dev
```
### Debian/Ubuntu
```
sudo apt-get install -y autoconf automake build-essential libffi-dev libtool pkg-config python3-dev
```
### RHEL/CentOS
```
sudo yum install -y autoconf automake gcc gcc-c++ libffi-devel libtool make pkgconfig python3-devel
```
### macOS
```
xcode-select --install
brew install autoconf automake libffi libtool pkg-config python
```
================================================
FILE: docs/users.md
================================================
# Users
-----
## Organizations
| Name | Projects |
| --- | --- |
| [Anyl](https://github.com/Anylsite) | <ul><li><a href=https://github.com/Anylsite/anyledger-backend/blob/cb9e277ef4ba775384a1eb80ff1577418f88684e/requirements.in#L5> anyledger-backend </a></li></ul> |
| [ARK](https://ark.io) | <ul><li><a href=https://github.com/ArkEcosystem/python-crypto/blob/1bd016f76b41eba9711be748c1caf20d8042f590/setup.py#L9> python-crypto </a></li></ul> |
| [Augur](https://www.augur.net) | <ul><li><a href=https://github.com/AugurProject/augur-core/blob/5388e00120d3e2328b5ccc70960bedff1c8a03dd/requirements.txt#L9> augur-core </a></li></ul> |
| [Blockcerts](https://www.blockcerts.org) | <ul><li><a href=https://github.com/blockchain-certificates/cert-issuer/blob/9b0ed451ef686018a507acd8bb7d217263a6fbf4/ethereum_requirements.txt#L2> cert-issuer </a></li></ul> |
| [ECIES](https://ecies.org) | <ul><li><a href=https://github.com/ecies/py/blob/7d4bd252129410eb1472d9e3cf0fd9f1fcfaf174/pyproject.toml#L38> py </a></li></ul> |
| [Elements](https://elementsproject.org) | <ul><li><a href=https://github.com/ElementsProject/lightning/blob/d134580419b90f2299cfa3646906b8b8b96c356e/requirements.txt#L33> lightning </a></li></ul> |
| [Ethereum](https://ethereum.org) | <ul><li><a href=https://github.com/ethereum/alexandria/blob/adba4114fbd5f707181da602abd977e008e463c9/setup.py#L67> alexandria </a></li><li><a href=https://github.com/ethereum/ddht/blob/341e84e9163338556cd48dd2fcfda9eedec3eb45/setup.py#L73> ddht </a></li><li><a href=https://github.com/ethereum/eth-keys/blob/dd4f00a5d2f2b394665ccecc9817f753e58cc7bc/setup.py#L10> eth-keys </a></li><li><a href=https://github.com/ethereum/eth-tester/blob/1e37e846915374914bdc2950fbb2f9ea6ca2f3ec/tox.ini#L16> eth-tester </a></li><li><a href=https://github.com/ethereum/py-evm/blob/5e949a457fbe6692dcd9e9e2f141a1848000a0c2/setup.py#L26> py-evm </a></li><li><a href=https://github.com/ethereum/pydevp2p/blob/b09b8a06a152f34cd7dc7950b14b04e3f01511af/requirements.txt#L8> pydevp2p </a></li><li><a href=https://github.com/ethereum/trinity/blob/65609f8fda7d880c0efe859ea84a7d0935c02edb/setup.py#L39> trinity </a></li></ul> |
| [Gnosis](https://gnosis.io) | <ul><li><a href=https://github.com/gnosis/gnosis-py/blob/7bad62eb83b50cf952227f5ae1019c95b8b4d9cd/README.rst#quick-start> gnosis-py </a></li></ul> |
| [Golem Network](https://golem.network) | <ul><li><a href=https://github.com/golemfactory/concent/blob/23ffa9464c995a628aa570bc9a0a05d29f48b044/concent_api/requirements.lock#L18> concent </a></li><li><a href=https://github.com/golemfactory/golem/blob/6280b5d946640e81c475c66c5de4a9e53cbfcc69/requirements.txt#L25> golem </a></li><li><a href=https://github.com/golemfactory/golem-messages/blob/aae2a60a88c938f5ed7012b838cf2a29934b58b2/setup.py#L41> golem-messages </a></li></ul> |
| [ICON Foundation](https://icon.foundation) | <ul><li><a href=https://github.com/icon-project/goloop/blob/6c8341e0c29fc7f9136221f315e8a76af9b5450c/pyee/requirements.txt#L2> goloop </a></li><li><a href=https://github.com/icon-project/icon-sdk-python/blob/cb293688c154349cabeb9b3f50a1cab29e91859d/requirements.txt#L2> icon-sdk-python </a></li><li><a href=https://github.com/icon-project/icon-service/blob/73c8d4521374207ef77e63b789fd511fc6d028b8/requirements.txt#L3> icon-service </a></li><li><a href=https://github.com/icon-project/loopchain/blob/9f29e8914918e12d683f2e1318c9b3c52dbee08d/requirements.txt#L11> loopchain </a></li><li><a href=https://github.com/icon-project/t-bears/pull/77> t-bears </a></li></ul> |
| [LBRY](https://lbry.com) | <ul><li><a href=https://github.com/lbryio/lbry-android-sdk/blob/b0331248cdb7b1ca4a866e4aaa166dd820549e56/recipes/coincurve/__init__.py> lbry-android-sdk </a></li><li><a href=https://github.com/lbryio/lbry-sdk/blob/7486ee95371c238d51e1c2552113ad25bfaf0426/setup.py#L53> lbry-sdk </a></li></ul> |
| [libp2p](https://libp2p.io) | <ul><li><a href=https://github.com/libp2p/py-libp2p/blob/12786f4e26783b530279a6f89089cf69af8e3922/setup.py#L73> py-libp2p </a></li></ul> |
| [Microsoft](https://www.microsoft.com) | <ul><li><a href=https://github.com/microsoft/CCF/blob/f6670587ec1cb6c0faf8efcc6e4d08b8f4c1fd60/tests/requirements.txt#L4> CCF </a></li></ul> |
| [NuCypher](https://www.nucypher.com) | <ul><li><a href=https://github.com/nucypher/nucypher/blob/24a57e1c810aa6408ecfc24942956925146aa024/requirements.txt#L16> nucypher </a></li><li><a href=https://github.com/nucypher/nucypher-monitor/blob/f8df51a37d0299c36541b26ef13d72fa390c294e/requirements.txt#L17> nucypher-monitor </a></li></ul> |
| [Quantstamp](https://quantstamp.com) | <ul><li><a href=https://github.com/quantstamp/qsp-protocol-node/blob/6f776b01c91a3b1c306ab74932324ea367fa6157/requirements.txt#L32> qsp-protocol-node </a></li></ul> |
| [QuarkChain](https://www.quarkchain.io) | <ul><li><a href=https://github.com/QuarkChain/pyquarkchain/blob/1f858f46efe31c3fb3bac20cdbd44eb94d89fb63/requirements.txt#L4> pyquarkchain </a></li></ul> |
| [Raiden Network](https://raiden.network) | <ul><li><a href=https://github.com/raiden-network/demo-train/blob/66d187bd4f9e83a7d8d0cc43fc5d31c8444745f5/requirements.txt#L16> demo-train </a></li><li><a href=https://github.com/raiden-network/light-client/blob/8da39f8f164a3d93674889db39875003e1bc6e93/e2e-environment/synapse/auth/eth_auth_provider.py#L20> light-client </a></li><li><a href=https://github.com/raiden-network/microraiden/blob/2d51e78afaf3c0a8ddab87e59a5260c0064cdbdd/requirements.txt#L5> microraiden </a></li><li><a href=https://github.com/raiden-network/raiden/blob/e4c0f6d22788eddf51da551f11ea988a8dd5fd0d/requirements/requirements.in#L4> raiden </a></li><li><a href=https://github.com/raiden-network/raiden-contracts/blob/93230caa554f6f29e55b4521aafd0af20b710b1f/requirements.txt#L3> raiden-contracts </a></li><li><a href=https://github.com/raiden-network/raiden-service-bundle/blob/1ba6a265016eca4ade0ed4f2a198cebc570c11d3/build/synapse/Dockerfile#L18> raiden-service-bundle </a></li><li><a href=https://github.com/raiden-network/raiden-services/blob/b5d0f81447fbe476ed8185d825560b2b9327d455/src/raiden_libs/utils.py#L4> raiden-services </a></li><li><a href=https://github.com/raiden-network/raiden-wizard/blob/bf2fe8662be4db2c36bcc920d8e31cec888a496e/requirements.txt#L17> raiden-wizard </a></li><li><a href=https://github.com/raiden-network/raidex/blob/master/requirements.txt#L12> raidex </a></li></ul> |
| [SKALE Network](https://skale.network) | <ul><li><a href=https://github.com/skalenetwork/libBLS/blob/785b7ab11f78512f6466b6dc996c4db44825696a/.travis.yml#L46> libBLS </a></li><li><a href=https://github.com/skalenetwork/sgx.py/blob/efff7d1a09abbea2c703cc21b25f9a6d9e7fcb79/setup.py#L11> sgx.py </a></li></ul> |
## Projects
- [bit](https://github.com/ofek/bit/blob/776f97ae7f9b3f05157113abc913eb141b2817ee/setup.py#L44)
- [btcrecover](https://github.com/gurnec/btcrecover/commit/f113867fa22d2f5b22175cc2b5b3892351bc1109)
- [crankycoin](https://github.com/cranklin/crankycoin/blob/7663a1c5429b3ddd11997b6a2e3488018789bf3b/requirements.txt#L2)
- [ForkDelta](https://github.com/forkdelta/backend-replacement/blob/97ccd1a19544f26d242a8412113086f0c0dd5760/requirements.txt#L46)
- [Heimdall](https://github.com/maddevsio/heimdall/blob/21f16880030cfdb1c1c97969158bec02ca6c0336/requirements.txt#L14)
- [HoneyBadgerBFT](https://github.com/initc3/HoneyBadgerBFT-Python/blob/e8bcbc081dfb5d1e7298039d47bbebf7048b8e62/setup.py#L30)
- [JoinMarket](https://github.com/JoinMarket-Org/joinmarket-clientserver/blob/dc581e9c99d7db6436ed9f8913b6ce614bcef8d8/jmbitcoin/setup.py#L13)
- [minichain](https://github.com/kigawas/minichain/blob/0ae0437fdc4aa05e73c4d31a8df91d371542c8fe/pyproject.toml#L13)
- [Nekoyume](https://github.com/nekoyume/nekoyume/blob/0dec2d6f1002091f3f727bd645ce67fadd85faeb/setup.cfg#L45)
- [NuCypher](https://github.com/nucypher/nucypher/blob/24a57e1c810aa6408ecfc24942956925146aa024/requirements.txt#L16)
- [python-idex](https://github.com/sammchardy/python-idex/blob/24cee970172491a7f7d5f52558727a77384cce26/requirements.txt#L2)
- [Rotki](https://github.com/rotki/rotki/blob/70508f99f890bcbd520f1efe7776194d6a5e5e06/requirements.txt#L8)
- [Vyper](https://github.com/vyperlang/vyper/blob/3bd0bf96856554810065fa9cfb89afef7625d436/Dockerfile#L15)
- [ZeroNet](https://github.com/zeronet-conservancy/zeronet-conservancy/blob/b6e18fd3738b4725726c5e170040deb3048c9048/requirements.txt#L12)
================================================
FILE: hatch.toml
================================================
[envs.default]
installer = "uv"
dev-mode = false
[envs.hatch-static-analysis]
config-path = "ruff_defaults.toml"
dependencies = ["ruff==0.13.0"]
[envs.hatch-test]
dev-mode = false
[envs.types]
dependencies = [
"mypy",
"pytest",
]
[envs.types.scripts]
check = "mypy --install-types --non-interactive {args:src/coincurve tests}"
[envs.docs]
dependencies = [
"mkdocs~=1.6.1",
"mkdocs-material~=9.5.40",
# Plugins
"mkdocs-minify-plugin~=0.8.0",
"mkdocs-git-revision-date-localized-plugin~=1.2.9",
"mkdocs-glightbox~=0.4.0",
"mkdocs-redirects~=1.2.1",
"mkdocstrings-python~=1.16.2",
"mike~=2.1.3",
# Extensions
"pymdown-extensions~=10.11.2",
# Necessary for syntax highlighting in code blocks
"pygments~=2.18.0",
# Validation
"linkchecker~=10.5.0",
]
[envs.docs.env-vars]
SOURCE_DATE_EPOCH = "1580601600"
PYTHONUNBUFFERED = "1"
[envs.docs.scripts]
build = "mkdocs build --clean --strict {args}"
serve = "mkdocs serve --dev-addr localhost:8000 {args}"
ci-build = "mike deploy --update-aliases {args}"
validate = "linkchecker --config .linkcheckerrc site"
# https://github.com/linkchecker/linkchecker/issues/678
build-check = [
"build --no-directory-urls",
"validate",
]
================================================
FILE: hatch_build.py
================================================
from __future__ import annotations
import os
import shutil
from functools import cached_property
from importlib.metadata import PackagePath, distribution
from typing import Any
import _cffi_backend # noqa: PLC2701
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
class CustomBuildHook(BuildHookInterface):
"""
A build hook that copies the `_cffi_backend` extension module into the wheel so that
the `cffi` package is not required as a runtime dependency.
"""
LICENSE_NAME = "LICENSE-cffi"
@cached_property
def local_cffi_license(self) -> str:
return os.path.join(self.root, self.LICENSE_NAME)
@staticmethod
def get_cffi_distribution_license_files() -> list[PackagePath]:
license_files = []
dist_files = distribution("cffi").files or []
for f in dist_files:
if f.name == "LICENSE" and f.parts[0].endswith(".dist-info"):
license_files.append(f)
break
return license_files
def initialize(self, version: str, build_data: dict[str, Any]) -> None: # noqa: ARG002
cffi_shared_lib = _cffi_backend.__file__
relative_path = f"coincurve/{os.path.basename(cffi_shared_lib)}"
build_data["force_include"][cffi_shared_lib] = relative_path
license_files = self.get_cffi_distribution_license_files()
if len(license_files) != 1:
message = f"Expected exactly one LICENSE file in cffi distribution, got {len(license_files)}"
raise RuntimeError(message)
license_file = license_files[0]
shutil.copy2(license_file.locate(), self.local_cffi_license)
self.metadata.core.license_files.append(self.LICENSE_NAME)
def finalize(self, version: str, build_data: dict[str, Any], artifact: str) -> None: # noqa: ARG002
os.remove(self.local_cffi_license)
================================================
FILE: mkdocs.yml
================================================
site_name: coincurve
site_description: Cross-platform Python bindings for libsecp256k1
site_author: Ofek Lev
site_url: https://ofek.dev/coincurve/
repo_name: ofek/coincurve
repo_url: https://github.com/ofek/coincurve
edit_uri: blob/master/docs
copyright: 'Copyright © Ofek Lev 2017-present'
docs_dir: docs
site_dir: site
theme:
name: material
language: en
font:
text: Roboto
code: Roboto Mono
icon:
logo: material/circle-multiple
repo: fontawesome/brands/github-alt
favicon: assets/images/favicon.ico
palette:
- media: "(prefers-color-scheme: dark)"
scheme: slate
primary: teal
accent: teal
toggle:
icon: material/weather-night
name: Switch to light mode
- media: "(prefers-color-scheme: light)"
scheme: default
primary: teal
accent: teal
toggle:
icon: material/weather-sunny
name: Switch to dark mode
features:
- content.action.edit
- content.code.copy
- content.tabs.link
- content.tooltips
- navigation.expand
- navigation.footer
- navigation.instant
- navigation.sections
nav:
- About: index.md
- Install: install.md
- API Reference: api.md
- Benchmarks: benchmarks.md
- Meta:
- Users: users.md
- History: history.md
plugins:
# Built-in
search: {}
# Extra
glightbox: {}
minify:
minify_html: true
git-revision-date-localized:
type: date
strict: false
mkdocstrings:
default_handler: python
handlers:
python:
paths:
- src
options:
# Rendering
show_root_full_path: false
# Headings
show_root_heading: true
show_source: false
show_symbol_type_toc: true
# Docstrings
show_if_no_docstring: true
# Signatures/annotations
show_signature_annotations: true
signature_crossrefs: true
# Other
show_bases: false
import:
- https://docs.python.org/3/objects.inv
markdown_extensions:
# Built-in
- markdown.extensions.abbr:
- markdown.extensions.admonition:
- markdown.extensions.attr_list:
- markdown.extensions.footnotes:
- markdown.extensions.md_in_html:
- markdown.extensions.meta:
- markdown.extensions.tables:
- markdown.extensions.toc:
permalink: true
# Extra
- pymdownx.arithmatex:
- pymdownx.betterem:
smart_enable: all
- pymdownx.caret:
- pymdownx.critic:
- pymdownx.details:
- pymdownx.emoji:
# https://github.com/twitter/twemoji
# https://raw.githubusercontent.com/facelessuser/pymdown-extensions/master/pymdownx/twemoji_db.py
emoji_index: !!python/name:material.extensions.emoji.twemoji
emoji_generator: !!python/name:material.extensions.emoji.to_svg
- pymdownx.highlight:
guess_lang: false
linenums_style: pymdownx-inline
use_pygments: true
- pymdownx.inlinehilite:
- pymdownx.keys:
- pymdownx.magiclink:
repo_url_shortener: true
repo_url_shorthand: true
social_url_shortener: true
social_url_shorthand: true
normalize_issue_symbols: true
provider: github
user: ofek
repo: coincurve
- pymdownx.mark:
- pymdownx.progressbar:
- pymdownx.saneheaders:
- pymdownx.smartsymbols:
- pymdownx.snippets:
check_paths: true
base_path:
- docs/.snippets
auto_append:
- links.txt
- abbrs.txt
- pymdownx.superfences:
- pymdownx.tabbed:
alternate_style: true
slugify: !!python/object/apply:pymdownx.slugs.slugify
kwds:
case: lower
- pymdownx.tasklist:
custom_checkbox: true
- pymdownx.tilde:
extra:
social:
- icon: fontawesome/brands/github-alt
link: https://github.com/ofek
- icon: fontawesome/solid/blog
link: https://ofek.dev/words/
- icon: fontawesome/brands/x-twitter
link: https://x.com/Ofekmeister
- icon: fontawesome/brands/linkedin
link: https://www.linkedin.com/in/ofeklev/
extra_css:
- assets/css/custom.css
- https://cdn.jsdelivr.net/npm/firacode@6.2.0/distr/fira_code.css
================================================
FILE: pyproject.toml
================================================
[build-system]
build-backend = "hatchling.build"
requires = [
"hatchling>=1.27.0",
"cffi",
"scikit-build-core>=0.9.0",
"pkgconf; sys_platform == 'win32'",
]
[project]
name = "coincurve"
authors = [
{ name = "Ofek Lev", email = "oss@ofek.dev" },
]
description = "Safest and fastest Python library for secp256k1 elliptic curve operations"
keywords = [
"bitcoin",
"crypto",
"cryptocurrency",
"ecdh",
"ecdsa",
"elliptic curves",
"ethereum",
"libsecp256k1",
"secp256k1",
"schnorr",
]
readme = "README.md"
license = "MIT OR Apache-2.0"
license-files = [
"LICENSE-APACHE",
"LICENSE-MIT",
"NOTICE",
]
requires-python = ">=3.9"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Natural Language :: English",
"Operating System :: OS Independent",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Software Development :: Libraries",
"Topic :: Security :: Cryptography",
]
dynamic = ["version"]
[project.urls]
Homepage = "https://ofek.dev/coincurve/"
Sponsor = "https://github.com/sponsors/ofek"
History = "https://ofek.dev/coincurve/history/"
Tracker = "https://github.com/ofek/coincurve/issues"
Source = "https://github.com/ofek/coincurve"
# --- hatch ---
[tool.hatch.version]
path = "src/coincurve/__init__.py"
[tool.hatch.build.targets.wheel.hooks.custom]
[tool.hatch.build.targets.wheel.hooks.scikit-build]
experimental = true
build.verbose = true
cmake.build-type = "Release"
cmake.source-dir = "."
wheel.py-api = ""
wheel.packages = []
# --- scikit-build-core ---
[tool.scikit-build.cmake.define]
CMAKE_BUILD_TYPE = "Release"
# Coincurve build options - This may be better extracted from the ENV by the CMake directly?
PROJECT_IGNORE_SYSTEM_LIB = { env = "COINCURVE_IGNORE_SYSTEM_LIB", default = "ON" }
PROJECT_CROSS_COMPILE_TARGET = { env = "COINCURVE_CROSS_HOST", default = "" }
# Vendored library: SECP256K1
VENDORED_LIBRARY_CMAKE_TARGET = "secp256k1"
VENDORED_LIBRARY_PKG_CONFIG = "libsecp256k1"
VENDORED_LIBRARY_PKG_CONFIG_VERSION = "0.6.0"
VENDORED_UPSTREAM_URL = "https://github.com/bitcoin-core/secp256k1/archive/"
VENDORED_UPSTREAM_REF = { env = "COINCURVE_UPSTREAM_REF", default = "0cdc758a56360bf58a851fe91085a327ec97685a" }
VENDORED_UPSTREAM_SHA = { env = "COINCURVE_UPSTREAM_SHA", default = "385c115a21ee1ff31d0b0320acc2b278c92f7bde971f510566ad481a38835be0" }
# SECP256K1 library specific build options
# `VENDORED_OPTION` is reserved prefix for vendored library build options
VENDORED_LIBRARY_OPTION_PREFIX = "SECP256K1"
VENDORED_OPTION_DISABLE_SHARED = { env = "COINCURVE_SECP256K1_STATIC", default = "ON" }
VENDORED_OPTION_BUILD_BENCHMARK = "OFF"
VENDORED_OPTION_BUILD_TESTS = "OFF"
VENDORED_OPTION_BUILD_CTIME_TESTS = "OFF"
VENDORED_OPTION_BUILD_EXHAUSTIVE_TESTS = "OFF"
VENDORED_OPTION_BUILD_EXAMPLES = "OFF"
VENDORED_OPTION_ENABLE_MODULE_ECDH = "ON"
VENDORED_OPTION_ENABLE_MODULE_RECOVERY = "ON"
VENDORED_OPTION_ENABLE_MODULE_SCHNORRSIG = "ON"
VENDORED_OPTION_ENABLE_MODULE_EXTRAKEYS = "ON"
VENDORED_OPTION_EXPERIMENTAL = "ON"
# Vendored library build options (cmake, compiler, linker, etc.)
# VENDORED_CMAKE is reserved prefix for vendored library cmake options
# VENDORED_CMAKE_<STATIC|SHARED>_<CMAKE_OPTION> = <VALUE>
VENDORED_LIBRARY_STATIC_BUILD = { env = "COINCURVE_SECP256K1_STATIC", default = "ON" }
# --- Coverage ---
[tool.coverage.run]
source_pkgs = ["coincurve", "tests"]
branch = true
parallel = true
omit = [
"src/coincurve/__init__.py",
"tests/test_bench.py",
]
[tool.coverage.report]
exclude_lines =[
"no cov",
# Ignore missing debug-only code
"def __repr__",
"if self\\.debug",
# Ignore non-runnable code
"if __name__ == .__main__.:",
]
[tool.coverage.paths]
coincurve = ["src/coincurve", "*/coincurve/src/coincurve"]
tests = ["tests", "*/coincurve/tests"]
# --- Pytest ---
[tool.pytest.ini_options]
addopts = [
"--import-mode=importlib",
]
# --- Mypy ---
[tool.mypy]
disallow_untyped_defs = false
follow_imports = "normal"
ignore_missing_imports = true
pretty = true
show_column_numbers = true
warn_no_return = false
warn_unused_ignores = true
================================================
FILE: ruff.toml
================================================
extend = "ruff_defaults.toml"
[format]
preview = true
[lint]
preview = true
[lint.extend-per-file-ignores]
# Implicit namespace packages
"cm_library_c_binding/build.py" = ["INP001"]
"cm_library_cffi_headers/compose_cffi_headers.py" = ["INP001"]
================================================
FILE: ruff_defaults.toml
================================================
line-length = 120
[format]
docstring-code-format = true
docstring-code-line-length = 80
[lint]
select = [
"A001",
"A002",
"A003",
"ARG001",
"ARG002",
"ARG003",
"ARG004",
"ARG005",
"ASYNC100",
"ASYNC105",
"ASYNC109",
"ASYNC110",
"ASYNC115",
"B002",
"B003",
"B004",
"B005",
"B006",
"B007",
"B008",
"B009",
"B010",
"B011",
"B012",
"B013",
"B014",
"B015",
"B016",
"B017",
"B018",
"B019",
"B020",
"B021",
"B022",
"B023",
"B024",
"B025",
"B026",
"B028",
"B029",
"B030",
"B031",
"B032",
"B033",
"B034",
"B035",
"B904",
"B905",
"B909",
"BLE001",
"C400",
"C401",
"C402",
"C403",
"C404",
"C405",
"C406",
"C408",
"C409",
"C410",
"C411",
"C413",
"C414",
"C415",
"C416",
"C417",
"C418",
"C419",
"C420",
"COM818",
"DTZ001",
"DTZ002",
"DTZ003",
"DTZ004",
"DTZ005",
"DTZ006",
"DTZ007",
"DTZ011",
"DTZ012",
"E101",
"E112",
"E113",
"E115",
"E116",
"E201",
"E202",
"E203",
"E211",
"E221",
"E222",
"E223",
"E224",
"E225",
"E226",
"E227",
"E228",
"E231",
"E241",
"E242",
"E251",
"E252",
"E261",
"E262",
"E265",
"E266",
"E271",
"E272",
"E273",
"E274",
"E275",
"E401",
"E402",
"E502",
"E701",
"E702",
"E703",
"E711",
"E712",
"E713",
"E714",
"E721",
"E722",
"E731",
"E741",
"E742",
"E743",
"E902",
"EM101",
"EM102",
"EM103",
"EXE001",
"EXE002",
"EXE003",
"EXE004",
"EXE005",
"F401",
"F402",
"F403",
"F404",
"F405",
"F406",
"F407",
"F501",
"F502",
"F503",
"F504",
"F505",
"F506",
"F507",
"F508",
"F509",
"F521",
"F522",
"F523",
"F524",
"F525",
"F541",
"F601",
"F602",
"F621",
"F622",
"F631",
"F632",
"F633",
"F634",
"F701",
"F702",
"F704",
"F706",
"F707",
"F722",
"F811",
"F821",
"F822",
"F823",
"F841",
"F842",
"F901",
"FA100",
"FA102",
"FBT001",
"FBT002",
"FLY002",
"FURB105",
"FURB110",
"FURB113",
"FURB116",
"FURB118",
"FURB129",
"FURB131",
"FURB132",
"FURB136",
"FURB142",
"FURB145",
"FURB148",
"FURB152",
"FURB157",
"FURB161",
"FURB163",
"FURB164",
"FURB166",
"FURB167",
"FURB168",
"FURB169",
"FURB171",
"FURB177",
"FURB180",
"FURB181",
"FURB187",
"FURB192",
"G001",
"G002",
"G003",
"G004",
"G010",
"G101",
"G201",
"G202",
"I001",
"I002",
"ICN001",
"ICN002",
"ICN003",
"INP001",
"INT001",
"INT002",
"INT003",
"ISC003",
"LOG001",
"LOG002",
"LOG007",
"LOG009",
"N801",
"N802",
"N803",
"N804",
"N805",
"N806",
"N807",
"N811",
"N812",
"N813",
"N814",
"N815",
"N816",
"N817",
"N818",
"N999",
"PERF101",
"PERF102",
"PERF401",
"PERF402",
"PERF403",
"PGH005",
"PIE790",
"PIE794",
"PIE796",
"PIE800",
"PIE804",
"PIE807",
"PIE808",
"PIE810",
"PLC0105",
"PLC0131",
"PLC0132",
"PLC0205",
"PLC0208",
"PLC0414",
"PLC0415",
"PLC1901",
"PLC2401",
"PLC2403",
"PLC2701",
"PLC2801",
"PLC3002",
"PLE0100",
"PLE0101",
"PLE0115",
"PLE0116",
"PLE0117",
"PLE0118",
"PLE0237",
"PLE0241",
"PLE0302",
"PLE0303",
"PLE0304",
"PLE0305",
"PLE0307",
"PLE0308",
"PLE0309",
"PLE0604",
"PLE0605",
"PLE0643",
"PLE0704",
"PLE1132",
"PLE1141",
"PLE1142",
"PLE1205",
"PLE1206",
"PLE1300",
"PLE1307",
"PLE1310",
"PLE1507",
"PLE1519",
"PLE1520",
"PLE1700",
"PLE2502",
"PLE2510",
"PLE2512",
"PLE2513",
"PLE2514",
"PLE2515",
"PLE4703",
"PLR0124",
"PLR0133",
"PLR0202",
"PLR0203",
"PLR0206",
"PLR0402",
"PLR1704",
"PLR1711",
"PLR1714",
"PLR1722",
"PLR1730",
"PLR1733",
"PLR1736",
"PLR2004",
"PLR2044",
"PLR5501",
"PLR6104",
"PLR6201",
"PLR6301",
"PLW0108",
"PLW0120",
"PLW0127",
"PLW0128",
"PLW0129",
"PLW0131",
"PLW0133",
"PLW0177",
"PLW0211",
"PLW0245",
"PLW0406",
"PLW0602",
"PLW0603",
"PLW0604",
"PLW0642",
"PLW0711",
"PLW1501",
"PLW1508",
"PLW1509",
"PLW1510",
"PLW1514",
"PLW1641",
"PLW2101",
"PLW2901",
"PLW3201",
"PLW3301",
"PT001",
"PT002",
"PT003",
"PT006",
"PT007",
"PT008",
"PT009",
"PT010",
"PT011",
"PT012",
"PT013",
"PT014",
"PT015",
"PT016",
"PT017",
"PT018",
"PT019",
"PT020",
"PT021",
"PT022",
"PT023",
"PT024",
"PT025",
"PT026",
"PT027",
"PYI001",
"PYI002",
"PYI003",
"PYI004",
"PYI005",
"PYI006",
"PYI007",
"PYI008",
"PYI009",
"PYI010",
"PYI011",
"PYI012",
"PYI013",
"PYI014",
"PYI015",
"PYI016",
"PYI017",
"PYI018",
"PYI019",
"PYI020",
"PYI021",
"PYI024",
"PYI025",
"PYI026",
"PYI029",
"PYI030",
"PYI032",
"PYI033",
"PYI034",
"PYI035",
"PYI036",
"PYI041",
"PYI042",
"PYI043",
"PYI044",
"PYI045",
"PYI046",
"PYI047",
"PYI048",
"PYI049",
"PYI050",
"PYI051",
"PYI052",
"PYI053",
"PYI054",
"PYI055",
"PYI056",
"PYI058",
"PYI059",
"PYI062",
"RET503",
"RET504",
"RET505",
"RET506",
"RET507",
"RET508",
"RSE102",
"RUF001",
"RUF002",
"RUF003",
"RUF005",
"RUF006",
"RUF007",
"RUF008",
"RUF009",
"RUF010",
"RUF012",
"RUF013",
"RUF015",
"RUF016",
"RUF017",
"RUF018",
"RUF019",
"RUF020",
"RUF021",
"RUF022",
"RUF023",
"RUF024",
"RUF026",
"RUF027",
"RUF028",
"RUF029",
"RUF100",
"RUF101",
"S101",
"S102",
"S103",
"S104",
"S105",
"S106",
"S107",
"S108",
"S110",
"S112",
"S113",
"S201",
"S202",
"S301",
"S302",
"S303",
"S304",
"S305",
"S306",
"S307",
"S308",
"S310",
"S311",
"S312",
"S313",
"S314",
"S315",
"S316",
"S317",
"S318",
"S319",
"S321",
"S323",
"S324",
"S401",
"S402",
"S403",
"S405",
"S406",
"S407",
"S408",
"S409",
"S411",
"S412",
"S413",
"S415",
"S501",
"S502",
"S503",
"S504",
"S505",
"S506",
"S507",
"S508",
"S509",
"S601",
"S602",
"S604",
"S605",
"S606",
"S607",
"S608",
"S609",
"S610",
"S611",
"S612",
"S701",
"S702",
"SIM101",
"SIM102",
"SIM103",
"SIM105",
"SIM107",
"SIM108",
"SIM109",
"SIM110",
"SIM112",
"SIM113",
"SIM114",
"SIM115",
"SIM116",
"SIM117",
"SIM118",
"SIM201",
"SIM202",
"SIM208",
"SIM210",
"SIM211",
"SIM212",
"SIM220",
"SIM221",
"SIM222",
"SIM223",
"SIM300",
"SIM910",
"SIM911",
"SLF001",
"SLOT000",
"SLOT001",
"SLOT002",
"T100",
"T201",
"T203",
"TC001",
"TC002",
"TC003",
"TC004",
"TC005",
"TC010",
"TD004",
"TD005",
"TD006",
"TD007",
"TID251",
"TID252",
"TID253",
"TRY002",
"TRY003",
"TRY004",
"TRY201",
"TRY203",
"TRY300",
"TRY301",
"TRY400",
"TRY401",
"UP001",
"UP003",
"UP004",
"UP005",
"UP006",
"UP007",
"UP008",
"UP009",
"UP010",
"UP011",
"UP012",
"UP013",
"UP014",
"UP015",
"UP017",
"UP018",
"UP019",
"UP020",
"UP021",
"UP022",
"UP023",
"UP024",
"UP025",
"UP026",
"UP028",
"UP029",
"UP030",
"UP031",
"UP032",
"UP033",
"UP034",
"UP035",
"UP036",
"UP037",
"UP039",
"UP040",
"UP041",
"UP042",
"W291",
"W292",
"W293",
"W391",
"W505",
"W605",
"YTT101",
"YTT102",
"YTT103",
"YTT201",
"YTT202",
"YTT203",
"YTT204",
"YTT301",
"YTT302",
"YTT303",
]
[lint.per-file-ignores]
"**/scripts/*" = [
"INP001",
"T201",
]
"**/tests/**/*" = [
"PLC1901",
"PLR2004",
"PLR6301",
"S",
"TID252",
]
[lint.flake8-tidy-imports]
ban-relative-imports = "all"
[lint.isort]
known-first-party = ["coincurve"]
[lint.flake8-pytest-style]
fixture-parentheses = false
mark-parentheses = false
================================================
FILE: scripts/README.md
================================================
# Scripts
-----
This directory contains scripts that are used to develop the project.
================================================
FILE: scripts/bench.py
================================================
# /// script
# dependencies = [
# "coincurve",
# "fastecdsa==3.0.1; sys_platform != 'win32'",
# "rich",
# ]
# [tool.uv.sources]
# coincurve = { path = ".." }
# ///
import os
import sys
from abc import ABC, abstractmethod
from decimal import Decimal
from textwrap import dedent
from time import perf_counter_ns
from timeit import Timer
from rich.live import Live
from rich.table import Table
MESSAGE = os.urandom(8192).hex()
class BenchmarkSpec:
__slots__ = ("setup", "statement")
def __init__(self, setup: str, statement: str):
self.setup = dedent(setup[1:])
self.statement = dedent(statement[1:])
class Benchmark(ABC):
@staticmethod
@abstractmethod
def name() -> str:
pass
@staticmethod
@abstractmethod
def generate_key_pair() -> BenchmarkSpec:
pass
@staticmethod
@abstractmethod
def sign() -> BenchmarkSpec:
pass
@staticmethod
@abstractmethod
def verify() -> BenchmarkSpec:
pass
@staticmethod
@abstractmethod
def key_export() -> BenchmarkSpec:
pass
@staticmethod
@abstractmethod
def key_import() -> BenchmarkSpec:
pass
class CoincurveBenchmark(Benchmark):
@staticmethod
def name() -> str:
return "coincurve"
@staticmethod
def generate_key_pair() -> BenchmarkSpec:
return BenchmarkSpec(
"""
from coincurve import PrivateKey
""",
"""
PrivateKey()
""",
)
@staticmethod
def sign() -> BenchmarkSpec:
return BenchmarkSpec(
f"""
from coincurve import PrivateKey
message = {MESSAGE!r}.encode()
private_key = PrivateKey()
""",
"""
private_key.sign(message)
""",
)
@staticmethod
def verify() -> BenchmarkSpec:
return BenchmarkSpec(
f"""
from coincurve import PrivateKey, verify_signature
message = {MESSAGE!r}.encode()
private_key = PrivateKey()
signature = private_key.sign(message)
public_key = private_key.public_key.format(compressed=False)
""",
"""
assert verify_signature(signature, message, public_key)
""",
)
@staticmethod
def key_export() -> BenchmarkSpec:
return BenchmarkSpec(
"""
from coincurve import PrivateKey
private_key = PrivateKey()
""",
"""
private_key.to_pem()
""",
)
@staticmethod
def key_import() -> BenchmarkSpec:
return BenchmarkSpec(
"""
from coincurve import PrivateKey
private_key = PrivateKey()
private_key_pem = private_key.to_pem()
""",
"""
PrivateKey.from_pem(private_key_pem)
""",
)
class FastecdsaBenchmark(Benchmark):
@staticmethod
def name() -> str:
return "fastecdsa"
@staticmethod
def generate_key_pair() -> BenchmarkSpec:
return BenchmarkSpec(
"""
from fastecdsa import curve, keys
""",
"""
keys.gen_keypair(curve.secp256k1)
""",
)
@staticmethod
def sign() -> BenchmarkSpec:
return BenchmarkSpec(
f"""
from fastecdsa import curve, ecdsa, keys
message = {MESSAGE!r}
private_key, _ = keys.gen_keypair(curve.secp256k1)
""",
"""
r, s = ecdsa.sign(message, private_key, curve=curve.secp256k1)
""",
)
@staticmethod
def verify() -> BenchmarkSpec:
return BenchmarkSpec(
f"""
from fastecdsa import curve, ecdsa, keys
message = {MESSAGE!r}
private_key, public_key = keys.gen_keypair(curve.secp256k1)
r, s = ecdsa.sign(message, private_key, curve=curve.secp256k1)
""",
"""
assert ecdsa.verify((r, s), message, public_key, curve=curve.secp256k1)
""",
)
@staticmethod
def key_export() -> BenchmarkSpec:
return BenchmarkSpec(
"""
from fastecdsa import curve, keys
from fastecdsa.encoding.pem import PEMEncoder
private_key, _ = keys.gen_keypair(curve.secp256k1)
encoder = PEMEncoder()
""",
"""
encoder.encode_private_key(private_key, curve=curve.secp256k1)
""",
)
@staticmethod
def key_import() -> BenchmarkSpec:
return BenchmarkSpec(
"""
from fastecdsa import curve, keys
from fastecdsa.encoding.pem import PEMEncoder
private_key, _ = keys.gen_keypair(curve.secp256k1)
encoder = PEMEncoder()
private_key_pem = encoder.encode_private_key(private_key, curve=curve.secp256k1)
""",
"""
encoder.decode_private_key(private_key_pem)
""",
)
def generate_table(rows: list[list[str]]):
table = Table()
table.add_column("Library")
table.add_column("Key generation")
table.add_column("Signing")
table.add_column("Verification")
table.add_column("Key export")
table.add_column("Key import")
for row in rows:
table.add_row(*row)
return table
def main():
print(sys.version)
rows = []
table = generate_table(rows)
with Live(table, auto_refresh=False) as live:
for benchmark in [CoincurveBenchmark, FastecdsaBenchmark]:
row = [benchmark.name()]
rows.append(row)
live.update(generate_table(rows), refresh=True)
for method in [
benchmark.generate_key_pair,
benchmark.sign,
benchmark.verify,
benchmark.key_export,
benchmark.key_import,
]:
spec = method()
timer = Timer(stmt=spec.statement, setup=spec.setup, timer=perf_counter_ns)
try:
loops, _ = timer.autorange()
times = timer.repeat(number=loops, repeat=1000)
except Exception as e: # noqa: BLE001
row.append(str(e))
live.update(generate_table(rows), refresh=True)
continue
best = Decimal(min(times))
# Convert nanoseconds to microseconds and round to 1 decimal place
best /= 1_000
best = best.quantize(Decimal("0.1"))
row.append(str(best))
live.update(generate_table(rows), refresh=True)
if __name__ == "__main__":
main()
================================================
FILE: src/coincurve/__init__.py
================================================
from coincurve.context import GLOBAL_CONTEXT, Context
from coincurve.keys import PrivateKey, PublicKey, PublicKeyXOnly
from coincurve.utils import verify_signature
__version__ = "21.0.0"
__all__ = [
"GLOBAL_CONTEXT",
"Context",
"PrivateKey",
"PublicKey",
"PublicKeyXOnly",
"verify_signature",
]
================================================
FILE: src/coincurve/context.py
================================================
from __future__ import annotations
from os import urandom
from threading import Lock
from coincurve._libsecp256k1 import ffi, lib
from coincurve.flags import CONTEXT_FLAGS, CONTEXT_NONE
class Context:
def __init__(self, seed: bytes | None = None, flag=CONTEXT_NONE, name: str = ""):
if flag not in CONTEXT_FLAGS:
msg = f"{flag} is an invalid context flag."
raise ValueError(msg)
self._lock = Lock()
self.ctx = ffi.gc(lib.secp256k1_context_create(flag), lib.secp256k1_context_destroy)
self.reseed(seed)
self.name = name
def reseed(self, seed: bytes | None = None):
"""
Protects against certain possible future side-channel timing attacks.
"""
with self._lock:
seed = urandom(32) if not seed or len(seed) != 32 else seed # noqa: PLR2004
res = lib.secp256k1_context_randomize(self.ctx, ffi.new("unsigned char [32]", seed))
if not res:
msg = "secp256k1_context_randomize"
raise ValueError(msg)
def __repr__(self):
return self.name or super().__repr__()
GLOBAL_CONTEXT = Context(name="GLOBAL_CONTEXT")
================================================
FILE: src/coincurve/der.py
================================================
"""
Minimal, dependency-free ASN.1/DER encoder & decoder for secp256k1 EC private keys.
This module implements just enough DER encoding/decoding to support:
1. Outputting a DER-encoded PKCS#8 EC private key (with an embedded ECPrivateKey per RFC 5915)
2. Reading such a DER-encoded EC private key
Only the following ASN.1 types are supported:
- INTEGER
- BIT STRING
- OCTET STRING
- OBJECT IDENTIFIER
- SEQUENCE
- Context-specific EXPLICIT tags (for the optional public key)
The expected DER structure is as follows:
PrivateKeyInfo ::= SEQUENCE {
version INTEGER, -- must be 0
privateKeyAlgorithm SEQUENCE {
algorithm OBJECT IDENTIFIER, -- id-ecPublicKey (1.2.840.10045.2.1)
parameters OBJECT IDENTIFIER -- secp256k1 (1.3.132.0.10)
},
privateKey OCTET STRING -- DER encoding of ECPrivateKey
}
ECPrivateKey ::= SEQUENCE {
version INTEGER, -- must be 1
privateKey OCTET STRING, -- the secret bytes
publicKey [1] EXPLICIT BIT STRING OPTIONAL -- uncompressed public key
}
"""
from __future__ import annotations
from coincurve.utils import int_to_bytes
# ASN.1 DER tag bytes
INTEGER_TAG = 0x02
BIT_STRING_TAG = 0x03
OCTET_STRING_TAG = 0x04
OBJECT_IDENTIFIER_TAG = 0x06
SEQUENCE_TAG = 0x30
# OIDs
EC_PUBKEY_OID = bytes([0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01]) # 1.2.840.10045.2.1 (ecPublicKey)
SECP256K1_OID = bytes([0x2B, 0x81, 0x04, 0x00, 0x0A]) # 1.3.132.0.10 (secp256k1)
# Pre-computed structures
VERSION_INTEGER_ZERO = bytes([INTEGER_TAG, 0x01, 0x00]) # INTEGER 0
VERSION_INTEGER_ONE = bytes([INTEGER_TAG, 0x01, 0x01]) # INTEGER 1
EC_ALGORITHM_IDENTIFIER = bytes([
SEQUENCE_TAG,
16,
OBJECT_IDENTIFIER_TAG,
len(EC_PUBKEY_OID),
*EC_PUBKEY_OID,
OBJECT_IDENTIFIER_TAG,
len(SECP256K1_OID),
*SECP256K1_OID,
])
def encode_length(length: int) -> bytes:
"""Encode a length in DER format."""
# Short form
if length < 128: # noqa: PLR2004
return bytes([length])
# Long form
length_bytes = int_to_bytes(length)
return bytes([0x80 | len(length_bytes)]) + length_bytes
def encode_octet_string(value: bytes) -> bytes:
"""Encode an OCTET STRING in DER format."""
length_bytes = encode_length(len(value))
length_bytes_len = len(length_bytes)
result = bytearray(1 + length_bytes_len + len(value))
result[0] = OCTET_STRING_TAG
result[1 : 1 + length_bytes_len] = length_bytes
result[1 + length_bytes_len :] = value
return bytes(result)
def encode_bit_string(value: bytes, unused_bits: int = 0) -> bytes:
"""Encode a BIT STRING in DER format."""
length_bytes = encode_length(len(value) + 1)
length_bytes_len = len(length_bytes)
result = bytearray(1 + length_bytes_len + 1 + len(value))
result[0] = BIT_STRING_TAG
result[1 : 1 + length_bytes_len] = length_bytes
result[1 + length_bytes_len] = unused_bits
result[1 + length_bytes_len + 1 :] = value
return bytes(result)
def encode_der(private_key: bytes, public_key: bytes | None = None) -> bytes:
"""
Encode an EC private key in DER format (PKCS#8/RFC 5208).
Optimized for secp256k1 keys.
Parameters:
private_key: The private key as bytes (32 bytes for secp256k1)
public_key: The public key as bytes (65 bytes uncompressed for secp256k1, starting with 0x04)
Returns:
The DER-encoded private key
"""
# EC private key contains version(1) + octet string + optional pubkey
ec_key_buffer = bytearray(VERSION_INTEGER_ONE)
# Add private key as octet string
private_key_os = encode_octet_string(private_key)
ec_key_buffer.extend(private_key_os)
# Add public key if provided (optional)
if public_key is not None:
public_key_bs = encode_bit_string(public_key)
pubkey_len = len(public_key_bs)
ec_key_buffer.append(0xA1) # context-specific [1] constructed
ec_key_buffer.extend(encode_length(pubkey_len))
ec_key_buffer.extend(public_key_bs)
# Wrap EC private key in sequence
ec_key_seq = bytearray([SEQUENCE_TAG])
ec_key_seq.extend(encode_length(len(ec_key_buffer)))
ec_key_seq.extend(ec_key_buffer)
# Wrap in octet string for outer structure
ec_key_os = encode_octet_string(ec_key_seq)
# Build the outer PKCS#8 structure
result = bytearray([SEQUENCE_TAG])
# Calculate total length: version(3) + alg_id(18) + octet_string(len)
outer_len = 3 + len(EC_ALGORITHM_IDENTIFIER) + len(ec_key_os)
result.extend(encode_length(outer_len))
# Version 0
result.extend(VERSION_INTEGER_ZERO)
# Algorithm identifier (pre-computed)
result.extend(EC_ALGORITHM_IDENTIFIER)
# EC key wrapped in octet string
result.extend(ec_key_os)
return bytes(result)
def decode_length(data: bytes, offset: int) -> tuple[int, int]:
"""
Decode a DER length field.
Parameters:
data: The DER-encoded data
offset: The current offset in the data
Returns:
Tuple of (length, new_offset)
"""
length_byte = data[offset]
offset += 1
# Short form
if length_byte < 128: # noqa: PLR2004
return length_byte, offset
# Long form
num_length_bytes = length_byte & 0x7F
length = 0
for _ in range(num_length_bytes):
length = (length << 8) | data[offset]
offset += 1
return length, offset
def decode_der(der_data: bytes) -> bytes:
"""
Decode a DER-encoded EC private key to extract the private key secret.
Optimized for secp256k1 keys.
Parameters:
der_data: The DER-encoded private key in PKCS#8 format
Returns:
The private key secret as bytes
"""
# Quick validation for performance
if len(der_data) < 34 or der_data[0] != SEQUENCE_TAG: # noqa: PLR2004
msg = "Invalid DER: not a valid PKCS#8 structure"
raise ValueError(msg)
# Skip outer sequence tag and length
offset = 1
_, offset = decode_length(der_data, offset)
# Skip version INTEGER (should be 0)
if der_data[offset] != INTEGER_TAG:
msg = "Invalid DER: expected INTEGER tag for version"
raise ValueError(msg)
offset += 1
version_len, offset = decode_length(der_data, offset)
offset += version_len # Skip version value
# Validate algorithm identifier is for EC
if der_data[offset] != SEQUENCE_TAG:
msg = "Invalid DER: expected SEQUENCE tag for algorithm"
raise ValueError(msg)
offset += 1
alg_len, offset = decode_length(der_data, offset)
alg_end = offset + alg_len # Store the end position of algorithm identifier
# Check if first OID is EC
if der_data[offset] != OBJECT_IDENTIFIER_TAG:
msg = "Invalid DER: expected OBJECT IDENTIFIER tag"
raise ValueError(msg)
offset += 1
oid_len, offset = decode_length(der_data, offset)
algorithm_oid = der_data[offset : offset + oid_len]
# Check if it's an EC key
if oid_len != len(EC_PUBKEY_OID) or algorithm_oid != EC_PUBKEY_OID:
msg = "Not an EC private key"
raise ValueError(msg)
# Skip to the end of algorithm identifier section
offset = alg_end
# Extract private key octet string
if der_data[offset] != OCTET_STRING_TAG:
msg = "Invalid DER: expected OCTET STRING for private key"
raise ValueError(msg)
offset += 1
priv_len, offset = decode_length(der_data, offset)
# Parse EC private key structure
ec_data = der_data[offset : offset + priv_len]
# Verify EC structure starts with sequence
if len(ec_data) < 2 or ec_data[0] != SEQUENCE_TAG: # noqa: PLR2004
msg = "Invalid EC key format: missing sequence"
raise ValueError(msg)
# Skip sequence tag and length
ec_offset = 1
_, ec_offset = decode_length(ec_data, ec_offset)
# Skip version INTEGER (should be 1)
if ec_data[ec_offset] != INTEGER_TAG:
msg = "Invalid EC key format: missing version"
raise ValueError(msg)
ec_offset += 1
ec_ver_len, ec_offset = decode_length(ec_data, ec_offset)
ec_offset += ec_ver_len # Skip version value
# Get private key octet string
if ec_data[ec_offset] != OCTET_STRING_TAG:
msg = "Invalid DER: expected OCTET STRING for EC private key"
raise ValueError(msg)
ec_offset += 1
key_len, ec_offset = decode_length(ec_data, ec_offset)
# Extract private key
return ec_data[ec_offset : ec_offset + key_len]
================================================
FILE: src/coincurve/ecdsa.py
================================================
from __future__ import annotations
from typing import TYPE_CHECKING
from coincurve._libsecp256k1 import ffi, lib
from coincurve.context import GLOBAL_CONTEXT, Context
from coincurve.utils import bytes_to_int, int_to_bytes, sha256
if TYPE_CHECKING:
from coincurve.types import Hasher
MAX_SIG_LENGTH = 72
CDATA_SIG_LENGTH = 64
def cdata_to_der(cdata, context: Context = GLOBAL_CONTEXT) -> bytes:
der = ffi.new("unsigned char[72]")
der_length = ffi.new("size_t *", MAX_SIG_LENGTH)
lib.secp256k1_ecdsa_signature_serialize_der(context.ctx, der, der_length, cdata)
return bytes(ffi.buffer(der, der_length[0]))
def der_to_cdata(der: bytes, context: Context = GLOBAL_CONTEXT):
cdata = ffi.new("secp256k1_ecdsa_signature *")
parsed = lib.secp256k1_ecdsa_signature_parse_der(context.ctx, cdata, der, len(der))
if not parsed:
msg = "The DER-encoded signature could not be parsed."
raise ValueError(msg)
return cdata
def recover(message: bytes, recover_sig, hasher: Hasher = sha256, context: Context = GLOBAL_CONTEXT):
msg_hash = hasher(message) if hasher is not None else message
if len(msg_hash) != 32: # noqa: PLR2004
msg = "Message hash must be 32 bytes long."
raise ValueError(msg)
pubkey = ffi.new("secp256k1_pubkey *")
recovered = lib.secp256k1_ecdsa_recover(context.ctx, pubkey, recover_sig, msg_hash)
if recovered:
return pubkey
msg = "failed to recover ECDSA public key"
raise ValueError(msg)
def serialize_recoverable(recover_sig, context: Context = GLOBAL_CONTEXT) -> bytes:
output = ffi.new("unsigned char[64]")
recid = ffi.new("int *")
lib.secp256k1_ecdsa_recoverable_signature_serialize_compact(context.ctx, output, recid, recover_sig)
return bytes(ffi.buffer(output, CDATA_SIG_LENGTH)) + int_to_bytes(recid[0])
def deserialize_recoverable(serialized: bytes, context: Context = GLOBAL_CONTEXT):
if len(serialized) != 65: # noqa: PLR2004
msg = "Serialized signature must be 65 bytes long."
raise ValueError(msg)
ser_sig, rec_id = serialized[:64], bytes_to_int(serialized[64:])
if not 0 <= rec_id <= 3: # noqa: PLR2004
msg = "Invalid recovery id."
raise ValueError(msg)
recover_sig = ffi.new("secp256k1_ecdsa_recoverable_signature *")
parsed = lib.secp256k1_ecdsa_recoverable_signature_parse_compact(context.ctx, recover_sig, ser_sig, rec_id)
if not parsed:
msg = "Failed to parse recoverable signature."
raise ValueError(msg)
return recover_sig
"""
Warning:
The functions below may change and are not tested!
"""
def serialize_compact(raw_sig, context: Context = GLOBAL_CONTEXT): # no cov
output = ffi.new("unsigned char[64]")
res = lib.secp256k1_ecdsa_signature_serialize_compact(context.ctx, output, raw_sig)
if not res:
msg = "secp256k1_ecdsa_signature_serialize_compact"
raise ValueError(msg)
return bytes(ffi.buffer(output, CDATA_SIG_LENGTH))
def deserialize_compact(ser_sig: bytes, context: Context = GLOBAL_CONTEXT): # no cov
if len(ser_sig) != 64: # noqa: PLR2004
msg = "invalid signature length"
raise ValueError(msg)
raw_sig = ffi.new("secp256k1_ecdsa_signature *")
res = lib.secp256k1_ecdsa_signature_parse_compact(context.ctx, raw_sig, ser_sig)
if not res:
msg = "secp256k1_ecdsa_signature_parse_compact"
raise ValueError(msg)
return raw_sig
def signature_normalize(raw_sig, context: Context = GLOBAL_CONTEXT): # no cov
"""
Check and optionally convert a signature to a normalized lower-S form.
This function always return a tuple containing a boolean (True if
not previously normalized or False if signature was already
normalized), and the normalized signature.
"""
sigout = ffi.new("secp256k1_ecdsa_signature *")
res = lib.secp256k1_ecdsa_signature_normalize(context.ctx, sigout, raw_sig)
return not not res, sigout # noqa: SIM208
def recoverable_convert(recover_sig, context: Context = GLOBAL_CONTEXT): # no cov
normal_sig = ffi.new("secp256k1_ecdsa_signature *")
lib.secp256k1_ecdsa_recoverable_signature_convert(context.ctx, normal_sig, recover_sig)
return normal_sig
================================================
FILE: src/coincurve/flags.py
================================================
from __future__ import annotations
from coincurve._libsecp256k1 import lib
CONTEXT_NONE = lib.SECP256K1_CONTEXT_NONE
CONTEXT_FLAGS = {
CONTEXT_NONE,
}
EC_COMPRESSED = lib.SECP256K1_EC_COMPRESSED
EC_UNCOMPRESSED = lib.SECP256K1_EC_UNCOMPRESSED
# Additional flags available from libsecp256k1
# lib.SECP256K1_TAG_PUBKEY_EVEN
# lib.SECP256K1_TAG_PUBKEY_ODD
# lib.SECP256K1_TAG_PUBKEY_UNCOMPRESSED
# lib.SECP256K1_TAG_PUBKEY_HYBRID_EVEN
# lib.SECP256K1_TAG_PUBKEY_HYBRID_ODD
================================================
FILE: src/coincurve/keys.py
================================================
from __future__ import annotations
import os
from typing import TYPE_CHECKING
from coincurve._libsecp256k1 import ffi, lib
from coincurve.context import GLOBAL_CONTEXT, Context
from coincurve.der import decode_der, encode_der
from coincurve.ecdsa import cdata_to_der, der_to_cdata, deserialize_recoverable, recover, serialize_recoverable
from coincurve.flags import EC_COMPRESSED, EC_UNCOMPRESSED
from coincurve.utils import (
DEFAULT_NONCE,
bytes_to_int,
der_to_pem,
get_valid_secret,
hex_to_bytes,
int_to_bytes_padded,
pad_scalar,
pem_to_der,
sha256,
validate_secret,
)
if TYPE_CHECKING:
from coincurve.types import Hasher, Nonce
class PrivateKey:
def __init__(self, secret: bytes | None = None, context: Context = GLOBAL_CONTEXT):
"""
Initializes a private key.
Parameters:
secret: The secret used to initialize the private key.
If not provided, a new key will be generated.
context: The context to use.
"""
self.secret: bytes = validate_secret(secret) if secret is not None else get_valid_secret()
self.context = context
self.public_key: PublicKey = PublicKey.from_valid_secret(self.secret, self.context)
self.public_key_xonly: PublicKeyXOnly = PublicKeyXOnly.from_valid_secret(self.secret, self.context)
def sign(self, message: bytes, hasher: Hasher = sha256, custom_nonce: Nonce = DEFAULT_NONCE) -> bytes:
"""
Creates an ECDSA signature.
Parameters:
message: The message to sign.
hasher (collections.abc.Callable[[bytes], bytes] | None): The hash function to use, which must
return 32 bytes. By default, the `sha256` algorithm is used. If `None`, no hashing occurs.
custom_nonce (tuple[ffi.CData, ffi.CData]): Custom nonce data in the form `(nonce_function, input_data)`.
For more information, refer to the `libsecp256k1` documentation
[here](https://github.com/bitcoin-core/secp256k1/blob/v0.6.0/include/secp256k1.h#L637-L642).
Returns:
The ECDSA signature.
Raises:
ValueError: If the message hash was not 32 bytes long, the nonce generation
function failed, or the private key was invalid.
"""
msg_hash = hasher(message) if hasher is not None else message
if len(msg_hash) != 32: # noqa: PLR2004
msg = "Message hash must be 32 bytes long."
raise ValueError(msg)
signature = ffi.new("secp256k1_ecdsa_signature *")
nonce_fn, nonce_data = custom_nonce
signed = lib.secp256k1_ecdsa_sign(self.context.ctx, signature, msg_hash, self.secret, nonce_fn, nonce_data)
if not signed:
msg = "The nonce generation function failed, or the private key was invalid."
raise ValueError(msg)
return cdata_to_der(signature, self.context)
def sign_schnorr(self, message: bytes, aux_randomness: bytes = b"") -> bytes:
"""
Creates a Schnorr signature.
Parameters:
message: The message to sign.
aux_randomness: 32 bytes of fresh randomness, empty bytestring (auto-generated),
or None (no randomness).
Returns:
The Schnorr signature.
Raises:
ValueError: If the message was not 32 bytes long, the optional auxiliary
random data was not 32 bytes long, signing failed, or the signature was invalid.
"""
if len(message) != 32: # noqa: PLR2004
msg = "Message must be 32 bytes long."
raise ValueError(msg)
if aux_randomness == b"":
aux_randomness = os.urandom(32)
elif aux_randomness is None:
aux_randomness = ffi.NULL
elif len(aux_randomness) != 32: # noqa: PLR2004
msg = "Auxiliary random data must be 32 bytes long."
raise ValueError(msg)
keypair = ffi.new("secp256k1_keypair *")
res = lib.secp256k1_keypair_create(self.context.ctx, keypair, self.secret)
if not res:
msg = "Secret was invalid"
raise ValueError(msg)
signature = ffi.new("unsigned char[64]")
res = lib.secp256k1_schnorrsig_sign32(self.context.ctx, signature, message, keypair, aux_randomness)
if not res:
msg = "Signing failed"
raise ValueError(msg)
res = lib.secp256k1_schnorrsig_verify(
self.context.ctx, signature, message, len(message), self.public_key_xonly.public_key
)
if not res:
msg = "Invalid signature"
raise ValueError(msg)
return bytes(ffi.buffer(signature))
def sign_recoverable(self, message: bytes, hasher: Hasher = sha256, custom_nonce: Nonce = DEFAULT_NONCE) -> bytes:
"""
Creates a recoverable ECDSA signature.
Parameters:
message: The message to sign.
hasher (collections.abc.Callable[[bytes], bytes] | None): The hash function to use, which must
return 32 bytes. By default, the `sha256` algorithm is used. If `None`, no hashing occurs.
custom_nonce (tuple[ffi.CData, ffi.CData]): Custom nonce data in the form `(nonce_function, input_data)`.
For more information, refer to the `libsecp256k1` documentation
[here](https://github.com/bitcoin-core/secp256k1/blob/v0.6.0/include/secp256k1.h#L637-L642).
Returns:
The recoverable ECDSA signature.
Raises:
ValueError: If the message hash was not 32 bytes long, the nonce generation
function failed, or the private key was invalid.
"""
msg_hash = hasher(message) if hasher is not None else message
if len(msg_hash) != 32: # noqa: PLR2004
msg = "Message hash must be 32 bytes long."
raise ValueError(msg)
signature = ffi.new("secp256k1_ecdsa_recoverable_signature *")
nonce_fn, nonce_data = custom_nonce
signed = lib.secp256k1_ecdsa_sign_recoverable(
self.context.ctx, signature, msg_hash, self.secret, nonce_fn, nonce_data
)
if not signed:
msg = "The nonce generation function failed, or the private key was invalid."
raise ValueError(msg)
return serialize_recoverable(signature, self.context)
def ecdh(self, public_key: bytes) -> bytes:
"""
Computes an EC Diffie-Hellman secret in constant time.
!!! note
This prevents malleability by returning `sha256(compressed_public_key)` instead of the `x` coordinate
directly.
Parameters:
public_key: The formatted public key.
Returns:
The 32-byte shared secret.
Raises:
ValueError: If the public key could not be parsed or was invalid.
"""
secret = ffi.new("unsigned char [32]")
lib.secp256k1_ecdh(self.context.ctx, secret, PublicKey(public_key).public_key, self.secret, ffi.NULL, ffi.NULL)
return bytes(ffi.buffer(secret, 32))
def add(self, scalar: bytes, update: bool = False) -> PrivateKey: # noqa: FBT001, FBT002
"""
Adds a scalar to the private key.
Parameters:
scalar: The scalar with which to add.
update: Whether to update the private key in-place.
Returns:
The new private key, or the modified private key if `update` is `True`.
Raises:
ValueError: If the tweak was out of range or the resulting private key was invalid.
"""
scalar = pad_scalar(scalar)
secret = ffi.new("unsigned char [32]", self.secret)
success = lib.secp256k1_ec_seckey_tweak_add(self.context.ctx, secret, scalar)
if not success:
msg = "The tweak was out of range, or the resulting private key is invalid."
raise ValueError(msg)
secret = bytes(ffi.buffer(secret, 32))
if update:
self.secret = secret
self._update_public_key()
return self
return PrivateKey(secret, self.context)
def multiply(self, scalar: bytes, update: bool = False) -> PrivateKey: # noqa: FBT001, FBT002
"""
Multiplies the private key by a scalar.
Parameters:
scalar: The scalar with which to multiply.
update: Whether to update the private key in-place.
Returns:
The new private key, or the modified private key if `update` is `True`.
"""
scalar = validate_secret(scalar)
secret = ffi.new("unsigned char [32]", self.secret)
lib.secp256k1_ec_seckey_tweak_mul(self.context.ctx, secret, scalar)
secret = bytes(ffi.buffer(secret, 32))
if update:
self.secret = secret
self._update_public_key()
return self
return PrivateKey(secret, self.context)
def to_hex(self) -> str:
"""
Returns the private key encoded as a hex string.
"""
return self.secret.hex()
def to_int(self) -> int:
"""
Returns the private key as an integer.
"""
return bytes_to_int(self.secret)
def to_pem(self) -> bytes:
"""
Returns the private key encoded in PEM format.
"""
return der_to_pem(self.to_der())
def to_der(self) -> bytes:
"""
Returns the private key encoded in DER format.
"""
return encode_der(self.secret, self.public_key.format(compressed=False))
@classmethod
def from_hex(cls, hexed: str, context: Context = GLOBAL_CONTEXT) -> PrivateKey:
"""
Creates a private key from a hex string.
Parameters:
hexed: The private key encoded as a hex string.
context: The context to use.
Returns:
The private key.
"""
return PrivateKey(hex_to_bytes(hexed), context)
@classmethod
def from_int(cls, num: int, context: Context = GLOBAL_CONTEXT) -> PrivateKey:
"""
Creates a private key from an integer.
Parameters:
num: The private key as an integer.
context: The context to use.
Returns:
The private key.
"""
return PrivateKey(int_to_bytes_padded(num), context)
@classmethod
def from_pem(cls, pem: bytes, context: Context = GLOBAL_CONTEXT) -> PrivateKey:
"""
Creates a private key from PEM format.
Parameters:
pem: The private key encoded in PEM format.
context: The context to use.
Returns:
The private key.
"""
return PrivateKey(decode_der(pem_to_der(pem)), context)
@classmethod
def from_der(cls, der: bytes, context: Context = GLOBAL_CONTEXT) -> PrivateKey:
"""
Creates a private key from DER format.
Parameters:
der: The private key encoded in DER format.
context: The context to use.
Returns:
The private key.
"""
return PrivateKey(decode_der(der), context)
def _update_public_key(self):
created = lib.secp256k1_ec_pubkey_create(self.context.ctx, self.public_key.public_key, self.secret)
if not created:
msg = "Invalid secret."
raise ValueError(msg)
def __eq__(self, other) -> bool:
return self.secret == other.secret
def __hash__(self) -> int:
return hash(self.secret)
class PublicKey:
def __init__(self, data: bytes | ffi.CData, context: Context = GLOBAL_CONTEXT):
"""
Initializes a public key.
Parameters:
data (bytes): The formatted public key. This class supports parsing
compressed (33 bytes, header byte `0x02` or `0x03`),
uncompressed (65 bytes, header byte `0x04`), or
hybrid (65 bytes, header byte `0x06` or `0x07`) format public keys.
context: The context to use.
Raises:
ValueError: If the public key could not be parsed or was invalid.
"""
if not isinstance(data, bytes):
self.public_key = data
else:
public_key = ffi.new("secp256k1_pubkey *")
parsed = lib.secp256k1_ec_pubkey_parse(context.ctx, public_key, data, len(data))
if not parsed:
msg = "The public key could not be parsed or is invalid."
raise ValueError(msg)
self.public_key = public_key
self.context = context
@classmethod
def from_secret(cls, secret: bytes, context: Context = GLOBAL_CONTEXT) -> PublicKey:
"""
Derives a public key from a private key secret.
Parameters:
secret: The private key secret.
context: The context to use.
Returns:
The public key.
Raises:
ValueError: If an invalid secret was used.
"""
public_key = ffi.new("secp256k1_pubkey *")
created = lib.secp256k1_ec_pubkey_create(context.ctx, public_key, validate_secret(secret))
if not created: # no cov
msg = (
"Somehow an invalid secret was used. Please "
"submit this as an issue here: "
"https://github.com/ofek/coincurve/issues/new"
)
raise ValueError(msg)
return PublicKey(public_key, context)
@classmethod
def from_valid_secret(cls, secret: bytes, context: Context = GLOBAL_CONTEXT) -> PublicKey:
"""
Derives a public key from a valid private key secret, avoiding input checks.
Parameters:
secret: The private key secret.
context: The context to use.
Returns:
The public key.
Raises:
ValueError: If the secret was invalid.
"""
public_key = ffi.new("secp256k1_pubkey *")
created = lib.secp256k1_ec_pubkey_create(context.ctx, public_key, secret)
if not created:
msg = "Invalid secret."
raise ValueError(msg)
return PublicKey(public_key, context)
@classmethod
def from_point(cls, x: int, y: int, context: Context = GLOBAL_CONTEXT) -> PublicKey:
"""
Derives a public key from a coordinate point.
Parameters:
x: The x coordinate.
y: The y coordinate.
context: The context to use.
Returns:
The public key.
"""
return PublicKey(b"\x04" + int_to_bytes_padded(x) + int_to_bytes_padded(y), context)
@classmethod
def from_signature_and_message(
cls, signature: bytes, message: bytes, hasher: Hasher = sha256, context: Context = GLOBAL_CONTEXT
) -> PublicKey:
"""
Recovers an ECDSA public key from a recoverable signature.
Parameters:
signature: The recoverable ECDSA signature.
message: The message that was supposedly signed.
hasher (collections.abc.Callable[[bytes], bytes] | None): The hash function to use, which must
return 32 bytes. By default, the `sha256` algorithm is used. If `None`, no hashing occurs.
context: The context to use.
Returns:
The public key that signed the message.
Raises:
ValueError: If the message hash was not 32 bytes long or recovery of the
ECDSA public key failed.
"""
return PublicKey(
recover(message, deserialize_recoverable(signature, context=context), hasher=hasher, context=context)
)
@classmethod
def combine_keys(cls, public_keys: list[PublicKey], context: Context = GLOBAL_CONTEXT) -> PublicKey:
"""
Adds a number of public keys together.
Parameters:
public_keys: A sequence of public keys.
context: The context to use.
Returns:
The combined public key.
Raises:
ValueError: If the sum of the public keys was invalid.
"""
public_key = ffi.new("secp256k1_pubkey *")
combined = lib.secp256k1_ec_pubkey_combine(
context.ctx, public_key, [pk.public_key for pk in public_keys], len(public_keys)
)
if not combined:
msg = "The sum of the public keys is invalid."
raise ValueError(msg)
return PublicKey(public_key, context)
def format(self, compressed: bool = True) -> bytes: # noqa: FBT001, FBT002
"""
Formats the public key.
Parameters:
compressed: Whether to use the compressed format.
Returns:
The 33 byte formatted public key, or the 65 byte formatted public key
if `compressed` is `False`.
"""
length = 33 if compressed else 65
serialized = ffi.new("unsigned char [%d]" % length) # noqa: UP031
output_len = ffi.new("size_t *", length)
lib.secp256k1_ec_pubkey_serialize(
self.context.ctx, serialized, output_len, self.public_key, EC_COMPRESSED if compressed else EC_UNCOMPRESSED
)
return bytes(ffi.buffer(serialized, length))
def point(self) -> tuple[int, int]:
"""
Returns the public key as a coordinate point.
"""
public_key = self.format(compressed=False)
return bytes_to_int(public_key[1:33]), bytes_to_int(public_key[33:])
def verify(self, signature: bytes, message: bytes, hasher: Hasher = sha256) -> bool:
"""
Verifies an ECDSA signature.
Parameters:
signature: The ECDSA signature.
message: The message that was supposedly signed.
hasher (collections.abc.Callable[[bytes], bytes] | None): The hash function to use, which must
return 32 bytes. By default, the `sha256` algorithm is used. If `None`, no hashing occurs.
Returns:
A boolean indicating whether the signature is correct.
Raises:
ValueError: If the message hash was not 32 bytes long or the
DER-encoded signature could not be parsed.
"""
msg_hash = hasher(message) if hasher is not None else message
if len(msg_hash) != 32: # noqa: PLR2004
msg = "Message hash must be 32 bytes long."
raise ValueError(msg)
verified = lib.secp256k1_ecdsa_verify(self.context.ctx, der_to_cdata(signature), msg_hash, self.public_key)
# A performance hack to avoid global bool() lookup.
return not not verified # noqa: SIM208
def add(self, scalar: bytes, update: bool = False) -> PublicKey: # noqa: FBT001, FBT002
"""
Adds a scalar to the public key.
Parameters:
scalar: The scalar with which to add.
update: Whether to update the public key in-place.
Returns:
The new public key, or the modified public key if `update` is `True`.
Raises:
ValueError: If the tweak was out of range or the resulting public key was invalid.
"""
scalar = pad_scalar(scalar)
new_key = ffi.new("secp256k1_pubkey *", self.public_key[0])
success = lib.secp256k1_ec_pubkey_tweak_add(self.context.ctx, new_key, scalar)
if not success:
msg = "The tweak was out of range, or the resulting public key is invalid."
raise ValueError(msg)
if update:
self.public_key = new_key
return self
return PublicKey(new_key, self.context)
def multiply(self, scalar: bytes, update: bool = False) -> PublicKey: # noqa: FBT001, FBT002
"""
Multiplies the public key by a scalar.
Parameters:
scalar: The scalar with which to multiply.
update: Whether to update the public key in-place.
Returns:
The new public key, or the modified public key if `update` is `True`.
"""
scalar = validate_secret(scalar)
new_key = ffi.new("secp256k1_pubkey *", self.public_key[0])
lib.secp256k1_ec_pubkey_tweak_mul(self.context.ctx, new_key, scalar)
if update:
self.public_key = new_key
return self
return PublicKey(new_key, self.context)
def combine(self, public_keys: list[PublicKey], update: bool = False) -> PublicKey: # noqa: FBT001, FBT002
"""
Adds a number of public keys together.
Parameters:
public_keys: A sequence of public keys.
update: Whether to update the public key in-place.
Returns:
The combined public key, or the modified public key if `update` is `True`.
Raises:
ValueError: If the sum of the public keys was invalid.
"""
new_key = ffi.new("secp256k1_pubkey *")
combined = lib.secp256k1_ec_pubkey_combine(
self.context.ctx, new_key, [pk.public_key for pk in [self, *public_keys]], len(public_keys) + 1
)
if not combined:
msg = "The sum of the public keys is invalid."
raise ValueError(msg)
if update:
self.public_key = new_key
return self
return PublicKey(new_key, self.context)
def __eq__(self, other) -> bool:
return self.format(compressed=False) == other.format(compressed=False)
def __hash__(self) -> int:
return hash(self.format(compressed=False))
class PublicKeyXOnly:
def __init__(self, data: bytes | ffi.CData, parity: bool = False, context: Context = GLOBAL_CONTEXT): # noqa: FBT001, FBT002
"""
Initializes a BIP340 `x-only` public key.
Parameters:
data (bytes): The formatted public key.
parity: Whether the encoded point is the negation of the public key.
context: The context to use.
Raises:
ValueError: If the public key could not be parsed or is invalid.
"""
if not isinstance(data, bytes):
self.public_key = data
else:
public_key = ffi.new("secp256k1_xonly_pubkey *")
parsed = lib.secp256k1_xonly_pubkey_parse(context.ctx, public_key, data)
if not parsed:
msg = "The public key could not be parsed or is invalid."
raise ValueError(msg)
self.public_key = public_key
self.parity = parity
self.context = context
@classmethod
def from_secret(cls, secret: bytes, context: Context = GLOBAL_CONTEXT) -> PublicKeyXOnly:
"""
Derives an x-only public key from a private key secret.
Parameters:
secret: The private key secret.
context: The context to use.
Returns:
The x-only public key.
Raises:
ValueError: If the secret was invalid.
"""
keypair = ffi.new("secp256k1_keypair *")
res = lib.secp256k1_keypair_create(context.ctx, keypair, validate_secret(secret))
if not res:
msg = "Secret was invalid"
raise ValueError(msg)
xonly_pubkey = ffi.new("secp256k1_xonly_pubkey *")
pk_parity = ffi.new("int *")
res = lib.secp256k1_keypair_xonly_pub(context.ctx, xonly_pubkey, pk_parity, keypair)
return cls(xonly_pubkey, parity=not not pk_parity[0], context=context) # noqa: SIM208
@classmethod
def from_valid_secret(cls, secret: bytes, context: Context = GLOBAL_CONTEXT) -> PublicKeyXOnly:
"""
Derives an x-only public key from a valid private key secret, avoiding input checks.
Parameters:
secret: The private key secret.
context: The context to use.
Returns:
The x-only public key.
Raises:
ValueError: If the secret was invalid.
"""
keypair = ffi.new("secp256k1_keypair *")
res = lib.secp256k1_keypair_create(context.ctx, keypair, secret)
if not res:
msg = "Secret was invalid"
raise ValueError(msg)
xonly_pubkey = ffi.new("secp256k1_xonly_pubkey *")
pk_parity = ffi.new("int *")
res = lib.secp256k1_keypair_xonly_pub(context.ctx, xonly_pubkey, pk_parity, keypair)
return cls(xonly_pubkey, parity=not not pk_parity[0], context=context) # noqa: SIM208
def format(self) -> bytes:
"""
Serializes the public key.
Returns:
The public key serialized as 32 bytes.
Raises:
ValueError: If the public key in `self.public_key` is invalid.
"""
output32 = ffi.new("unsigned char [32]")
res = lib.secp256k1_xonly_pubkey_serialize(self.context.ctx, output32, self.public_key)
if not res:
msg = "Public key in self.public_key must be valid"
raise ValueError(msg)
return bytes(ffi.buffer(output32, 32))
def verify(self, signature: bytes, message: bytes) -> bool:
"""
Verifies a Schnorr signature over a given message.
Parameters:
signature: The 64-byte Schnorr signature to verify.
message: The message to be verified.
Returns:
A boolean indicating whether the signature is correct.
Raises:
ValueError: If the signature is not 64 bytes long.
"""
if len(signature) != 64: # noqa: PLR2004
msg = "Signature must be 64 bytes long."
raise ValueError(msg)
return not not lib.secp256k1_schnorrsig_verify( # noqa: SIM208
self.context.ctx, signature, message, len(message), self.public_key
)
def tweak_add(self, scalar: bytes) -> None:
"""
Adds a scalar to the public key.
Parameters:
scalar: The scalar with which to add.
Returns:
The modified public key.
Raises:
ValueError: If the tweak was out of range or the resulting public key would be invalid.
"""
scalar = pad_scalar(scalar)
out_pubkey = ffi.new("secp256k1_pubkey *")
res = lib.secp256k1_xonly_pubkey_tweak_add(self.context.ctx, out_pubkey, self.public_key, scalar)
if not res:
msg = "The tweak was out of range, or the resulting public key would be invalid"
raise ValueError(msg)
pk_parity = ffi.new("int *")
lib.secp256k1_xonly_pubkey_from_pubkey(self.context.ctx, self.public_key, pk_parity, out_pubkey)
self.parity = not not pk_parity[0] # noqa: SIM208
def __eq__(self, other) -> bool:
res = lib.secp256k1_xonly_pubkey_cmp(self.context.ctx, self.public_key, other.public_key)
return res == 0
def __hash__(self) -> int:
return hash(self.format())
================================================
FILE: src/coincurve/py.typed
================================================
================================================
FILE: src/coincurve/types.py
================================================
from __future__ import annotations
from collections.abc import Callable
from coincurve._libsecp256k1 import ffi
Hasher = Callable[[bytes], bytes] | None
Nonce = tuple[ffi.CData, ffi.CData]
================================================
FILE: src/coincurve/utils.py
================================================
from __future__ import annotations
from base64 import b64decode, b64encode
from hashlib import sha256 as _sha256
from os import environ, urandom
from typing import TYPE_CHECKING
from coincurve._libsecp256k1 import ffi, lib
from coincurve.context import GLOBAL_CONTEXT, Context
if TYPE_CHECKING:
from collections.abc import Generator
from coincurve.types import Hasher
GROUP_ORDER = (
b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xba\xae\xdc\xe6\xafH\xa0;\xbf\xd2^\x8c\xd06AA"
)
GROUP_ORDER_INT = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
KEY_SIZE = 32
MSG_HASH_SIZE = 32
ZERO = b"\x00"
PEM_HEADER = b"-----BEGIN PRIVATE KEY-----\n"
PEM_FOOTER = b"-----END PRIVATE KEY-----\n"
if environ.get("COINCURVE_BUILDING_DOCS") != "true":
DEFAULT_NONCE = (ffi.NULL, ffi.NULL)
def sha256(bytestr: bytes) -> bytes:
return _sha256(bytestr).digest()
else: # no cov
class __Nonce(tuple): # noqa: SLOT001
def __repr__(self) -> str:
return "(ffi.NULL, ffi.NULL)"
class __HasherSHA256:
def __call__(self, bytestr: bytes) -> bytes:
return _sha256(bytestr).digest()
def __repr__(self) -> str:
return "sha256"
DEFAULT_NONCE = __Nonce((ffi.NULL, ffi.NULL))
sha256 = __HasherSHA256()
def pad_hex(hexed: str) -> str:
# Pad odd-length hex strings.
return hexed if not len(hexed) & 1 else f"0{hexed}"
def bytes_to_int(bytestr: bytes) -> int:
return int.from_bytes(bytestr, "big")
def int_to_bytes(num: int) -> bytes:
return num.to_bytes((num.bit_length() + 7) // 8 or 1, "big")
def int_to_bytes_padded(num: int) -> bytes:
return pad_scalar(num.to_bytes((num.bit_length() + 7) // 8 or 1, "big"))
def hex_to_bytes(hexed: str) -> bytes:
return pad_scalar(bytes.fromhex(pad_hex(hexed)))
def chunk_data(data: bytes, size: int) -> Generator[bytes, None, None]:
return (data[i : i + size] for i in range(0, len(data), size))
def der_to_pem(der: bytes) -> bytes:
return b"".join([PEM_HEADER, b"\n".join(chunk_data(b64encode(der), 64)), b"\n", PEM_FOOTER])
def pem_to_der(pem: bytes) -> bytes:
return b64decode(b"".join(pem.strip().splitlines()[1:-1]))
def get_valid_secret() -> bytes:
while True:
secret = urandom(KEY_SIZE)
if ZERO < secret < GROUP_ORDER:
return secret
def pad_scalar(scalar: bytes) -> bytes:
return (ZERO * (KEY_SIZE - len(scalar))) + scalar
def validate_secret(secret: bytes) -> bytes:
if not 0 < bytes_to_int(secret) < GROUP_ORDER_INT:
msg = f"Secret scalar must be greater than 0 and less than {GROUP_ORDER_INT}."
raise ValueError(msg)
return pad_scalar(secret)
def verify_signature(
signature: bytes, message: bytes, public_key: bytes, hasher: Hasher = sha256, context: Context = GLOBAL_CONTEXT
) -> bool:
"""
Verify an ECDSA signature.
Parameters:
signature: The ECDSA signature.
message: The message that was supposedly signed.
public_key: The formatted public key.
hasher (collections.abc.Callable[[bytes], bytes] | None): The hash function to use, which must return 32 bytes.
By default, the `sha256` algorithm is used. If `None`, no hashing occurs.
context: The secp256k1 context.
Returns:
A boolean indicating whether or not the signature is correct.
Raises:
ValueError: If the public key could not be parsed or was invalid, the
message hash was not 32 bytes long, or the DER-encoded signature
could not be parsed.
"""
pubkey = ffi.new("secp256k1_pubkey *")
pubkey_parsed = lib.secp256k1_ec_pubkey_parse(context.ctx, pubkey, public_key, len(public_key))
if not pubkey_parsed:
msg = "The public key could not be parsed or is invalid."
raise ValueError(msg)
msg_hash = hasher(message) if hasher is not None else message
if len(msg_hash) != MSG_HASH_SIZE:
msg = "Message hash must be 32 bytes long."
raise ValueError(msg)
sig = ffi.new("secp256k1_ecdsa_signature *")
sig_parsed = lib.secp256k1_ecdsa_signature_parse_der(context.ctx, sig, signature, len(signature))
if not sig_parsed:
msg = "The DER-encoded signature could not be parsed."
raise ValueError(msg)
verified = lib.secp256k1_ecdsa_verify(context.ctx, sig, msg_hash, pubkey)
# A performance hack to avoid global bool() lookup.
return not not verified # noqa: SIM208
================================================
FILE: tests/__init__.py
================================================
================================================
FILE: tests/conftest.py
================================================
import pytest
PRIVATE_KEY_BYTES = b"\xc2\x8a\x9f\x80s\x8fw\rRx\x03\xa5f\xcfo\xc3\xed\xf6\xce\xa5\x86\xc4\xfcJR#\xa5\xady~\x1a\xc3"
PRIVATE_KEY_DER = (
b"0\x81\x84\x02\x01\x000\x10\x06\x07*\x86H\xce=\x02\x01\x06"
b"\x05+\x81\x04\x00\n\x04m0k\x02\x01\x01\x04 \xc2\x8a\x9f"
b"\x80s\x8fw\rRx\x03\xa5f\xcfo\xc3\xed\xf6\xce\xa5\x86\xc4"
b"\xfcJR#\xa5\xady~\x1a\xc3\xa1D\x03B\x00\x04=\\(u\xc9\xbd"
b"\x11hu\xa7\x1a]\xb6L\xff\xcb\x139k\x16=\x03\x9b\x1d\x93'"
b"\x82H\x91\x80C4v\xa45**\xdd\x00\xeb\xb0\xd5\xc9LQ[r\xeb"
b"\x10\xf1\xfd\x8f?\x03\xb4/J+%[\xfc\x9a\xa9\xe3"
)
PRIVATE_KEY_HEX = "c28a9f80738f770d527803a566cf6fc3edf6cea586c4fc4a5223a5ad797e1ac3"
PRIVATE_KEY_NUM = 87993618360805341115891506172036624893404292644470266399436498750715784469187
PRIVATE_KEY_PEM = (
b"-----BEGIN PRIVATE KEY-----\n"
b"MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgwoqfgHOPdw1SeAOlZs9v\n"
b"w+32zqWGxPxKUiOlrXl+GsOhRANCAAQ9XCh1yb0RaHWnGl22TP/LEzlrFj0Dmx2T\n"
b"J4JIkYBDNHakNSoq3QDrsNXJTFFbcusQ8f2PPwO0L0orJVv8mqnj\n"
b"-----END PRIVATE KEY-----\n"
)
PUBLIC_KEY_COMPRESSED = b"\x03=\\(u\xc9\xbd\x11hu\xa7\x1a]\xb6L\xff\xcb\x139k\x16=\x03\x9b\x1d\x93'\x82H\x91\x80C4"
PUBLIC_KEY_UNCOMPRESSED = (
b"\x04=\\(u\xc9\xbd\x11hu\xa7\x1a]\xb6L\xff\xcb\x139k\x16=\x03"
b"\x9b\x1d\x93'\x82H\x91\x80C4v\xa45**\xdd\x00\xeb\xb0\xd5\xc9"
b"LQ[r\xeb\x10\xf1\xfd\x8f?\x03\xb4/J+%[\xfc\x9a\xa9\xe3"
)
PUBLIC_KEY_X = 27753912938952041417634381842191885283234814940840273460372041880794577257268
PUBLIC_KEY_Y = 53663045980837260634637807506183816949039230809110041985901491152185762425315
MESSAGE = (
b"\xdfw\xeb)\t2R8\xda5\x02\xadE\xdd\xce\xd2\xe0\xb4\xf1\x81\xe7\xdf"
b":\xce\x82m\xcf\x99\xf3o\x9d\xe6\xfb\xe4\x98O\x88\xcfh\xbe\xfd\xc2"
b"{\xafm\xb3\xff\xb4QR\xffPu$\xfc>A'\x03t\xc5\xf9\xd8\xf3I,\xaa\"*"
b"\xd7q\xfe\xb7]\x11\xa9uB'd\x89\x03'3\xb8/\x80\xa2#\x00\xa2\xfe"
b"\xff\xae\xb0\x86\xc1/ o\xc8]?\xa05L\xff8\x8az\x92\xc9\xab\x9fg0|"
b'\\5\x98\xfaG\x9b#\xec\x1a\xc5\x10\xd6\x08\x9c:\x01"\x0c\x812O/i'
b'\xc4WI\x0c\r\xd8\x81-m1_\x14]$\xf8\x16\xef\x1e\x1d\xb0"Q\x1a\xcf'
b'`R\xae\x0c"r2\x9a\xa3\xdb\xc4W}<c\xd8\x0e\xb5\x96\x99\x87\xdeU'
b"\x84\x1a?No\x10T\xf8\xb8\xd3\x18\xa4\xaf"
)
SIGNATURE = (
b"0E\x02!\x00\xee$\x1b\x0e@fa\xd4<\x17)\xa7\n\xd0\xd7\xef\x90\xcd\x13"
b"\xad`\xc1\x06[\xe0\x821\x96\xe29\x80'\x02 \r\x02\x13\xd2\xaf?\x92G"
b"\x80&8\x1cVz%2\xb0\x8a\xd0l\x0b4\x9c~\x93\x18\xad\xe4J\x9c-\n"
)
RECOVERABLE_SIGNATURE = (
b"\xee$\x1b\x0e@fa\xd4<\x17)\xa7\n\xd0\xd7\xef\x90\xcd\x13"
b"\xad`\xc1\x06[\xe0\x821\x96\xe29\x80'\r\x02\x13\xd2\xaf?"
b"\x92G\x80&8\x1cVz%2\xb0\x8a\xd0l\x0b4\x9c~\x93\x18\xad"
b"\xe4J\x9c-\n\x00"
)
X_ONLY_PUBKEY = b"Ncx\x00\xf1_'BV\x9ac\x0b\xec)\x0eH\xdf\xebc\xa9\\\x85\x19:\xf9L{B\xe6\x14\xfe\xa8"
X_ONLY_PUBKEY_INVALID = bytes(32)
@pytest.fixture
def samples():
return {
"MESSAGE": MESSAGE,
"PRIVATE_KEY_BYTES": PRIVATE_KEY_BYTES,
"PRIVATE_KEY_DER": PRIVATE_KEY_DER,
"PRIVATE_KEY_HEX": PRIVATE_KEY_HEX,
"PRIVATE_KEY_NUM": PRIVATE_KEY_NUM,
"PRIVATE_KEY_PEM": PRIVATE_KEY_PEM,
"PUBLIC_KEY_COMPRESSED": PUBLIC_KEY_COMPRESSED,
"PUBLIC_KEY_UNCOMPRESSED": PUBLIC_KEY_UNCOMPRESSED,
"PUBLIC_KEY_X": PUBLIC_KEY_X,
"PUBLIC_KEY_Y": PUBLIC_KEY_Y,
"SIGNATURE": SIGNATURE,
"RECOVERABLE_SIGNATURE": RECOVERABLE_SIGNATURE,
"X_ONLY_PUBKEY": X_ONLY_PUBKEY,
"X_ONLY_PUBKEY_INVALID": X_ONLY_PUBKEY_INVALID,
}
================================================
FILE: tests/data/ecdsa_sig.json
================================================
{
"vectors": [
{
"msg": "9e5755ec2f328cc8635a55415d0e9a09c2b6f2c9b0343c945fbbfe08247a4cbe",
"sig": "30440220132382ca59240c2e14ee7ff61d90fc63276325f4cbe8169fc53ade4a407c2fc802204d86fbe3bde6975dd5a91fdc95ad6544dcdf0dab206f02224ce7e2b151bd82ab01",
"privkey": "31a84594060e103f5a63eb742bd46cf5f5900d8406e2726dedfc61c7cf43ebad"
},
{
"msg": "2d46a712699bae19a634563d74d04cc2da497b841456da270dccb75ac2f7c4e7",
"sig": "3045022100d80cf7abc9ab601373780cee3733d2cb5ff69ba1452ec2d2a058adf9645c13be0220011d1213b7d152f72fd8759b45276ba32d9c909602e5ec89550baf3aaa8ed95001",
"privkey": "7177f0d04c79fa0b8c91fe90c1cf1d44772d1fba6e5eb9b281a22cd3aafb51fe"
},
{
"msg": "c94f4ec84be928017cbbb447d2ab5b5d4d69e5e5fd03da7eae4378a1b1c9c402",
"sig": "3045022100d0f5b740cbe3ee5b098d3c5afdefa61bb0797cb4e7b596afbd38174e1c653bb602200329e9f1a09632de477664814791ac31544e04715db68f4b02657ba35863e71101",
"privkey": "989e500d6b1397f2c5dcdf43c58ac2f14df753eb6089654e07ff946b3f84f3d5"
},
{
"msg": "dfeb2092955572ce0695aa038f58df5499949e18f58785553c3e83343cd5eb93",
"sig": "30440220692c01edf8aeab271df3ed4e8d57a170f014f8f9d65031aac28b5e1840acfb5602205075f9d1fdbf5079ee052e5f3572d518b3594ef49582899ec44d065f71a5519201",
"privkey": "39dfc615f2b718397f6903b0c46c47c5687e97d3d2a5e1f2b200f459f7b1219b"
},
{
"msg": "49e558d232ca204ecc27b321b197bfe0e165acd5fe46f986d5b007c68c2dcbd7",
"sig": "30440220330f62769e8e0fd43721029e1343b7f2c2575edc568007aa1b4458b2928dfc530220549c8967e8e8f414be10c6e2d36f451f1c50007c28511f9410fd7a921220190401",
"privkey": "1e17c938ef3754781130edcbe8fa2c8a27ecb43f93d333e64d89af3d9c26c23a"
},
{
"msg": "12a683325e3063e4d9d32d38f878300d84d4ebb63247607dfb2bd72849e5fb14",
"sig": "3045022100f1391011657e358464165b53f0993a1a637f6e296c1a8f6a984d522b441b470602206c78004f1489c398aeb05b6db63d0839875f4751b117a6bd239b173428f1c2e201",
"privkey": "9120318c2d0792a29516b18a3467051445e5a6c358da577dcf06ea5ddfb8839f"
},
{
"msg": "06e42f8adc7c2ac118b6e6d1082940c66f7d2544fa2eaae0d2b1d016fea26092",
"sig": "304402202c947ee0cf77bce3c4aab68737f07f9c43733a961552da82798a2628d6650a7c02205bb4a8634e8204bc3da98ddb1c5a7d7f5a2f4d60ee447e6e7ab20cff9dfdc5bc01",
"privkey": "a7433f154076cd9317e048562fe60529140a1155a1497ebd165b081c053e8944"
},
{
"msg": "43307439480b2d4c3a95e725d1a13a97ec032cdbdda467533dce304513752d46",
"sig": "3045022100ac6123107cc08a63fb80e2af53937cf5aa7dee9f4e233a668d6c15d0b474903b02205bdcd30cae23c89692ee53badd610385b84567f98dc65babf000055f33877ac801",
"privkey": "58a127499af1fa4f6cc95d6b2213171762a3be0d929246f27044d64de24c4db2"
},
{
"msg": "3a75a427f8311ae278b76bcce9a2ba99b9adc1ee91d653f9f00052b9e154b511",
"sig": "304402202f54a17ffa1b631f67232785b469d794969d3fdb140bb683ae8097217ec12c7d0220159f78ceece7e2fa1dea9e31f53374aebe6b8d893588c27e951bbc40b7ca57cf01",
"privkey": "17449f296f72790a85284a3eead10e775b452a32e7cdf1f0e68f109e5732d8a4"
},
{
"msg": "d2ae97ae7b0b488f3a9cca6b7ed92832b4a6a7a3546a3ed9964eb51fbb2fcd98",
"sig": "3045022100c842a06f6818768fc3d93d98332affc7160fc2f58ba6fc0239c2c9cf951e6bc302202756c90464f85193a52a29001eaf824d3b0602028d4c8f5abadf6995262bc7e001",
"privkey": "229d075d7a48c5725f21e585e9effb08c80ea768883fc372ed3ad48af60a653d"
},
{
"msg": "3492a279ef2ddfebc47cc7ee28be41b5ffc03c54b738f657862590237e56841b",
"sig": "30440220708aae22df437d7280fd8e06b9ab043abc26f6286973e94df9ae28982c534fa5022063724a156b0766c59fe75c571a5f47b80f313676f60830699b0f0c171841c32a01",
"privkey": "f2f83ccb7ecd11e6ef61a8a090890b6db56e87810be8f283626f6028843e70c2"
},
{
"msg": "bab37255a348dcfd8864aa2bd1b964c951da28370743905f892bbf5533b2c881",
"sig": "3044022054cc561ced7052b9b0d709578e691e8e4edb04e2a38d1b79fbdbf16302674c6f022013c9ec89010fe1d9643019c7bb863abd618d4dd492c116d8fb099710e33f5b5101",
"privkey": "c476e1df4d6bc771b23836b58616ed5bb6417baaab3c533dd255562d41cce2df"
},
{
"msg": "3543080c755844091a26bc812fefa40d00663f57622bdb63334ffbdd70ac8cfd",
"sig": "3044022066b520cb7f51656e610aefb238bfa5a62426953b22cc09689408429b68ff53f902205971ec48692210b050683f9ab2cd4443ade8a9d0ae3f95d32e865458515b8e4601",
"privkey": "e9fd9db20943a760c4cf89a9eb8aa150405686332c8a2ab8789ef19593312ac9"
},
{
"msg": "aa05ef24738774890500e5d4f25f2801802a4c20f1be4f06721220d065abe042",
"sig": "3045022100d081a3dcc756f91b4c6aaaa5eb57c18ebe733086391c4a0761a9b11a43dd477002205cf7ce2565d977572dd10fc17875d07b2f8e3d824740db45a30f8977f57c7a1d01",
"privkey": "251e11bc1fdf9ce349c904af6f3df523e906e0367e18c96e111104715fbbfcbc"
},
{
"msg": "57176b5966096a1818148936e1c56bea9717a7b9f4e3decf582b4ae3585e794d",
"sig": "304502210093a5c31daffcaa47b22a1f34dd969510acb76440879e91c51bb1b465fedfa6d302206edf49b1ad26bae5a1c179ae6913d3f3c3fcc0c5a3c7abec07d8adb315a0782a01",
"privkey": "099796458bffc713b89b1332089b093f8ed08039fa1d8323d7a8b198225d89d5"
},
{
"msg": "8b339212a72cbea99b8e882e4ce7c32a1a1f0e5185d3c8e4b99c9a8ebfe58060",
"sig": "30440220640997e496194c03b7e56fe5b97a44bbad81df5a9c2501138453b86e3d1ad16a02201a1dd1ffdbe9300184b7b1ff7d0a1339dd677a081acc2208050e0dde313f515b01",
"privkey": "00417c436e3f135eb2292eb5d88cab40157b0989dc0d917ab6e87e8faa0f1ee5"
},
{
"msg": "f1415142ca90a4f6d1f5c63dd0d776642e34bc3fe3a9ea1427afdf9d71d79231",
"sig": "304402202364525dea549ced7856a0551be97d73b1e5f20237e857cd0193f3aacaa6e1100220245974967a1f0a849a3aa7492f044c53fb10a0198c280caf075aad9beb80126401",
"privkey": "1d77dcd3d3631285c0a99a6c669d804fb603cc8a788c4568a46971b46718453a"
},
{
"msg": "e5a093e9d225f09961f35f44b3171962b2ad3f9c859c3c21d0f03fc7dfadbd51",
"sig": "304402200d2a510384e4ec4d294150598e893ef97827c11c61a081cf694cf53e1c0dfcc102200eed9c0dbe6294f692ead6d729eddfe9267411d67a08031012c4b46230561bb701",
"privkey": "df693dfdca61122d2e633d2a2d8f4270a9199624d06f9230325d82708da5e9d2"
},
{
"msg": "42f5f89f123967d7ecb0f0ffbac97d1e414d22f8aeea5f61ab18c67d9b05b062",
"sig": "3045022100a251fd56b0630c642143c05cc20fef8979163e85d8dd4413acf97c7f0b21832002206959532ff89b8515c5934f7dedef47c8ea1c3b0f3e1630f85445cc6989b8e61c01",
"privkey": "3e21112d32f27677d715c4f12d4005d551a5a523d7c63717f2166ebea89addc5"
},
{
"msg": "f1914da938a0c2e8112c769591ee070ac4123b9c487c0ed7fab6b04473bf7714",
"sig": "3044022010c823ef2f6bb11e4a974723499be7a4fc9610da16229ae49e025525eca9088102203e3ae2173dcc83b754e39fbc88eb1b31effda488c1c9632572513146ed5c7dfe01",
"privkey": "ec3eaa66a6ef4b6c31acd75528218e702419bdf1c086431137926fe71daaaea0"
},
{
"msg": "3cf9fb4cf59c6d37da6c36e0d680b1d364800e94b54f2b9074d755a456a0d7f4",
"sig": "3045022100cd40d40dbef9ac2e27f3c8d40a7f8dd538d417a7bfbcd0c00a6e92c93bcb145202205172d09504b59aeb6d2afc298441615665e4ec8fb7bf064a9364ac5f6ad2171201",
"privkey": "a51997dff0ae72ff3005ae6cad243a5d5c6864691d238fd58123bea364008009"
},
{
"msg": "ada128dfd83338a8b0f5ead162a0fc7acaa513d5c13b9b1d5d921d2648e674f5",
"sig": "3044022075c1178f1bb941fdfa4fa4e21f056c5717a5a94247030c321ad51ac6d8a2a860022036759348c6e87c3b27388ba92895ccc6c7af659f57b4205a236bebccfe6f3e3f01",
"privkey": "9a8114c3bfe3656290a0d092bb80a9f5c67b3e938ba719424c5c8c83e13ce266"
},
{
"msg": "d23cdc4accc2566b82fe6270e144f366f08007dffcf9e47c1c66d40708747da0",
"sig": "3045022100fbe82220345d27c870e152048698340e70d701ab868a5482a7cc22977962f8d4022009181b343e76c8645dc640762d49911df336941d79dedccba313ba03dd8c28b701",
"privkey": "f1d7fb29ab98d00f46135b74de1b7ea80f3e38360c9ee0cd7056404c5d1db57b"
},
{
"msg": "e89e786443b5634afbe711f66530326320a0e5ec27d1c42697e5fbc11e5ce169",
"sig": "3045022100be23de4212c4bca5d1be2882ab339aa8a586dd656daae12aa2287cdfb2809af002200d460339847f0c08311a59343997d5e307136a4fc0494f84aacbbf918deb11ff01",
"privkey": "ab8f7d2eb73af6349990d2b4296d98432da8bbd858863bfa136e234a56351090"
},
{
"msg": "ba7de66731341e48fca15ef5b62c88f19b2aee7786987d73754be8e339f706aa",
"sig": "304402202c2c1b9bee9b018df561d1f45ec07527ac916e8e56a915862cf80a4fe73779c8022037aff510509b3704390da0db4b647540647f897df5a211648f4180a292b12bca01",
"privkey": "d142f008e4ee702e6ddbf6f5429e4b39e0cd59034018a332635e6fab028f2fbe"
},
{
"msg": "1ddaa971fd3b025d0566d9bf893a0043329379e2c52561ad3ebd3815bae0e818",
"sig": "30440220110aa7dfe50948e3a8ae149a01d9b3824e7274a6f380e64d8b189b01e71ebc4902207712465f4f09e039f46d06fa949ab5f875a0f612c86c76702c07a4007532122f01",
"privkey": "c03a06e7e3958f790f3d1bbd8c9938e3c1636301a04b15b2774d27381a383d35"
},
{
"msg": "c5b4b2981c007f5e5b6aceb950985e64f8c5afec77955e973bc65f1a913ccd2d",
"sig": "3045022100d9f8081fc24e267dd5e2a70604277496c9c8a20b7c37f3d788e92e7356754b6e02207ffdafb0ba4efee3211a1d8e4623f13b63141054c942cd47e388faf3489bb8ff01",
"privkey": "795b50f2d308bf999565a084c8f709e6393f69a7842ce889c5addbd17c24ff5c"
},
{
"msg": "1544d33544fa93334739a60a35bab4390b523bdbaa43ba3956ca450ad03f6201",
"sig": "304402204d74290c6daedc88e3fdae9edac89984065c2774ed69d90668544aacc6e7e16402201ec57cf928279f30d56d8e5a1a46cd976cc7117bf2d03c9123833f08db6132ee01",
"privkey": "fd2190d00de40b004f9a5d6d06829fe87eb1b11e483e94023fa01b41c27da90b"
},
{
"msg": "cd40650b1d22d7a7777ac3a6d8701c12b485dd3859dacbaca56ad4ee4a3f6024",
"sig": "30450221008c17c93e1aa880c36c87ec08fe52ed68a96ee014a4cb8045fdd08a0537cdb602022045728db5a2bc1860cdf0767122e567f903a0de25f770d3a96b51833b2530e03e01",
"privkey": "3a6857669abb2d6f2f529b548f6386435856ebc98d8760e75642ff30a149454a"
},
{
"msg": "5817d0ffe477cab901884793ce1e5d40302941ea7460a2b468b7b02f08203527",
"sig": "304402205ab83ee406a958901837e7f155fa8c043558963fcac8156515166bf5d008b21c022026e18000c7050b8d7415337b10eccef395db63c6fdd8b008d2b613ac2583e8d201",
"privkey": "9054a4c8279b1812748c690f7de4a8305e7ba2afa27bb30a70ec2be6cfdcd104"
},
{
"msg": "082bca2d141957a5bcf2e6f0653557ddb12d0ef393d89c5c41849464cca6b8f3",
"sig": "3045022100d62ef631bc28d285f0ea2bde820c4edd321539e209c3575b139b4fa7fb571e35022015f47f15f24d40900ad13ddcf9809a4f8f45e8c1af2a241cd2641df02772809a01",
"privkey": "eb75f55fc51a5664c46889eb93af6834b8fc4760ffd12f36f2a7a54180765644"
},
{
"msg": "58a7fca8920df4322b3118241aca431418796937f2dbb32bf36b2c230ac3ce78",
"sig": "3045022100efed6c4c4287746b5870de319fd0fd1e7f39cfe19771764eea8bcc2641bb87120220284f66abc53254d9fce663ecc1648d417251b246c1f72e2668e786d65b01db0a01",
"privkey": "9d6cc489b02c25d0a33f3e2c71dc90c71bb91942bea45d543f41907f026fdf94"
},
{
"msg": "33cfeae00abc4f593d9499a7e11e314f9ead2bc6d033977d91fa13df224b56c4",
"sig": "304402205d51ef7d53b77f1e4933e38b72e60e2cb505f5c04c4589691c4afb66d45299b302202a9b34129c747c1a98dbbee519ccb8f45354cbe36b4e4bc6e9cb0079d8a8b0df01",
"privkey": "cdeee2375309bcb5cdf33d6e43f8949b3936a0bb776bb20b40087150c4eaaa9d"
},
{
"msg": "789d7a8a926e65c632dd4ae279c23c7cbbe1938082000a4f7e663dae4d2e6565",
"sig": "3045022100c316f7d0f51e6ea313d2b03805d695ac4426f959d14d9138ef171deddf0e593702203a94ff889daed2b366a17d5b0bbbe46f96faa439f3e0bf3da3755f2313cfd9e501",
"privkey": "f22cce5bfc11406b3c2013be86cda5e5597a10c99f045af275aa5c4136ae8247"
},
{
"msg": "cad973f4d052f59654c8b714e4c777c297217de855f7e72679ddc4d88353179a",
"sig": "30440220140c11ee563970fd34a31076fb92a81320bad49373682aab2d11ad0e9e71da5902202bc4a45f694e1a25b89e87b21b659afe87e83f57a53a2fe7a4160a93fab4331701",
"privkey": "9056b9aeb9b88e4b2d2cc34a3be77e6120551c9ec9991aeac46d75b3b07307ab"
},
{
"msg": "08f3306ded0c07980fc8e008c6805fa19582a1c635f892d0027256b8f6e658ef",
"sig": "3045022100ea0f425a8f27a9fc82286ca7545207cfe0ca1e41d54ad3168836086bc097740b022027ede91105e79097d091efa8d3045ee9a2e250ee9029245560caa5b8bb5450fd01",
"privkey": "54f8d7d845453d60ee39167ea8c0b3203487eb9fd7f9aa83f8b92ee41799d3ac"
},
{
"msg": "ab440f7e37a9ac189e08850866f9fec3fc2c323dc802f5cc13c9b2dd6ea9c366",
"sig": "30450221008e6a5185622e53f986c0f865b376b69cf7e2297b681c7b4baf92396808e90de90220633313c8afcb4bde5d1e099871e75df82245c9f2fe24bcdae86e53a48116f74001",
"privkey": "ee1eda8fc9d0cb958fa518d0b95189c7eb82202e6d07099b8a6a11106797af70"
},
{
"msg": "c0d12e0b35fa92dd04ca66b9e9537a71c5683a4fe5da9354e9f74cec0746251e",
"sig": "304402201e78c59ad8c6ea7ea75735696271eca86851f7d65b2c6771eb5ebbae2c3a5c0a02203f0e9b34333f544e692f7ae92ef6ad68fc455099760944d3e074bc994f2da7bb01",
"privkey": "56983efa120cfd3a7fb92df780a60463decd5f47c6422c6dd0538a68d3c7bfec"
},
{
"msg": "98f7eeb9417e950aa953c110b245adba213084db4bbd58978f2aac125ec2e584",
"sig": "3045022100ba311522b51cfceb11f8fadb34a79f5a75e7eb6b5878c907fb63c983feaf695402205b9e0910415722ce1bf94ac5b6c3bcb09919d3497999ecdde7de6fa76f3d00b301",
"privkey": "04dbf45e1b5620e3ca700a82e4e83a971551e25e15abfcf49c8a201a4174bb58"
},
{
"msg": "2e18fcb984ba778c0017cdf882a862dfcadee1ba4c7ad44937006a7ee0ee90cb",
"sig": "3044022013ccb3f7845476ceea2b990d15ec53253f333ebc1cf392e96590d88aac70dff902202c4874427363f84ab7bf331d2a811d57399237d58fe0ceef34056f26bcd9de2501",
"privkey": "8bfb17789767931b9602e3a1310ae79ffbd5fe7fe1f2583099568a4308cb80c8"
},
{
"msg": "d6fe1d5d5670b02f399ab0125c57fdaa181ae93aeea97c5ad238e0521dc10873",
"sig": "304402202d633a9bbdf3e486fbcd8dae7afb7b9604cf9a806d72121d08a95895d16db21202202f83e6d1c5d17d676885bc05660271770618acaccd35a30b1d3723176c1b395201",
"privkey": "e74ff299a3e399fc7833799b1591c128102d441361dc2ec77810d99abf280fae"
},
{
"msg": "798a41c8bc899e458e9e07d3d83410752ea580020accae54a3ff65ebdf607f54",
"sig": "3045022100ac0d8bee62c3aa92ee6f29c98978a7e47d179f6954fb2b04b977489a2d24e7af022057808f4c12d8deb7737827a1543e88bbd44abbafb4d4db43e2b5a6566d0e821801",
"privkey": "cd4d9c04ec290aa6fe5cb0e34e3f585beaddcba4f39d883bcd0a7e989801a778"
},
{
"msg": "1bafdf652c98542066d53aedf3b0fdd1d3517bc6fcb0155be621ed0032675a1a",
"sig": "304402204ffba8cc994840f5824b2217188bd0e671cc4b787ac33509c774a4075c62a989022041129edaee766692b1c8d9301974f6daeb7139d463bad561f650801f097b4b2601",
"privkey": "51385eadf7238fa581b13905b83ff1c59cb7923d2afdfe2b29396ec9da1f708e"
},
{
"msg": "6fc96a01cf2e3ab6ab277cce670d446f0da3a25bc4344c02651cda1510bda8cc",
"sig": "30450221008bf5889acf97948be7bf6afd1da1504bab75d841c09e3e6cf1cc445da0ed5cd2022030cf8407f6aabdd056e1d4cc14b4a0e4ac09c6cb45f572788f5353fda223923701",
"privkey": "20dc42c28ec79d4b62d84e2be93945888178a0c29aa9b67cb1afa5e4ff4f1073"
},
{
"msg": "524fc94cd49f7fb80b5d2a111d7fa9f9022b79f5368608dc5b50d1706e7e7fce",
"sig": "3045022100d391974a51503f4b8675328abdbeecc998b54bf6de23492f57631c15b8f7ef8a022075e1d215853ce491a0efb1b2ca60776142a26b1e8975158988569c2db2af87b601",
"privkey": "8cdfd627ab00b392e38c27ab8eaae73b202f306deea075815bec61a0d2cec7b2"
},
{
"msg": "d2a53bebeaeb041272699990b027da459ca5badb811e4ad57b3445c56e5112d7",
"sig": "3045022100e2e221910d4ce1fbd7d489a4807a5d11e6fa45f35a997ea5583f8508a1faaf1102200d5fc4fa852eb1b26abfaf6799559a7165b4590707ac446019acf2d468712c1801",
"privkey": "021144fa085375c86c681cd021ad5ed3e7070503309745e97143194248dca839"
},
{
"msg": "63446421e65a1fde302d32b962dcc75a2286b7c2f0491c25b3fb6f051b208dc4",
"sig": "304402200c792c52f28ffdcccda08d7875b3a62e665dbd65ffdd80b0767fefe7af88bcb102207780c849bbd193e805916fded694b89376136cc7b2b8567f52179fd10c64a48601",
"privkey": "dae1c4edd2a889f677615ba48e22e9144580360cb3c7094495579b3a4c17ee24"
},
{
"msg": "53751b08cdfd84b6e7a72d9d32f8684efcec290ec4ff53e5d2b66d85fe1075e4",
"sig": "304402206fa2f2ec4f36d3b92f901d6d1935f6167752c13bce38fde71e1cccace629abe802201575d02c1f9599e19e72c1552a1e67c2b84d780bb663cfe26f0200f7857152ac01",
"privkey": "81560d6c8f7f56947e85c05a5bf58247972a91974dc886fda04557e42cf7aeb6"
},
{
"msg": "a8f00b68747f932865b1e652495e2ee2b962d58ae65a803a83d5dd723b961195",
"sig": "30440220113a2d165fa751a5fbbd96032fe0ba55a9f7395c546c56460c74800206c7689d022037cf2dbd41bfca49004f603aa6476f9a2ead3c0e55f4fe437b4f968379b8951f01",
"privkey": "3648124d1c85eab5ab6a9e48377e1727d1da31b9dafe46844bdbda5381364b16"
},
{
"msg": "c0c93d49025eba4dcc15427306e598ce42b2b301dfda34e8fc944a10cbdf827d",
"sig": "3045022100fc15d1c1a7ab3647c56aec5fa47f1d3d52d082fb797701e32278ecc0ae93dc3f022074bd65fb1164ce8f856dd8c0d5432bef396357302409ca101f02904b7b7fd44801",
"privkey": "5516f2b53a5e43cc284f074d8960a918e67afc6c0c6fb74246fefdf2ae55b0eb"
},
{
"msg": "8cf263c83989fa23970f41b30fdaaf226792c0348d21da89d659bca19fd6d26c",
"sig": "3044022072a768ab0f2f878492d56153ee51b170deb652b6b2a8fe940ded965dd32e49180220300b9252ba85a611adb8827c315d47c2cc33627db9be8e283fdf09b527bd21f701",
"privkey": "467904e0c8850c5b84066e2c135096df2374e5a2066c0e94114b1d06eb98c454"
},
{
"msg": "ab2e3da1e5a5f2acb51e00f7f8e6cda712882080f24bcd9ea43ca555577f3b30",
"sig": "3045022100b1e275667ba42709e6a6da1306df173728b032c42fe955e60721819d7ca77c2302201219d9db72e278fab11dbc80fd14191d2d37a13fb31338280cf2837d64e27b7c01",
"privkey": "6a7536d6733334e204ac5bfa2172c86f7446fd32cd7180027391087f98f18116"
},
{
"msg": "6eba52cd7a8f859632b2b5e8a6ac69581ce266d82a6b010c7b666d3afd0e19df",
"sig": "3045022100fcaa0e84521f46c9a9dcff5da933c981fd2d0275c2cef76881243c6390077ba702207f7aefa9e2e9d2b27526a34c26bfa57b7cb2e22ddbd9ecd830e7cfaf81a7b10b01",
"privkey": "e814cd0bf690b780f8c615cc6f1202247b5218725e76eca315faf9b438c5e554"
},
{
"msg": "85b07d8a8d4100a4302ca49cfde58266911ab1ad45368cf7451dbd9b6f45eb78",
"sig": "3045022100fe510f6954ac3f2fba233f109ec7a08dd93aa955336e47fe7b095f1d79cfe198022037925a771d139f2649a5665a3281b4a363ca96fef8843444f23f101400f247f001",
"privkey": "4f4587386955963eaaac84b32eefff63b32dfbb540bfb578ff7b0cf9f01298b1"
},
{
"msg": "13063f26e50c5b6e62ea39fda446e3beaccf50ad07441241df62e3115f939552",
"sig": "3045022100d44570f08a9e64119d9885d73f688e51176807a4143856d06b684fff4a8ac2da02205f757d21c8c56258c63034d9b169c402889532493cab2c06c697154f44705a9a01",
"privkey": "b6b2e1edbcf892e41285f4ac942a331c4a88d785267c45c8095059c5914d3a98"
},
{
"msg": "738516a95cdb7d1b374324ab22e642b3b23db43464d898e80789eccb25240e6d",
"sig": "30440220655630c6bef940bc7360febfc8e7a09614e871b7ad8c500ad658a6ba7f1669d502202ad85b78a32a10c28b2d581d7e364f6c2b7caacb24a61e31130f2fae5a951de701",
"privkey": "23bf641b12ea841b7a8386a2ff4f378c8d33218fd147906db34c64363f59c1ae"
},
{
"msg": "2a7f44ec15c00e5c457c545a7bd27dcf38ad636cd98d48f0964496d3a01f781f",
"sig": "3044022059bfd164d817b91b94dba6c3843cc85d08442662a9f3736b12f294630f75578b022017f6dffcc2039291bcf0719281b2e599044a63709e94ab0050ebe37a2394933c01",
"privkey": "86b53e69d27fa695bf8217b0a78d72651584e24f1d85e928647310ecc04d33da"
},
{
"msg": "865c6d42b0ca9767593c8736c8b5151753f9c0068c9c52eaaf4305a19e2ea559",
"sig": "304402202b995842e430ed36900761f11440116c4a0ca5c4702aecf293625e608011b3df0220051b45eacacb9448cadd96cf4daef05db8f41dddfcb1943cb8ed67ded4a8e9e901",
"privkey": "8ca07dc245d61105afc9b40e69bbd1532b65e72094f929c117c5a72b4b6ddd25"
},
{
"msg": "5170ee5b8311f24570aa8a639e33c22c8f883e8d947548d47e1e330b965cc57d",
"sig": "3045022100d523659075fe10d9f6cc3e75cc74bf94ee65f4a099eabf7fe6dbe24bbc17f65c02204423cb40c94d732a8a51487b9e398fdca78005bdf1571b7126d67d9f50b712c901",
"privkey": "44e488036d994a53d6d62bd6e3201b7d61aac740157bc25ea41c7da37b5d6149"
},
{
"msg": "29b9327f728c7107537475f99ac50f28e33ec0f68f4db619ff0f8e5b314935f5",
"sig": "3044022035bc811ac5054c04febe41208c422922750ac045acc1cacf2529666a7fb7e40d022071837d85c64cf07a1d598de345125306053c3c9014b408898dc5edd04dd919e401",
"privkey": "1dc87fae4276cdae8ffd67563e0f8539231582818b2e2226dbbe159c021e61a0"
},
{
"msg": "99c751a334d1a3687432e87a34301beae87ec78aed2d48ce066f118b476a85b0",
"sig": "3045022100efcfddecdfbd8157fbe996563256efdc6f29955fab32edb3efed9cab08ef1dcb022067b01f720312ae64475eccb6381e1d3fa342cbcf2852b1ca6a53f5b1a6e16ff901",
"privkey": "6e9f1467ebbfa71faa565e02a8663fd138487dc9526e5d096cfbb2ad38883d22"
},
{
"msg": "76bdddc10926a641df369a6179ad622c6188d3081a3cc90d8dec398c68ab7af6",
"sig": "304402204d275af1bcd7ad578aff98d8a1551a83584b8ee9b57ef6d425fd191426f6f63a02206cb25c37fff11029f8fa5c2b99d0aa5bbc5a5fc484c863a49ab4ab4479892b3201",
"privkey": "f64b62c51f2de8f4643857ba12f81a433764a3e0cd9fa2316b8e202cad9778bd"
},
{
"msg": "f87a7e12df4d4a0ed99ec33e3ce805151526bdbced2bbcb0a012019a5cec7f99",
"sig": "3044022029fa907c4e4ba985b362c2e291291271c6acbbecaf4c49b938261ea6f255ddde02206cfc13e0dbc8d5ec4c98684fb36087738f9b449cb0bd9d930cda0025083543d201",
"privkey": "c21f2a248126a5b5d964d404911682ae1cbe7b7741681226867337dab8974fcb"
},
{
"msg": "493f0c699d505325bfd1dd86e28d7bf94d698120dc10d475aa2547bfdcf5d66e",
"sig": "3045022100c716b0c65339df2357089c202f4644603c21d7139f2cd77336f871834c27150d02206ef0c8cad1605b3957a15695db5fada122b9a8b9b4c49743088e88f93112320101",
"privkey": "5a4b74a8e52b8f2dfd45c2ff015641be2b1f66fc285661de8fc65badbd6707f5"
},
{
"msg": "82c5878eb438661cf7c50655890ed64c374611f3cef2e309a76565f745fc2c96",
"sig": "3044022030db6955679658577e9a7cac8f7081105a173647362178efa7c74bc813396b8102206836332abe41ab0e23e6f62b0aa08666dbe8228c5138638ed0c2351c22e6c57301",
"privkey": "aa6db6512e8b33be3543a46a908f26720402e3c15bd70ba8ebdfe7ab80787afc"
},
{
"msg": "652c4068be47d9437d49d36173e095118367e1d9d0e86295604c0eb94484d5ea",
"sig": "3044022065c05eecb47346d6b45b588a90eb0f5489a23f93170557eb969f258adffabde902202682a1ab6397c49892e349ba581fd623033a35910c97f4079e4c2e505dfdb21f01",
"privkey": "ef0d812b85eec684b3314de835f73fc06c3c90078d404862133643c3347ebe15"
},
{
"msg": "a04a3ee113f5d3054fd1eaacf8f3b1db506122c413ab346250e08ef6b5be8236",
"sig": "3045022100ff6927f3d0c14cfff2ac2c8b47b20ac99baa48ac72726aca35e87af7965725730220654333918dc7615a2e1f5b59af74a1565c281db62201c54befc6158222f8284f01",
"privkey": "b85e9ba6297a46fa4f017742485616c61ce5dbf080782ba1dd961933b7390e18"
},
{
"msg": "428f1dde2440673a19d0b97fe46ece447721e61a700376cc56263e63cac5a3da",
"sig": "3044022070ebbfb49ab2d2529546115e55f8aabb7784bd38fa16eb6a5cc6ac7ccce2fce202206d9afb41c0254c2aa1ba4fe3a05be5d5c1297803adb701197c918609184c2cda01",
"privkey": "c17b6ff03bc75792c3a3b4ed5612140e2f9d9e5d6b21650701523665b3819b05"
},
{
"msg": "ca0d05a273ef3b2782059307d838599eb027b0962798d71efc00648a5b07b744",
"sig": "30450221009c88cd669ab7743fc2f3ccde723fa8ef653f951f67abad5afe8031c6fa3652c4022012cf4d5d66bc0e9d40322489ceedce43bcad5fb41cbf92a6ea0f387c4b3b214f01",
"privkey": "c658044aab784be15a5776d2c4346e1099368a45f06853277352d70823ea7572"
},
{
"msg": "f6f4aa6943deafc1879c00589208656e927422b5f25cc9526a9fa6139c66613c",
"sig": "304402205325ccf115446eef902499ead3734d02065dfd337d924ca615f5d00b1580ef8b02205285496cb0cb63e8ccebf4ff999c922bc3bb5d4a4eb3267fa2c5608e6d0b5b1301",
"privkey": "480989d814f56895a0c5781497adb1ab82744c009815f00362321dcdb709f036"
},
{
"msg": "f5747a05e3488bd03da4210f9233131e1983f9e969d23330b3858a6c71f5c484",
"sig": "30450221009e81de985b1bad7843c72cfb6ee2a30295f13ffb7e1c3b10ddd505cd5d6feb6d02203389442e24b7027466eeebf44a9390ff8f9832716544a4f508574fa919b4848701",
"privkey": "db1e27ec5686db0d8a2b7c609847652067534751944f610bc7ba9f3792e92fda"
},
{
"msg": "ee0a83c20ff143b6629bd9599b5beb2e63384be72a53221c3263bb83efde8647",
"sig": "3044022063f2d7f32985641841f7c218e7a90add74b82e76004eb349d9e122162a574d7c022070e4793900af91b659030179ef46db54f478d74a31b31dd527af0952c49d461f01",
"privkey": "88624e28090c22fa80888c3f15000bd52da334c7b295587773a172aa95bffe2c"
},
{
"msg": "80aaab2aba272c8ccb67022ae9a54bbfaf8443bb37d2f922021a681bc4252a90",
"sig": "304402204457849658bacecab40c4893021d2e567fc86f1c2ed2b20d9f1982138f7f00450220225b04c91531470606088263b28b3df2cccc5a9f1df36130d8960343ed89e13c01",
"privkey": "ac461d001fce26e7f75fd82fb910e839d6e92bca1db3969fd78e74674d3a6bff"
},
{
"msg": "285b0f5cc3c35deb52d5e1f5fe6e7c5a82a0c3ae10249c39c4b81307bbc9a1df",
"sig": "304402205eed90c7c43c9dced57edcce95ae69e5babadaff58f033055d998e4df596121702201e47072efa39405f2920b240b56d3ee41c0b1c2adbef1f161c8da1e7eda6fbde01",
"privkey": "786f5c06867cc7e8a6b16c003ad6809da46851a8e3024527e93af60b1ff70a07"
},
{
"msg": "d615006da4b6aeb980bc12f769e263821890003123b1d2439c69da2cecbe51bc",
"sig": "3045022100e13137464625b65c0403b71a3ff57b42da0f5d35be2384da2a4f52e93d8039b302207ae93812fb1aeffaf60c39b0096b39f2fd73bf2e695be8bddea458a2bd55b89101",
"privkey": "226140d24cc86b64a1c2aadc77bc8b05552386da2d707668b3c460f69d70108b"
},
{
"msg": "0c26d76afb90e2fe6a8c4b2a9d2496e2a6e27f25380885f7985d8c1ab7d2dda6",
"sig": "304402207dfd5a172204356d4dbffe018b04cd6c123a48daf9b9fc201cf7e32088989b11022030217c1dce7e1d039ac011138569c4c897fd201694b10052a1f3ecb2aade3a7901",
"privkey": "1e807394e21a0412d2d48711c0be3685a49fa3810ff84517b5adb1189ebc452e"
},
{
"msg": "ae93855f5897620269b0fbb6f2444fabf304c570c2bc3d1342aa3b0615ad5b70",
"sig": "3045022100bd83f85e36346c428909a60eddd7b1c52abfaf662cab6fc810e0fc398371f83d02207caafa58dceabf7176c2e3b8a8890e47b9c1cddd1cf000ba428a91c396932c7301",
"privkey": "c8d382437c63d3debc2844e00c7eea0e0f400b111e805ba32695de93fc150a65"
},
{
"msg": "b9df3eba9ad579c1840fab29448186122ae27c9add5b5fd6c8a2c7556e0fda8c",
"sig": "30450221008bded4f87ee25852e45908397876b3601c6a0e177dc986bc43f52c5e25fe5d9a02205473929f0502abfc9ae0da25086b13d92e0ee4f1e1c9fcaf334fb4623048dbe101",
"privkey": "54a92b44ea83313d0779475a1e664ad132abc53dfd8e4742ef81d01b3ef657e5"
},
{
"msg": "f304cff5f74fc72bddb353ecb5d1a250848ca764ace690cc308d00b247577226",
"sig": "3045022100c1f9fc9a63f0d26677bfd68687dd0e56a6342bc536a71c9b1f3a2bbd2a48360e022040a3f3179228bb89b82ebb648073e713cb759c1cb7d4e4f5486c29945d5c55e501",
"privkey": "b59821f0dba07cf87f0bcfb4310c6ab4e676ca319fc6c0d9d11ae80be8a0468b"
},
{
"msg": "77ba3ec6b8fa5c2b8f6e4bfab18b2b51ecc94586a41a1dd80290665913412266",
"sig": "304402205d9fb1e3c6ea1b18849cbb2666ba5d5c67bffa78d6952e7b0f009226db6eac85022043cecaab697ed0684d53b497ac61ae614477ccc153c87f938e5a3302bb70d59f01",
"privkey": "a62d04ca999d3b5311f317edec9418fbb065805872b25dcf69c088ad7d3b9d11"
},
{
"msg": "63c7a73d7d35dd73b94d23b446bde100dc15e3544222b9c7e6c9d0be84b1f5d6",
"sig": "30450221008043f2ee24182f28ecfc6c53cc3a51ae6da4ab902ca4dbcbcc629f3480b9a97c022052e44edd783f81cae9bcbf4e7185da17a82c09894bc44b0405392aa00000f85901",
"privkey": "57b66d323c278d6aa3799db509d1909da6d7f69385b2627f8785712777f8236d"
},
{
"msg": "2ce5f3aff72b0867bc0b7c286dd49f011f65eb563a93cafef5d554ef3d9c1488",
"sig": "304402206b1cacf8fcc9e813f421f88ec8541c04de7f00430e1332829ded5ff73f218835022055b9da3bc1de4ed413f35765529c1df9d5b9b8e8ba1fbc7507c6e8fd747b191601",
"privkey": "81db6a86b8e6d840a97da1969d5ccb9b9eb4f9d36f6e59df65bcd42b9f3ef12b"
},
{
"msg": "475ab4796a2038629a3b9f43a14d5a2c86467bc1e32ba654e87ba67ff99d2890",
"sig": "304402207d3375beafa30381e9ec3b32b0ab819d67c7f63c61df796993085ed9f61a1a100220050e89b744e94e4d7301701d602886a39e0529740c24f6a0f8bed0d91cc01ed501",
"privkey": "8b51025a732cd22a179591598414b66bc370295c1d35284f706d468de4684dda"
},
{
"msg": "af1a72105095a243242132ad24085c444fc78bce26de4421467266d248536b87",
"sig": "3044022025accec34080896b7ef072474e0096fc58ae84bd73f045027007925dbb7fb5e002205602c0a5cce0b4d9e10a3009f9dca9d2599b694b6ef0e7c4360b08d35fbd607001",
"privkey": "5f0cc5d1b7996f0187a0e1c709981dc6dc944e92f908367eebd82f1babd2ea63"
},
{
"msg": "029b0efbdf0374c6a23593cd7c6917a9404b9fa43ed32428a82be05bc7f2fc3a",
"sig": "304402205fee5f55838ab4f7f7551b64ed346565f64dd5b42e2694d7c372233db2fae37d0220026e8f5d54af25529d7ca1a4991a1385e7ea3ea9fb37db826d81aa32b0aeadbd01",
"privkey": "b7906b63b11eeeb3f69586b7a8323aab9f4dd51c157f813d24f8e34407eb5c18"
},
{
"msg": "9db7ee956f52eba9fbfabcf840d9c107117a00f5e2874eeb54a91025951d2ea9",
"sig": "304402201fed017ec208e65cca442552269487cd15961c2603f6aa19fd035491cd4fa09602200d0c5c9ac89dd501c34f3b7403a82832f58153bb77d070c44fd8f9396dd0c2b401",
"privkey": "3e89238cb4a7de25f833f4b3cd3374cd5687d9cbb858de906a570818e14031db"
},
{
"msg": "b502771c17443b8835fd6582fb2b44512b66f9bbe92dbed5e710b76fe35bcc56",
"sig": "3045022100a424be7d1cad14f52a4bc99736635e3130dceff4f9321dc3eec31915a99d365202202c573a3a8d2d24a79ae70ea15e32e0a79819406edcf54bcbae4d49ff87512c8001",
"privkey": "6a4aa42e630ede3342539fae35f43c6fa02c94c634753adcc2130de76e62dafe"
},
{
"msg": "5c66cbe4059db5bc8046fc393266d2db410a81ec2a02ecae0368aa0eb35d68c6",
"sig": "304302204203bed95a0e92b6a4b6776551e01473b891487b051824cf707ca553b40972c7021f64f496fab45d743ffa95a520a7a6f5e98c49379e69f0b8a7dbeb98b576215001",
"privkey": "d7e3eb80f852de183837573ad3aef624e16e8302f7d0d4b4d5ff4abf9063e438"
},
{
"msg": "12e16733960b2c7d5de1f06b824094bd116ee9ed83b680233298be7cead86741",
"sig": "304502210091d303cb2e80690531645c79743cdb29906fe6119023844b8c66c81d39c9e299022021e6614ab44c6da7bba1d7de4b37871cfaea7a76df43bb5b90ffc6f61fa7e86f01",
"privkey": "0686905b967248099704688e15d8e3eeeeb58ff24a06e8c7a0e17b534a4714f9"
},
{
"msg": "2309bde84fa5fd4ed530fa7ba01af3dbb71e7967df1792a53b13bfa8225dca77",
"sig": "3045022100e8f17e0c541518d5eaa7ddfe24d7caec497783da37b7605522039024bcb835a2022075d5ef13cef091ebb861006299cac29fa762c2d635b3be798649633e4d8e0cf301",
"privkey": "4ded96d9315bf52be4ef30a2295d76a3c6a997a331555bebb5012491b6d315a5"
},
{
"msg": "1453bb7e9be2757cfac795c751dcac6ea24a1a597d8ec4b5c0131142a985e17e",
"sig": "304402201a686a1eca60be4ec2d95f22765af2afe91a6493ae70dee9ec0ab8915792c02a022071aa6f6f48f2c04e1a56e116eca35cb36866595561cd10e75bfdaf7f08f2af2e01",
"privkey": "6d9ed1aae9dc352667d4feb5e3f41f51da9ef9eb56a58b2497d502779680ac4a"
},
{
"msg": "863f7f5d7f0619816d65ef3eac2121c9d3b3a6980b044074652f99c70abf539d",
"sig": "304402200fcb08df78fd9aa098d627f6683faaf6535cb4452ac075a228ee72a3e5cdb16b02206936c264990abb530ab705249e564bb4bfa7227edbb71c5686387f7d82ec251501",
"privkey": "34f87676d627e5ce30cec37ca8b7add7b30e82f3e510b8ebac31fcedcabdb22a"
},
{
"msg": "9d3469873f54a1c26498eaffa9fc7c49644664cc4b25297fefc1b1b8429f6674",
"sig": "304402206735289ce27cf7e55251526382a07eb9e4a132971834082002d4464faf012c1602204a6c9b451f9ad560bd5c10561a551f33b712d553dd209b5e9f12fec46c2ef9f201",
"privkey": "8b4bf88a79deedef116259d5dcc331e4e566c04f1581c2ce0d02e6e2f15b1ce0"
},
{
"msg": "f479dd8ca6bef32889a4a0159462eb2b56e387b6ad15f8ae0b09e1f489d49cb3",
"sig": "3044022046df345f1bacaee73ab2157bcbe6e1b333d2abbe5cce53fb5a50f81c952caead022071ef3cab8a146da86366c1f79ccc7edc76bc630ab0e30956305ae041b908be7701",
"privkey": "d3259f9685535af057328f4b4aba66311fa654da96b01a4161749f12d27fdf60"
},
{
"msg": "9b4ecdd6c6fd1a43ba3cdbf534a8eb0159d0216ccbff47c3e41665776f00aeba",
"sig": "3045022100e7f0b77b9f767b6cfcf33ecf00a5dde22fc7fb46f87a3289f09501dfd889a0360220645639cfb7bcf062d3bc62a81c27c79d6909758f2ee37b08032b2351988a7b5601",
"privkey": "3a6f3397de4bedee3cce0568298f039d97ef8f4ea1d3d136e6c857cc3cf477a4"
},
{
"msg": "bc64a2216423e642a30a0f1d790f3cec68b71f06618494723541d3c778237c5e",
"sig": "3045022100f85ea06b50d834109e5a0cb1e4f506e86cddb875867a9c3b28b48b1ae086589c02206e875f5abce1a66c30cb082243dbd9ceca233ccfc758cf91bd16cbf08648f98501",
"privkey": "3783268c39a70a6ba824e2adbaa07cb11f94b39b63cc3e8d53cf3603042c5fd3"
},
{
"msg": "3962c385ea39d4df584dc1d9b93c1424bce2bef2aebff1a26dc7ed181e57ca50",
"sig": "3044022043d17ebe7a376f79dd2ecd1b745548d9633e5c7871dd521f922af941c7ad47cb02203190a04501956eb66840ff7f7af13fb492e51b59ac981a6ae9e43fb06552365401",
"privkey": "c26e9a48223b3a3966495620a14db81803ffb6f3b3a69932b72b65f52b46ca80"
},
{
"msg": "f87d0fe24e507120efd2d10aeed2905131d296d50e688684d1bb847e68c02bef",
"sig": "3045022100fe995bc6c1bc4e6f60fd8054baa3685cb09344fb83b18496f0a74da81a4ca46d02203ae1bed24bee86d68bad7501091593c5db3442b6f21fe02445ff14a0574d98af01",
"privkey": "53dc1c26dcbcd6220e7329e5d12fcf17d838b9f8936333b03d0abed3cfd3ac2d"
},
{
"msg": "62d97d2a26ab35d9e2d18e4335f7e45665a712eb65a1b2da9c2824339c4f9780",
"sig": "3045022100e8542c42cb929f670c2c7e5faf2b46ff9a02852704e91e1a79097610476ed4f302206d9aa52a7b886fb4062a87ffb587e54c0c2a5a7ed1c12dcf112123e1b827b51401",
"privkey": "6742a01cbff6a2e83a4f68a338c6f0c0995f6cac6e842c6f06de8337fc679f19"
},
{
"msg": "364f0f3c7f17aab5567cb6ab0d9a364a59ad8ff294a006eb89193396618ff58e",
"sig": "3045022100ec728047c10274e6c1f3ee2404a9bfb5d58beebed4ed7d0c80901926a2c5caa4022027f1905073e6730963b440c22e150504b47ae4b02b10dde1aa2177a169a80c9a01",
"privkey": "f2415dd427697296d40dfc72084df1249b04d33cedbc880df1d9a6c88917dd9d"
},
{
"msg": "d0080a4d6d945980321e346feefa6d8056cde94b974210dc2064837d8f47ebaf",
"sig": "3045022100a81edb49b4d7544d27a5cd7e112b4caf58898cb83d76740934c151e79566f97802200156257235e64803ad6b15ee58d0eb7a103fbe1f6ea024fc25420db1d4b22b5b01",
"privkey": "a7ba1b4480cf766d833940311d8a8c59a22d761858443412a94aa3407e330c16"
},
{
"msg": "f09feb58a0f48e1678434d3995d12185a9d340baf3d19902a4680a532456cd20",
"sig": "3045022100df54f5a3f4791b77107c11ac15b0ed7ac59cdc8a46234031f16317d1dfba946402203d768943db33aa775d86000428699ac45e683d9d0b4b5ecd47b99ec7c548709601",
"privkey": "ecc32707de9576a9e91fa013190253e12ef780a5d30a0b0806da2ac05678eb6e"
},
{
"msg": "2e8e25a6cf4c689b3560007bbe26a3df37dbf0eb5562c68b8efcb42fd5760f1b",
"sig": "304402206d2f9b5b37afa234eabc8e9905f7094c56cb936b038abce5166a0e35b82ff9ad02206e0060d1b784e880f517a5d8607c77a82e93d6dab3c3b0ceffbd9e632ed3c78d01",
"privkey": "bd42ee836125cdb5cc41ef767933be53fd374924441c4ef829b18c88ae1340f8"
},
{
"msg": "5d659bd8337e119afa182ead19ef91f5d2f4dc6f5849e8fc56b4e6045204679a",
"sig": "304402203ebb171ab9732a29344b2c5474f46068a7466c6db0694ce2935bb85f5a6cb57602201b80113965c0670f3b0065b0c3a527b89a4c328865362767d8fdd6d5c97258a201",
"privkey": "17324fd463b6990e89218846d24f13813e415ac8c29faa2c6e7b2a6169e32e8c"
},
{
"msg": "1cb65e2253db80412c92e092db5864c2074fae2e0800ee213b4ee82d69d639d4",
"sig": "304402205c216ab6a7cb4bb9d33360999d2e00ac6b7c6e956150db10865ba8fcb87cda90022064fceefaee8c40a8f5ed8cee478d45d0e5da1a
gitextract_tbfrxx9v/
├── .codecov.yml
├── .conda/
│ ├── environment-dev.yml
│ └── environment.yml
├── .gemini/
│ └── config.yaml
├── .gitattributes
├── .github/
│ ├── FUNDING.yml
│ ├── dependabot.yml
│ ├── scripts/
│ │ └── install-macos-build-deps.sh
│ └── workflows/
│ ├── build.yml
│ ├── docs.yml
│ ├── verify_conda_build.yml
│ └── verify_shared_build.yml
├── .gitignore
├── .linkcheckerrc
├── CMakeLists.txt
├── LICENSE-APACHE
├── LICENSE-MIT
├── NOTICE
├── README.md
├── cm_library_c_binding/
│ ├── CMakeLists.txt
│ └── build.py
├── cm_library_cffi_headers/
│ ├── CMakeLists.txt
│ └── compose_cffi_headers.py
├── cm_python_module/
│ └── CMakeLists.txt
├── cm_vendored_library/
│ └── CMakeLists.txt
├── cmake/
│ ├── SetCrossCompilerGithubActions.cmake
│ ├── SetDefaultVendoredLibrary.cmake
│ ├── SetSystemLibIfExists.cmake
│ ├── UnsetVendoredLibraryOptions.cmake
│ ├── UpdateVendoredLibraryOptions.cmake
│ └── VerifyPythonModule.cmake
├── docs/
│ ├── .snippets/
│ │ ├── abbrs.txt
│ │ └── links.txt
│ ├── api.md
│ ├── assets/
│ │ └── css/
│ │ └── custom.css
│ ├── benchmarks.md
│ ├── history.md
│ ├── index.md
│ ├── install.md
│ └── users.md
├── hatch.toml
├── hatch_build.py
├── mkdocs.yml
├── pyproject.toml
├── ruff.toml
├── ruff_defaults.toml
├── scripts/
│ ├── README.md
│ └── bench.py
├── src/
│ └── coincurve/
│ ├── __init__.py
│ ├── context.py
│ ├── der.py
│ ├── ecdsa.py
│ ├── flags.py
│ ├── keys.py
│ ├── py.typed
│ ├── types.py
│ └── utils.py
└── tests/
├── __init__.py
├── conftest.py
├── data/
│ ├── ecdsa_sig.json
│ └── pubkey.json
├── test_ecdsa.py
├── test_flags.py
├── test_keys.py
└── test_utils.py
SYMBOL INDEX (179 symbols across 14 files)
FILE: cm_library_c_binding/build.py
class Source (line 15) | class Source(NamedTuple):
function gather_sources_from_directory (line 20) | def gather_sources_from_directory(directory: str) -> list[Source]:
function mk_ffi (line 42) | def mk_ffi(
FILE: cm_library_cffi_headers/compose_cffi_headers.py
function remove_c_comments_emptylines (line 10) | def remove_c_comments_emptylines(text):
function remove_c_includes (line 16) | def remove_c_includes(lines):
function remove_special_defines (line 20) | def remove_special_defines(lines, defines):
function apply_cffi_defines_syntax (line 24) | def apply_cffi_defines_syntax(lines):
function remove_c_ifdef (line 28) | def remove_c_ifdef(lines):
function concatenate_c_defines (line 51) | def concatenate_c_defines(lines):
function remove_deprecated_functions (line 78) | def remove_deprecated_functions(lines, deprecation):
function remove_function_attributes (line 113) | def remove_function_attributes(lines, attributes):
function remove_header_guard (line 137) | def remove_header_guard(lines, keywords):
function concatenate_c_struct (line 151) | def concatenate_c_struct(lines):
function make_header_cffi_compliant (line 175) | def make_header_cffi_compliant(src_header_dir, src_header, cffi_dir):
FILE: hatch_build.py
class CustomBuildHook (line 13) | class CustomBuildHook(BuildHookInterface):
method local_cffi_license (line 22) | def local_cffi_license(self) -> str:
method get_cffi_distribution_license_files (line 26) | def get_cffi_distribution_license_files() -> list[PackagePath]:
method initialize (line 37) | def initialize(self, version: str, build_data: dict[str, Any]) -> None...
method finalize (line 51) | def finalize(self, version: str, build_data: dict[str, Any], artifact:...
FILE: scripts/bench.py
class BenchmarkSpec (line 24) | class BenchmarkSpec:
method __init__ (line 27) | def __init__(self, setup: str, statement: str):
class Benchmark (line 32) | class Benchmark(ABC):
method name (line 35) | def name() -> str:
method generate_key_pair (line 40) | def generate_key_pair() -> BenchmarkSpec:
method sign (line 45) | def sign() -> BenchmarkSpec:
method verify (line 50) | def verify() -> BenchmarkSpec:
method key_export (line 55) | def key_export() -> BenchmarkSpec:
method key_import (line 60) | def key_import() -> BenchmarkSpec:
class CoincurveBenchmark (line 64) | class CoincurveBenchmark(Benchmark):
method name (line 66) | def name() -> str:
method generate_key_pair (line 70) | def generate_key_pair() -> BenchmarkSpec:
method sign (line 81) | def sign() -> BenchmarkSpec:
method verify (line 94) | def verify() -> BenchmarkSpec:
method key_export (line 109) | def key_export() -> BenchmarkSpec:
method key_import (line 121) | def key_import() -> BenchmarkSpec:
class FastecdsaBenchmark (line 134) | class FastecdsaBenchmark(Benchmark):
method name (line 136) | def name() -> str:
method generate_key_pair (line 140) | def generate_key_pair() -> BenchmarkSpec:
method sign (line 151) | def sign() -> BenchmarkSpec:
method verify (line 164) | def verify() -> BenchmarkSpec:
method key_export (line 178) | def key_export() -> BenchmarkSpec:
method key_import (line 192) | def key_import() -> BenchmarkSpec:
function generate_table (line 207) | def generate_table(rows: list[list[str]]):
function main (line 222) | def main():
FILE: src/coincurve/context.py
class Context (line 10) | class Context:
method __init__ (line 11) | def __init__(self, seed: bytes | None = None, flag=CONTEXT_NONE, name:...
method reseed (line 22) | def reseed(self, seed: bytes | None = None):
method __repr__ (line 33) | def __repr__(self):
FILE: src/coincurve/der.py
function encode_length (line 66) | def encode_length(length: int) -> bytes:
function encode_octet_string (line 77) | def encode_octet_string(value: bytes) -> bytes:
function encode_bit_string (line 88) | def encode_bit_string(value: bytes, unused_bits: int = 0) -> bytes:
function encode_der (line 100) | def encode_der(private_key: bytes, public_key: bytes | None = None) -> b...
function decode_length (line 154) | def decode_length(data: bytes, offset: int) -> tuple[int, int]:
function decode_der (line 181) | def decode_der(der_data: bytes) -> bytes:
FILE: src/coincurve/ecdsa.py
function cdata_to_der (line 16) | def cdata_to_der(cdata, context: Context = GLOBAL_CONTEXT) -> bytes:
function der_to_cdata (line 25) | def der_to_cdata(der: bytes, context: Context = GLOBAL_CONTEXT):
function recover (line 36) | def recover(message: bytes, recover_sig, hasher: Hasher = sha256, contex...
function serialize_recoverable (line 50) | def serialize_recoverable(recover_sig, context: Context = GLOBAL_CONTEXT...
function deserialize_recoverable (line 59) | def deserialize_recoverable(serialized: bytes, context: Context = GLOBAL...
function serialize_compact (line 86) | def serialize_compact(raw_sig, context: Context = GLOBAL_CONTEXT): # no...
function deserialize_compact (line 97) | def deserialize_compact(ser_sig: bytes, context: Context = GLOBAL_CONTEX...
function signature_normalize (line 111) | def signature_normalize(raw_sig, context: Context = GLOBAL_CONTEXT): # ...
function recoverable_convert (line 126) | def recoverable_convert(recover_sig, context: Context = GLOBAL_CONTEXT):...
FILE: src/coincurve/keys.py
class PrivateKey (line 28) | class PrivateKey:
method __init__ (line 29) | def __init__(self, secret: bytes | None = None, context: Context = GLO...
method sign (line 43) | def sign(self, message: bytes, hasher: Hasher = sha256, custom_nonce: ...
method sign_schnorr (line 78) | def sign_schnorr(self, message: bytes, aux_randomness: bytes = b"") ->...
method sign_recoverable (line 126) | def sign_recoverable(self, message: bytes, hasher: Hasher = sha256, cu...
method ecdh (line 163) | def ecdh(self, public_key: bytes) -> bytes:
method add (line 186) | def add(self, scalar: bytes, update: bool = False) -> PrivateKey: # n...
method multiply (line 219) | def multiply(self, scalar: bytes, update: bool = False) -> PrivateKey:...
method to_hex (line 245) | def to_hex(self) -> str:
method to_int (line 251) | def to_int(self) -> int:
method to_pem (line 257) | def to_pem(self) -> bytes:
method to_der (line 263) | def to_der(self) -> bytes:
method from_hex (line 270) | def from_hex(cls, hexed: str, context: Context = GLOBAL_CONTEXT) -> Pr...
method from_int (line 284) | def from_int(cls, num: int, context: Context = GLOBAL_CONTEXT) -> Priv...
method from_pem (line 298) | def from_pem(cls, pem: bytes, context: Context = GLOBAL_CONTEXT) -> Pr...
method from_der (line 312) | def from_der(cls, der: bytes, context: Context = GLOBAL_CONTEXT) -> Pr...
method _update_public_key (line 325) | def _update_public_key(self):
method __eq__ (line 332) | def __eq__(self, other) -> bool:
method __hash__ (line 335) | def __hash__(self) -> int:
class PublicKey (line 339) | class PublicKey:
method __init__ (line 340) | def __init__(self, data: bytes | ffi.CData, context: Context = GLOBAL_...
method from_secret (line 370) | def from_secret(cls, secret: bytes, context: Context = GLOBAL_CONTEXT)...
method from_valid_secret (line 399) | def from_valid_secret(cls, secret: bytes, context: Context = GLOBAL_CO...
method from_point (line 424) | def from_point(cls, x: int, y: int, context: Context = GLOBAL_CONTEXT)...
method from_signature_and_message (line 439) | def from_signature_and_message(
method combine_keys (line 464) | def combine_keys(cls, public_keys: list[PublicKey], context: Context =...
method format (line 490) | def format(self, compressed: bool = True) -> bytes: # noqa: FBT001, F...
method point (line 511) | def point(self) -> tuple[int, int]:
method verify (line 518) | def verify(self, signature: bytes, message: bytes, hasher: Hasher = sh...
method add (line 545) | def add(self, scalar: bytes, update: bool = False) -> PublicKey: # no...
method multiply (line 575) | def multiply(self, scalar: bytes, update: bool = False) -> PublicKey: ...
method combine (line 598) | def combine(self, public_keys: list[PublicKey], update: bool = False) ...
method __eq__ (line 628) | def __eq__(self, other) -> bool:
method __hash__ (line 631) | def __hash__(self) -> int:
class PublicKeyXOnly (line 635) | class PublicKeyXOnly:
method __init__ (line 636) | def __init__(self, data: bytes | ffi.CData, parity: bool = False, cont...
method from_secret (line 663) | def from_secret(cls, secret: bytes, context: Context = GLOBAL_CONTEXT)...
method from_valid_secret (line 690) | def from_valid_secret(cls, secret: bytes, context: Context = GLOBAL_CO...
method format (line 716) | def format(self) -> bytes:
method verify (line 735) | def verify(self, signature: bytes, message: bytes) -> bool:
method tweak_add (line 757) | def tweak_add(self, scalar: bytes) -> None:
method __eq__ (line 782) | def __eq__(self, other) -> bool:
method __hash__ (line 786) | def __hash__(self) -> int:
FILE: src/coincurve/utils.py
function sha256 (line 30) | def sha256(bytestr: bytes) -> bytes:
class __Nonce (line 35) | class __Nonce(tuple): # noqa: SLOT001
method __repr__ (line 36) | def __repr__(self) -> str:
class __HasherSHA256 (line 39) | class __HasherSHA256:
method __call__ (line 40) | def __call__(self, bytestr: bytes) -> bytes:
method __repr__ (line 43) | def __repr__(self) -> str:
function pad_hex (line 50) | def pad_hex(hexed: str) -> str:
function bytes_to_int (line 55) | def bytes_to_int(bytestr: bytes) -> int:
function int_to_bytes (line 59) | def int_to_bytes(num: int) -> bytes:
function int_to_bytes_padded (line 63) | def int_to_bytes_padded(num: int) -> bytes:
function hex_to_bytes (line 67) | def hex_to_bytes(hexed: str) -> bytes:
function chunk_data (line 71) | def chunk_data(data: bytes, size: int) -> Generator[bytes, None, None]:
function der_to_pem (line 75) | def der_to_pem(der: bytes) -> bytes:
function pem_to_der (line 79) | def pem_to_der(pem: bytes) -> bytes:
function get_valid_secret (line 83) | def get_valid_secret() -> bytes:
function pad_scalar (line 90) | def pad_scalar(scalar: bytes) -> bytes:
function validate_secret (line 94) | def validate_secret(secret: bytes) -> bytes:
function verify_signature (line 101) | def verify_signature(
FILE: tests/conftest.py
function samples (line 59) | def samples():
FILE: tests/test_ecdsa.py
function test_der (line 6) | def test_der(samples):
FILE: tests/test_flags.py
function test_context_flags (line 9) | def test_context_flags():
function test_context_none (line 16) | def test_context_none():
function test_ec_compressed (line 21) | def test_ec_compressed():
function test_ec_uncompressed (line 26) | def test_ec_uncompressed():
FILE: tests/test_keys.py
class TestPrivateKey (line 19) | class TestPrivateKey:
method test_public_key (line 20) | def test_public_key(self, samples):
method test_xonly_pubkey (line 23) | def test_xonly_pubkey(self, samples):
method test_signature_correct (line 28) | def test_signature_correct(self):
method test_signature_deterministic (line 38) | def test_signature_deterministic(self, samples):
method test_signature_invalid_hasher (line 41) | def test_signature_invalid_hasher(self, samples):
method test_signature_recoverable (line 45) | def test_signature_recoverable(self, samples):
method test_schnorr_signature (line 57) | def test_schnorr_signature(self):
method test_to_hex (line 73) | def test_to_hex(self, samples):
method test_to_int (line 76) | def test_to_int(self, samples):
method test_to_pem (line 79) | def test_to_pem(self, samples):
method test_to_der (line 82) | def test_to_der(self, samples):
method test_from_hex (line 85) | def test_from_hex(self, samples):
method test_from_int (line 88) | def test_from_int(self, samples):
method test_from_pem (line 91) | def test_from_pem(self, samples):
method test_from_der (line 94) | def test_from_der(self, samples):
method test_ecdh (line 97) | def test_ecdh(self):
method test_add (line 103) | def test_add(self):
method test_add_update (line 106) | def test_add_update(self):
method test_multiply (line 113) | def test_multiply(self):
method test_multiply_update (line 116) | def test_multiply_update(self):
class TestPublicKey (line 124) | class TestPublicKey:
method test_from_secret (line 125) | def test_from_secret(self, samples):
method test_from_point (line 128) | def test_from_point(self, samples):
method test_from_signature_and_message (line 133) | def test_from_signature_and_message(self, samples):
method test_format (line 139) | def test_format(self, samples):
method test_point (line 147) | def test_point(self, samples):
method test_verify (line 153) | def test_verify(self, samples):
method test_transform (line 157) | def test_transform(self):
method test_combine (line 164) | def test_combine(self):
class TestXonlyPubKey (line 171) | class TestXonlyPubKey:
method test_parse_invalid (line 172) | def test_parse_invalid(self, samples):
method test_roundtrip (line 181) | def test_roundtrip(self, samples):
method test_tweak (line 188) | def test_tweak(self):
method test_parity (line 195) | def test_parity(self):
FILE: tests/test_utils.py
class TestPadScalar (line 22) | class TestPadScalar:
method test_correct (line 23) | def test_correct(self):
method test_pad_limit (line 26) | def test_pad_limit(self):
method test_empty_scalar (line 30) | def test_empty_scalar(self):
function test_get_valid_secret (line 34) | def test_get_valid_secret():
class TestValidateSecret (line 40) | class TestValidateSecret:
method test_valid (line 41) | def test_valid(self):
method test_bytes_greater_than_group_order (line 46) | def test_bytes_greater_than_group_order(self):
method test_out_of_range (line 56) | def test_out_of_range(self):
function test_bytes_int_conversion (line 64) | def test_bytes_int_conversion():
function test_bytes_int_conversion_padded (line 69) | def test_bytes_int_conversion_padded():
function test_der_conversion (line 74) | def test_der_conversion(samples):
function test_verify_signature (line 78) | def test_verify_signature(samples):
function test_chunk_data (line 83) | def test_chunk_data():
Condensed preview — 65 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (388K chars).
[
{
"path": ".codecov.yml",
"chars": 217,
"preview": "comment:\n layout: \"diff, files\"\n behavior: default\n\ncoverage:\n range: 50..100\n round: down\n precision: 2\n\n status:"
},
{
"path": ".conda/environment-dev.yml",
"chars": 206,
"preview": "name: coincurve-with-conda-dev\nchannels:\n - conda-forge\n - defaults\ndependencies:\n - cffi >=1.3.0\n - cmake\n - libse"
},
{
"path": ".conda/environment.yml",
"chars": 130,
"preview": "name: coincurve-with-conda\nchannels:\n - conda-forge\n - defaults\ndependencies:\n - libsecp256k1\n - cffi >=1.3.0\n - as"
},
{
"path": ".gemini/config.yaml",
"chars": 183,
"preview": "# https://developers.google.com/gemini-code-assist/docs/customize-gemini-behavior-github\nhave_fun: false\ncode_review:\n "
},
{
"path": ".gitattributes",
"chars": 378,
"preview": "# Auto detect text files and perform LF normalization\n* text=auto\n\n# Custom for Visual Studio\n*.cs diff=csharp\n\n# St"
},
{
"path": ".github/FUNDING.yml",
"chars": 78,
"preview": "github:\n- ofek\ncustom:\n- https://ofek.dev/donate/\n- https://paypal.me/ofeklev\n"
},
{
"path": ".github/dependabot.yml",
"chars": 111,
"preview": "version: 2\nupdates:\n- package-ecosystem: \"github-actions\"\n directory: \"/\"\n schedule:\n interval: \"monthly\"\n"
},
{
"path": ".github/scripts/install-macos-build-deps.sh",
"chars": 309,
"preview": "#!/bin/bash\nset -ex\n\n# update brew\nbrew update\n\n# Update openssl if necessary\nbrew outdated openssl || brew upgrade open"
},
{
"path": ".github/workflows/build.yml",
"chars": 6847,
"preview": "name: build\n\non:\n push:\n tags:\n - v*\n branches:\n - master\n pull_request:\n branches:\n - master\n\nconcu"
},
{
"path": ".github/workflows/docs.yml",
"chars": 1389,
"preview": "name: docs\n\non:\n push:\n branches:\n - master\n pull_request:\n branches:\n - master\n\njobs:\n build:\n runs-o"
},
{
"path": ".github/workflows/verify_conda_build.yml",
"chars": 2529,
"preview": "name: conda_build\n\non:\n push:\n tags:\n - v*\n branches:\n - master\n pull_request:\n branches:\n - master\n"
},
{
"path": ".github/workflows/verify_shared_build.yml",
"chars": 3229,
"preview": "name: shared_build\n\non:\n push:\n tags:\n - v*\n branches:\n - master\n pull_request:\n branches:\n - master"
},
{
"path": ".gitignore",
"chars": 103,
"preview": "*.log\n*.pyc\n/.cache\n/.coverage\n/.eggs\n/.idea\n/.mypy_cache\n/.tox\n/coincurve.egg-info\n/build\n/dist\n/site\n"
},
{
"path": ".linkcheckerrc",
"chars": 14,
"preview": "[AnchorCheck]\n"
},
{
"path": "CMakeLists.txt",
"chars": 1709,
"preview": "cmake_minimum_required(VERSION 3.26)\n\nproject(${SKBUILD_PROJECT_NAME}\n VERSION ${SKBUILD_PROJECT_VERSION}\n LANGUAG"
},
{
"path": "LICENSE-APACHE",
"chars": 9748,
"preview": "Copyright 2017 Ofek Lev\n\n Apache License\n Version 2.0, January 2004\n"
},
{
"path": "LICENSE-MIT",
"chars": 1065,
"preview": "MIT License\n\nCopyright (c) 2017 Ofek Lev\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no"
},
{
"path": "NOTICE",
"chars": 455,
"preview": "This package is dual-licensed under MIT or Apache-2.0.\n\nThe final distribution includes the following compiled artifacts"
},
{
"path": "README.md",
"chars": 2413,
"preview": "# coincurve\n\n| | |\n| --- | --- |\n| CI/CD | [\n\nif (NOT CFFI_C_CODE)\n set(CFFI_"
},
{
"path": "cm_library_c_binding/build.py",
"chars": 3033,
"preview": "from __future__ import annotations\n\nimport argparse\nimport logging\nimport os\nfrom typing import NamedTuple\n\nfrom cffi im"
},
{
"path": "cm_library_cffi_headers/CMakeLists.txt",
"chars": 1585,
"preview": "# create folder for _gen code\nfile(MAKE_DIRECTORY ${CFFI_HEADERS_DIR})\n\nmacro(generate_cffi_header src_header cffi_heade"
},
{
"path": "cm_library_cffi_headers/compose_cffi_headers.py",
"chars": 8411,
"preview": "import argparse\nimport logging\nimport os\nimport re\nimport sys\n\nlogging.basicConfig(level=logging.ERROR)\n\n\ndef remove_c_c"
},
{
"path": "cm_python_module/CMakeLists.txt",
"chars": 2797,
"preview": "# Create the shared library from the CFFI binding and the static library from ${CFFI_INPUT_LIBRARY}\nif (CMAKE_SYSTEM_NAM"
},
{
"path": "cm_vendored_library/CMakeLists.txt",
"chars": 2644,
"preview": "if (PROJECT_IGNORE_SYSTEM_LIB OR NOT VENDORED_AS_SYSTEM_LIB_FOUND)\n # Note that this could also be handled by: Extern"
},
{
"path": "cmake/SetCrossCompilerGithubActions.cmake",
"chars": 1294,
"preview": "function(SetCrossCompilerGithubActions)\n # Cross-compilation options: This is setup for Github/Actions runners\n # "
},
{
"path": "cmake/SetDefaultVendoredLibrary.cmake",
"chars": 965,
"preview": "function (SetDefaultVendoredLibrary)\n if (DEFINED VENDORED_LIBRARY_CMAKE_TARGET)\n set(VENDORED_LIBRARY_CMAKE_T"
},
{
"path": "cmake/SetSystemLibIfExists.cmake",
"chars": 468,
"preview": "function (SetSystemLibIfExists)\n set(ENV{PKG_CONFIG_PATH} \"$ENV{PKG_CONFIG_PATH};$ENV{CONDA_PREFIX}/Library/lib/pkgco"
},
{
"path": "cmake/UnsetVendoredLibraryOptions.cmake",
"chars": 273,
"preview": "function (UnsetVendoredLibraryOptions _prefix)\n get_cmake_property(_vars VARIABLES)\n string (REGEX MATCHALL \"(^|;)"
},
{
"path": "cmake/UpdateVendoredLibraryOptions.cmake",
"chars": 410,
"preview": "function (UpdateVendoredLibraryOptions _prefix _newPrefix)\n get_cmake_property(_vars VARIABLES)\n string (REGEX MAT"
},
{
"path": "cmake/VerifyPythonModule.cmake",
"chars": 453,
"preview": "# Verify CFFI python module is available\nfunction(VerifyPythonModule module python_executable)\n find_package(Python3 "
},
{
"path": "docs/.snippets/abbrs.txt",
"chars": 69,
"preview": "*[ECDH]: Elliptic-curve Diffie–Hellman\n*[PyPI]: Python Package Index\n"
},
{
"path": "docs/.snippets/links.txt",
"chars": 283,
"preview": "[Bitcoin Core]: https://github.com/bitcoin/bitcoin\n[ECDH]: https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93H"
},
{
"path": "docs/api.md",
"chars": 884,
"preview": "# Developer Interface\n\n-----\n\nAll objects are available directly under the root namespace `coincurve`.\n\n::: coincurve.ve"
},
{
"path": "docs/assets/css/custom.css",
"chars": 1672,
"preview": "/* Brighter links for dark mode */\n[data-md-color-scheme=slate] {\n /* https://github.com/squidfunk/mkdocs-material/blob"
},
{
"path": "docs/benchmarks.md",
"chars": 691,
"preview": "# Benchmarks\n\n-----\n\n## Setup\n\nDownload [Hatch](https://hatch.pypa.io/latest/install/) or [UV](https://docs.astral.sh/uv"
},
{
"path": "docs/history.md",
"chars": 4583,
"preview": "# History\n\n-----\n\nImportant changes are emphasized.\n\n## Unreleased\n\n## 21.0.0\n\n- **Breaking:** Drop support for Python 3"
},
{
"path": "docs/index.md",
"chars": 3180,
"preview": "# coincurve\n\n| | |\n| --- | --- |\n| CI/CD | [:\n\n```\npip i"
},
{
"path": "docs/users.md",
"chars": 8377,
"preview": "# Users\n\n-----\n\n## Organizations\n\n| Name | Projects |\n| --- | --- |\n| [Anyl](https://github.com/Anylsite) | <ul><li><a h"
},
{
"path": "hatch.toml",
"chars": 1206,
"preview": "[envs.default]\ninstaller = \"uv\"\ndev-mode = false\n\n[envs.hatch-static-analysis]\nconfig-path = \"ruff_defaults.toml\"\ndepend"
},
{
"path": "hatch_build.py",
"chars": 1884,
"preview": "from __future__ import annotations\n\nimport os\nimport shutil\nfrom functools import cached_property\nfrom importlib.metadat"
},
{
"path": "mkdocs.yml",
"chars": 4144,
"preview": "site_name: coincurve\nsite_description: Cross-platform Python bindings for libsecp256k1\nsite_author: Ofek Lev\nsite_url: h"
},
{
"path": "pyproject.toml",
"chars": 4262,
"preview": "[build-system]\nbuild-backend = \"hatchling.build\"\nrequires = [\n \"hatchling>=1.27.0\",\n \"cffi\",\n \"scikit-build-cor"
},
{
"path": "ruff.toml",
"chars": 248,
"preview": "extend = \"ruff_defaults.toml\"\n\n[format]\npreview = true\n\n[lint]\npreview = true\n\n[lint.extend-per-file-ignores]\n# Implicit"
},
{
"path": "ruff_defaults.toml",
"chars": 7755,
"preview": "line-length = 120\n\n[format]\ndocstring-code-format = true\ndocstring-code-line-length = 80\n\n[lint]\nselect = [\n \"A001\",\n "
},
{
"path": "scripts/README.md",
"chars": 88,
"preview": "# Scripts\n\n-----\n\nThis directory contains scripts that are used to develop the project.\n"
},
{
"path": "scripts/bench.py",
"chars": 6879,
"preview": "# /// script\n# dependencies = [\n# \"coincurve\",\n# \"fastecdsa==3.0.1; sys_platform != 'win32'\",\n# \"rich\",\n# ]\n# [too"
},
{
"path": "src/coincurve/__init__.py",
"chars": 320,
"preview": "from coincurve.context import GLOBAL_CONTEXT, Context\nfrom coincurve.keys import PrivateKey, PublicKey, PublicKeyXOnly\nf"
},
{
"path": "src/coincurve/context.py",
"chars": 1192,
"preview": "from __future__ import annotations\n\nfrom os import urandom\nfrom threading import Lock\n\nfrom coincurve._libsecp256k1 impo"
},
{
"path": "src/coincurve/der.py",
"chars": 8626,
"preview": "\"\"\"\nMinimal, dependency-free ASN.1/DER encoder & decoder for secp256k1 EC private keys.\n\nThis module implements just eno"
},
{
"path": "src/coincurve/ecdsa.py",
"chars": 4274,
"preview": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom coincurve._libsecp256k1 import ffi, lib\nfrom "
},
{
"path": "src/coincurve/flags.py",
"chars": 478,
"preview": "from __future__ import annotations\n\nfrom coincurve._libsecp256k1 import lib\n\nCONTEXT_NONE = lib.SECP256K1_CONTEXT_NONE\nC"
},
{
"path": "src/coincurve/keys.py",
"chars": 27015,
"preview": "from __future__ import annotations\n\nimport os\nfrom typing import TYPE_CHECKING\n\nfrom coincurve._libsecp256k1 import ffi,"
},
{
"path": "src/coincurve/py.typed",
"chars": 0,
"preview": ""
},
{
"path": "src/coincurve/types.py",
"chars": 192,
"preview": "from __future__ import annotations\n\nfrom collections.abc import Callable\n\nfrom coincurve._libsecp256k1 import ffi\n\nHashe"
},
{
"path": "src/coincurve/utils.py",
"chars": 4527,
"preview": "from __future__ import annotations\n\nfrom base64 import b64decode, b64encode\nfrom hashlib import sha256 as _sha256\nfrom o"
},
{
"path": "tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/conftest.py",
"chars": 3534,
"preview": "import pytest\n\nPRIVATE_KEY_BYTES = b\"\\xc2\\x8a\\x9f\\x80s\\x8fw\\rRx\\x03\\xa5f\\xcfo\\xc3\\xed\\xf6\\xce\\xa5\\x86\\xc4\\xfcJR#\\xa5\\xad"
},
{
"path": "tests/data/ecdsa_sig.json",
"chars": 73058,
"preview": "{\n \"vectors\": [\n {\n \"msg\": \"9e5755ec2f328cc8635a55415d0e9a09c2b6f2c9b0343c945fbbfe08247a4cbe\", \n "
},
{
"path": "tests/data/pubkey.json",
"chars": 127409,
"preview": "{\n \"vectors\": [\n {\n \"seckey\": \"7ccca75d019dbae79ac4266501578684ee64eeb3c9212105f7a3bdc0ddb0f27e\", \n"
},
{
"path": "tests/test_ecdsa.py",
"chars": 242,
"preview": "import pytest\n\nfrom coincurve.ecdsa import cdata_to_der, der_to_cdata\n\n\ndef test_der(samples):\n assert cdata_to_der(d"
},
{
"path": "tests/test_flags.py",
"chars": 577,
"preview": "from coincurve.flags import (\n CONTEXT_FLAGS,\n CONTEXT_NONE,\n EC_COMPRESSED,\n EC_UNCOMPRESSED,\n)\n\n\ndef test_"
},
{
"path": "tests/test_keys.py",
"chars": 8737,
"preview": "from hashlib import sha512\nfrom os import urandom\n\nimport pytest\n\nfrom coincurve.ecdsa import deserialize_recoverable, r"
},
{
"path": "tests/test_utils.py",
"chars": 2745,
"preview": "from os import urandom\n\nimport pytest\n\nfrom coincurve.utils import (\n GROUP_ORDER,\n GROUP_ORDER_INT,\n ZERO,\n "
}
]
About this extraction
This page contains the full source code of the ofek/coincurve GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 65 files (358.0 KB), approximately 144.7k tokens, and a symbol index with 179 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.