mirror of
https://github.com/bitcoin/bips.git
synced 2026-02-09 15:23:09 +00:00
Merge commit '3050bb6b25c0c20b62e2fc1a23276a09d50d151b' as 'bip-0374/secp256k1lab'
This commit is contained in:
commit
d2ceae1dd6
17
bip-0374/secp256k1lab/.github/workflows/main.yml
vendored
Normal file
17
bip-0374/secp256k1lab/.github/workflows/main.yml
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
name: Tests
|
||||||
|
on: [push, pull_request]
|
||||||
|
jobs:
|
||||||
|
ruff:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Install the latest version of uv
|
||||||
|
uses: astral-sh/setup-uv@v5
|
||||||
|
- run: uvx ruff check .
|
||||||
|
mypy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Install the latest version of uv
|
||||||
|
uses: astral-sh/setup-uv@v5
|
||||||
|
- run: uvx mypy .
|
||||||
1
bip-0374/secp256k1lab/.python-version
Normal file
1
bip-0374/secp256k1lab/.python-version
Normal file
@ -0,0 +1 @@
|
|||||||
|
3.9
|
||||||
10
bip-0374/secp256k1lab/CHANGELOG.md
Normal file
10
bip-0374/secp256k1lab/CHANGELOG.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.0.0] - 2025-03-31
|
||||||
|
|
||||||
|
Initial release.
|
||||||
23
bip-0374/secp256k1lab/COPYING
Normal file
23
bip-0374/secp256k1lab/COPYING
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2009-2024 The Bitcoin Core developers
|
||||||
|
Copyright (c) 2009-2024 Bitcoin Developers
|
||||||
|
Copyright (c) 2025- The secp256k1lab Developers
|
||||||
|
|
||||||
|
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.
|
||||||
13
bip-0374/secp256k1lab/README.md
Normal file
13
bip-0374/secp256k1lab/README.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
secp256k1lab
|
||||||
|
============
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
An INSECURE implementation of the secp256k1 elliptic curve and related cryptographic schemes written in Python, intended for prototyping, experimentation and education.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
* Low-level secp256k1 field and group arithmetic.
|
||||||
|
* Schnorr signing/verification and key generation according to [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki).
|
||||||
|
* ECDH key exchange.
|
||||||
|
|
||||||
|
WARNING: The code in this library is slow and trivially vulnerable to side channel attacks.
|
||||||
34
bip-0374/secp256k1lab/pyproject.toml
Normal file
34
bip-0374/secp256k1lab/pyproject.toml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
[project]
|
||||||
|
name = "secp256k1lab"
|
||||||
|
version = "1.0.0"
|
||||||
|
description = "An INSECURE implementation of the secp256k1 elliptic curve and related cryptographic schemes, intended for prototyping, experimentation and education"
|
||||||
|
readme = "README.md"
|
||||||
|
authors = [
|
||||||
|
{ name = "Pieter Wuille", email = "pieter@wuille.net" },
|
||||||
|
{ name = "Tim Ruffing", email = "me@real-or-random.org" },
|
||||||
|
{ name = "Jonas Nick", email = "jonasd.nick@gmail.com" },
|
||||||
|
{ name = "Sebastian Falbesoner", email = "sebastian.falbesoner@gmail.com" }
|
||||||
|
]
|
||||||
|
maintainers = [
|
||||||
|
{ name = "Tim Ruffing", email = "me@real-or-random.org" },
|
||||||
|
{ name = "Jonas Nick", email = "jonasd.nick@gmail.com" },
|
||||||
|
{ name = "Sebastian Falbesoner", email = "sebastian.falbesoner@gmail.com" }
|
||||||
|
]
|
||||||
|
requires-python = ">=3.9"
|
||||||
|
license = "MIT"
|
||||||
|
license-files = ["COPYING"]
|
||||||
|
keywords = ["secp256k1", "elliptic curves", "cryptography", "Bitcoin"]
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 5 - Production/Stable",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"Intended Audience :: Education",
|
||||||
|
"Intended Audience :: Science/Research",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Programming Language :: Python",
|
||||||
|
"Topic :: Security :: Cryptography",
|
||||||
|
]
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
0
bip-0374/secp256k1lab/src/secp256k1lab/__init__.py
Normal file
0
bip-0374/secp256k1lab/src/secp256k1lab/__init__.py
Normal file
73
bip-0374/secp256k1lab/src/secp256k1lab/bip340.py
Normal file
73
bip-0374/secp256k1lab/src/secp256k1lab/bip340.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
# The following functions are based on the BIP 340 reference implementation:
|
||||||
|
# https://github.com/bitcoin/bips/blob/master/bip-0340/reference.py
|
||||||
|
|
||||||
|
from .secp256k1 import FE, GE, G
|
||||||
|
from .util import int_from_bytes, bytes_from_int, xor_bytes, tagged_hash
|
||||||
|
|
||||||
|
|
||||||
|
def pubkey_gen(seckey: bytes) -> bytes:
|
||||||
|
d0 = int_from_bytes(seckey)
|
||||||
|
if not (1 <= d0 <= GE.ORDER - 1):
|
||||||
|
raise ValueError("The secret key must be an integer in the range 1..n-1.")
|
||||||
|
P = d0 * G
|
||||||
|
assert not P.infinity
|
||||||
|
return P.to_bytes_xonly()
|
||||||
|
|
||||||
|
|
||||||
|
def schnorr_sign(
|
||||||
|
msg: bytes, seckey: bytes, aux_rand: bytes, tag_prefix: str = "BIP0340"
|
||||||
|
) -> bytes:
|
||||||
|
d0 = int_from_bytes(seckey)
|
||||||
|
if not (1 <= d0 <= GE.ORDER - 1):
|
||||||
|
raise ValueError("The secret key must be an integer in the range 1..n-1.")
|
||||||
|
if len(aux_rand) != 32:
|
||||||
|
raise ValueError("aux_rand must be 32 bytes instead of %i." % len(aux_rand))
|
||||||
|
P = d0 * G
|
||||||
|
assert not P.infinity
|
||||||
|
d = d0 if P.has_even_y() else GE.ORDER - d0
|
||||||
|
t = xor_bytes(bytes_from_int(d), tagged_hash(tag_prefix + "/aux", aux_rand))
|
||||||
|
k0 = (
|
||||||
|
int_from_bytes(tagged_hash(tag_prefix + "/nonce", t + P.to_bytes_xonly() + msg))
|
||||||
|
% GE.ORDER
|
||||||
|
)
|
||||||
|
if k0 == 0:
|
||||||
|
raise RuntimeError("Failure. This happens only with negligible probability.")
|
||||||
|
R = k0 * G
|
||||||
|
assert not R.infinity
|
||||||
|
k = k0 if R.has_even_y() else GE.ORDER - k0
|
||||||
|
e = (
|
||||||
|
int_from_bytes(
|
||||||
|
tagged_hash(
|
||||||
|
tag_prefix + "/challenge", R.to_bytes_xonly() + P.to_bytes_xonly() + msg
|
||||||
|
)
|
||||||
|
)
|
||||||
|
% GE.ORDER
|
||||||
|
)
|
||||||
|
sig = R.to_bytes_xonly() + bytes_from_int((k + e * d) % GE.ORDER)
|
||||||
|
assert schnorr_verify(msg, P.to_bytes_xonly(), sig, tag_prefix=tag_prefix)
|
||||||
|
return sig
|
||||||
|
|
||||||
|
|
||||||
|
def schnorr_verify(
|
||||||
|
msg: bytes, pubkey: bytes, sig: bytes, tag_prefix: str = "BIP0340"
|
||||||
|
) -> bool:
|
||||||
|
if len(pubkey) != 32:
|
||||||
|
raise ValueError("The public key must be a 32-byte array.")
|
||||||
|
if len(sig) != 64:
|
||||||
|
raise ValueError("The signature must be a 64-byte array.")
|
||||||
|
try:
|
||||||
|
P = GE.from_bytes_xonly(pubkey)
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
r = int_from_bytes(sig[0:32])
|
||||||
|
s = int_from_bytes(sig[32:64])
|
||||||
|
if (r >= FE.SIZE) or (s >= GE.ORDER):
|
||||||
|
return False
|
||||||
|
e = (
|
||||||
|
int_from_bytes(tagged_hash(tag_prefix + "/challenge", sig[0:32] + pubkey + msg))
|
||||||
|
% GE.ORDER
|
||||||
|
)
|
||||||
|
R = s * G - e * P
|
||||||
|
if R.infinity or (not R.has_even_y()) or (R.x != r):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
16
bip-0374/secp256k1lab/src/secp256k1lab/ecdh.py
Normal file
16
bip-0374/secp256k1lab/src/secp256k1lab/ecdh.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import hashlib
|
||||||
|
|
||||||
|
from .secp256k1 import GE, Scalar
|
||||||
|
|
||||||
|
|
||||||
|
def ecdh_compressed_in_raw_out(seckey: bytes, pubkey: bytes) -> GE:
|
||||||
|
"""TODO"""
|
||||||
|
shared_secret = Scalar.from_bytes_checked(seckey) * GE.from_bytes_compressed(pubkey)
|
||||||
|
assert not shared_secret.infinity # prime-order group
|
||||||
|
return shared_secret
|
||||||
|
|
||||||
|
|
||||||
|
def ecdh_libsecp256k1(seckey: bytes, pubkey: bytes) -> bytes:
|
||||||
|
"""TODO"""
|
||||||
|
shared_secret = ecdh_compressed_in_raw_out(seckey, pubkey)
|
||||||
|
return hashlib.sha256(shared_secret.to_bytes_compressed()).digest()
|
||||||
15
bip-0374/secp256k1lab/src/secp256k1lab/keys.py
Normal file
15
bip-0374/secp256k1lab/src/secp256k1lab/keys.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from .secp256k1 import GE, G
|
||||||
|
from .util import int_from_bytes
|
||||||
|
|
||||||
|
# The following function is based on the BIP 327 reference implementation
|
||||||
|
# https://github.com/bitcoin/bips/blob/master/bip-0327/reference.py
|
||||||
|
|
||||||
|
|
||||||
|
# Return the plain public key corresponding to a given secret key
|
||||||
|
def pubkey_gen_plain(seckey: bytes) -> bytes:
|
||||||
|
d0 = int_from_bytes(seckey)
|
||||||
|
if not (1 <= d0 <= GE.ORDER - 1):
|
||||||
|
raise ValueError("The secret key must be an integer in the range 1..n-1.")
|
||||||
|
P = d0 * G
|
||||||
|
assert not P.infinity
|
||||||
|
return P.to_bytes_compressed()
|
||||||
0
bip-0374/secp256k1lab/src/secp256k1lab/py.typed
Normal file
0
bip-0374/secp256k1lab/src/secp256k1lab/py.typed
Normal file
454
bip-0374/secp256k1lab/src/secp256k1lab/secp256k1.py
Normal file
454
bip-0374/secp256k1lab/src/secp256k1lab/secp256k1.py
Normal file
@ -0,0 +1,454 @@
|
|||||||
|
# Copyright (c) 2022-2023 The Bitcoin Core developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
"""Test-only implementation of low-level secp256k1 field and group arithmetic
|
||||||
|
|
||||||
|
It is designed for ease of understanding, not performance.
|
||||||
|
|
||||||
|
WARNING: This code is slow and trivially vulnerable to side channel attacks. Do not use for
|
||||||
|
anything but tests.
|
||||||
|
|
||||||
|
Exports:
|
||||||
|
* FE: class for secp256k1 field elements
|
||||||
|
* GE: class for secp256k1 group elements
|
||||||
|
* G: the secp256k1 generator point
|
||||||
|
"""
|
||||||
|
|
||||||
|
# TODO Docstrings of methods still say "field element"
|
||||||
|
class APrimeFE:
|
||||||
|
"""Objects of this class represent elements of a prime field.
|
||||||
|
|
||||||
|
They are represented internally in numerator / denominator form, in order to delay inversions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The size of the field (also its modulus and characteristic).
|
||||||
|
SIZE: int
|
||||||
|
|
||||||
|
def __init__(self, a=0, b=1):
|
||||||
|
"""Initialize a field element a/b; both a and b can be ints or field elements."""
|
||||||
|
if isinstance(a, type(self)):
|
||||||
|
num = a._num
|
||||||
|
den = a._den
|
||||||
|
else:
|
||||||
|
num = a % self.SIZE
|
||||||
|
den = 1
|
||||||
|
if isinstance(b, type(self)):
|
||||||
|
den = (den * b._num) % self.SIZE
|
||||||
|
num = (num * b._den) % self.SIZE
|
||||||
|
else:
|
||||||
|
den = (den * b) % self.SIZE
|
||||||
|
assert den != 0
|
||||||
|
if num == 0:
|
||||||
|
den = 1
|
||||||
|
self._num = num
|
||||||
|
self._den = den
|
||||||
|
|
||||||
|
def __add__(self, a):
|
||||||
|
"""Compute the sum of two field elements (second may be int)."""
|
||||||
|
if isinstance(a, type(self)):
|
||||||
|
return type(self)(self._num * a._den + self._den * a._num, self._den * a._den)
|
||||||
|
if isinstance(a, int):
|
||||||
|
return type(self)(self._num + self._den * a, self._den)
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __radd__(self, a):
|
||||||
|
"""Compute the sum of an integer and a field element."""
|
||||||
|
return type(self)(a) + self
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
# REVIEW This should be
|
||||||
|
# def sum(cls, *es: Iterable[Self]) -> Self:
|
||||||
|
# but Self needs the typing_extension package on Python <= 3.12.
|
||||||
|
def sum(cls, *es):
|
||||||
|
"""Compute the sum of field elements.
|
||||||
|
|
||||||
|
sum(a, b, c, ...) is identical to (0 + a + b + c + ...)."""
|
||||||
|
return sum(es, start=cls(0))
|
||||||
|
|
||||||
|
def __sub__(self, a):
|
||||||
|
"""Compute the difference of two field elements (second may be int)."""
|
||||||
|
if isinstance(a, type(self)):
|
||||||
|
return type(self)(self._num * a._den - self._den * a._num, self._den * a._den)
|
||||||
|
if isinstance(a, int):
|
||||||
|
return type(self)(self._num - self._den * a, self._den)
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __rsub__(self, a):
|
||||||
|
"""Compute the difference of an integer and a field element."""
|
||||||
|
return type(self)(a) - self
|
||||||
|
|
||||||
|
def __mul__(self, a):
|
||||||
|
"""Compute the product of two field elements (second may be int)."""
|
||||||
|
if isinstance(a, type(self)):
|
||||||
|
return type(self)(self._num * a._num, self._den * a._den)
|
||||||
|
if isinstance(a, int):
|
||||||
|
return type(self)(self._num * a, self._den)
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __rmul__(self, a):
|
||||||
|
"""Compute the product of an integer with a field element."""
|
||||||
|
return type(self)(a) * self
|
||||||
|
|
||||||
|
def __truediv__(self, a):
|
||||||
|
"""Compute the ratio of two field elements (second may be int)."""
|
||||||
|
if isinstance(a, type(self)) or isinstance(a, int):
|
||||||
|
return type(self)(self, a)
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __pow__(self, a):
|
||||||
|
"""Raise a field element to an integer power."""
|
||||||
|
return type(self)(pow(self._num, a, self.SIZE), pow(self._den, a, self.SIZE))
|
||||||
|
|
||||||
|
def __neg__(self):
|
||||||
|
"""Negate a field element."""
|
||||||
|
return type(self)(-self._num, self._den)
|
||||||
|
|
||||||
|
def __int__(self):
|
||||||
|
"""Convert a field element to an integer in range 0..SIZE-1. The result is cached."""
|
||||||
|
if self._den != 1:
|
||||||
|
self._num = (self._num * pow(self._den, -1, self.SIZE)) % self.SIZE
|
||||||
|
self._den = 1
|
||||||
|
return self._num
|
||||||
|
|
||||||
|
def sqrt(self):
|
||||||
|
"""Compute the square root of a field element if it exists (None otherwise)."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def is_square(self):
|
||||||
|
"""Determine if this field element has a square root."""
|
||||||
|
# A more efficient algorithm is possible here (Jacobi symbol).
|
||||||
|
return self.sqrt() is not None
|
||||||
|
|
||||||
|
def is_even(self):
|
||||||
|
"""Determine whether this field element, represented as integer in 0..SIZE-1, is even."""
|
||||||
|
return int(self) & 1 == 0
|
||||||
|
|
||||||
|
def __eq__(self, a):
|
||||||
|
"""Check whether two field elements are equal (second may be an int)."""
|
||||||
|
if isinstance(a, type(self)):
|
||||||
|
return (self._num * a._den - self._den * a._num) % self.SIZE == 0
|
||||||
|
return (self._num - self._den * a) % self.SIZE == 0
|
||||||
|
|
||||||
|
def to_bytes(self):
|
||||||
|
"""Convert a field element to a 32-byte array (BE byte order)."""
|
||||||
|
return int(self).to_bytes(32, 'big')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_int_checked(cls, v):
|
||||||
|
"""Convert an integer to a field element (no overflow allowed)."""
|
||||||
|
if v >= cls.SIZE:
|
||||||
|
raise ValueError
|
||||||
|
return cls(v)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_int_wrapping(cls, v):
|
||||||
|
"""Convert an integer to a field element (reduced modulo SIZE)."""
|
||||||
|
return cls(v % cls.SIZE)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_bytes_checked(cls, b):
|
||||||
|
"""Convert a 32-byte array to a field element (BE byte order, no overflow allowed)."""
|
||||||
|
v = int.from_bytes(b, 'big')
|
||||||
|
return cls.from_int_checked(v)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_bytes_wrapping(cls, b):
|
||||||
|
"""Convert a 32-byte array to a field element (BE byte order, reduced modulo SIZE)."""
|
||||||
|
v = int.from_bytes(b, 'big')
|
||||||
|
return cls.from_int_wrapping(v)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""Convert this field element to a 64 character hex string."""
|
||||||
|
return f"{int(self):064x}"
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
"""Get a string representation of this field element."""
|
||||||
|
return f"{type(self).__qualname__}(0x{int(self):x})"
|
||||||
|
|
||||||
|
|
||||||
|
class FE(APrimeFE):
|
||||||
|
SIZE = 2**256 - 2**32 - 977
|
||||||
|
|
||||||
|
def sqrt(self):
|
||||||
|
# Due to the fact that our modulus p is of the form (p % 4) == 3, the Tonelli-Shanks
|
||||||
|
# algorithm (https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm) is simply
|
||||||
|
# raising the argument to the power (p + 1) / 4.
|
||||||
|
|
||||||
|
# To see why: (p-1) % 2 = 0, so 2 divides the order of the multiplicative group,
|
||||||
|
# and thus only half of the non-zero field elements are squares. An element a is
|
||||||
|
# a (nonzero) square when Euler's criterion, a^((p-1)/2) = 1 (mod p), holds. We're
|
||||||
|
# looking for x such that x^2 = a (mod p). Given a^((p-1)/2) = 1, that is equivalent
|
||||||
|
# to x^2 = a^(1 + (p-1)/2) mod p. As (1 + (p-1)/2) is even, this is equivalent to
|
||||||
|
# x = a^((1 + (p-1)/2)/2) mod p, or x = a^((p+1)/4) mod p.
|
||||||
|
v = int(self)
|
||||||
|
s = pow(v, (self.SIZE + 1) // 4, self.SIZE)
|
||||||
|
if s**2 % self.SIZE == v:
|
||||||
|
return type(self)(s)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class Scalar(APrimeFE):
|
||||||
|
"""TODO Docstring"""
|
||||||
|
SIZE = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
|
||||||
|
|
||||||
|
|
||||||
|
class GE:
|
||||||
|
"""Objects of this class represent secp256k1 group elements (curve points or infinity)
|
||||||
|
|
||||||
|
GE objects are immutable.
|
||||||
|
|
||||||
|
Normal points on the curve have fields:
|
||||||
|
* x: the x coordinate (a field element)
|
||||||
|
* y: the y coordinate (a field element, satisfying y^2 = x^3 + 7)
|
||||||
|
* infinity: False
|
||||||
|
|
||||||
|
The point at infinity has field:
|
||||||
|
* infinity: True
|
||||||
|
"""
|
||||||
|
|
||||||
|
# TODO The following two class attributes should probably be just getters as
|
||||||
|
# classmethods to enforce immutability. Unfortunately Python makes it hard
|
||||||
|
# to create "classproperties". `G` could then also be just a classmethod.
|
||||||
|
|
||||||
|
# Order of the group (number of points on the curve, plus 1 for infinity)
|
||||||
|
ORDER = Scalar.SIZE
|
||||||
|
|
||||||
|
# Number of valid distinct x coordinates on the curve.
|
||||||
|
ORDER_HALF = ORDER // 2
|
||||||
|
|
||||||
|
@property
|
||||||
|
def infinity(self):
|
||||||
|
"""Whether the group element is the point at infinity."""
|
||||||
|
return self._infinity
|
||||||
|
|
||||||
|
@property
|
||||||
|
def x(self):
|
||||||
|
"""The x coordinate (a field element) of a non-infinite group element."""
|
||||||
|
assert not self.infinity
|
||||||
|
return self._x
|
||||||
|
|
||||||
|
@property
|
||||||
|
def y(self):
|
||||||
|
"""The y coordinate (a field element) of a non-infinite group element."""
|
||||||
|
assert not self.infinity
|
||||||
|
return self._y
|
||||||
|
|
||||||
|
def __init__(self, x=None, y=None):
|
||||||
|
"""Initialize a group element with specified x and y coordinates, or infinity."""
|
||||||
|
if x is None:
|
||||||
|
# Initialize as infinity.
|
||||||
|
assert y is None
|
||||||
|
self._infinity = True
|
||||||
|
else:
|
||||||
|
# Initialize as point on the curve (and check that it is).
|
||||||
|
fx = FE(x)
|
||||||
|
fy = FE(y)
|
||||||
|
assert fy**2 == fx**3 + 7
|
||||||
|
self._infinity = False
|
||||||
|
self._x = fx
|
||||||
|
self._y = fy
|
||||||
|
|
||||||
|
def __add__(self, a):
|
||||||
|
"""Add two group elements together."""
|
||||||
|
# Deal with infinity: a + infinity == infinity + a == a.
|
||||||
|
if self.infinity:
|
||||||
|
return a
|
||||||
|
if a.infinity:
|
||||||
|
return self
|
||||||
|
if self.x == a.x:
|
||||||
|
if self.y != a.y:
|
||||||
|
# A point added to its own negation is infinity.
|
||||||
|
assert self.y + a.y == 0
|
||||||
|
return GE()
|
||||||
|
else:
|
||||||
|
# For identical inputs, use the tangent (doubling formula).
|
||||||
|
lam = (3 * self.x**2) / (2 * self.y)
|
||||||
|
else:
|
||||||
|
# For distinct inputs, use the line through both points (adding formula).
|
||||||
|
lam = (self.y - a.y) / (self.x - a.x)
|
||||||
|
# Determine point opposite to the intersection of that line with the curve.
|
||||||
|
x = lam**2 - (self.x + a.x)
|
||||||
|
y = lam * (self.x - x) - self.y
|
||||||
|
return GE(x, y)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def sum(*ps):
|
||||||
|
"""Compute the sum of group elements.
|
||||||
|
|
||||||
|
GE.sum(a, b, c, ...) is identical to (GE() + a + b + c + ...)."""
|
||||||
|
return sum(ps, start=GE())
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def batch_mul(*aps):
|
||||||
|
"""Compute a (batch) scalar group element multiplication.
|
||||||
|
|
||||||
|
GE.batch_mul((a1, p1), (a2, p2), (a3, p3)) is identical to a1*p1 + a2*p2 + a3*p3,
|
||||||
|
but more efficient."""
|
||||||
|
# Reduce all the scalars modulo order first (so we can deal with negatives etc).
|
||||||
|
naps = [(int(a), p) for a, p in aps]
|
||||||
|
# Start with point at infinity.
|
||||||
|
r = GE()
|
||||||
|
# Iterate over all bit positions, from high to low.
|
||||||
|
for i in range(255, -1, -1):
|
||||||
|
# Double what we have so far.
|
||||||
|
r = r + r
|
||||||
|
# Add then add the points for which the corresponding scalar bit is set.
|
||||||
|
for (a, p) in naps:
|
||||||
|
if (a >> i) & 1:
|
||||||
|
r += p
|
||||||
|
return r
|
||||||
|
|
||||||
|
def __rmul__(self, a):
|
||||||
|
"""Multiply an integer with a group element."""
|
||||||
|
if self == G:
|
||||||
|
return FAST_G.mul(Scalar(a))
|
||||||
|
return GE.batch_mul((Scalar(a), self))
|
||||||
|
|
||||||
|
def __neg__(self):
|
||||||
|
"""Compute the negation of a group element."""
|
||||||
|
if self.infinity:
|
||||||
|
return self
|
||||||
|
return GE(self.x, -self.y)
|
||||||
|
|
||||||
|
def __sub__(self, a):
|
||||||
|
"""Subtract a group element from another."""
|
||||||
|
return self + (-a)
|
||||||
|
|
||||||
|
def __eq__(self, a):
|
||||||
|
"""Check if two group elements are equal."""
|
||||||
|
return (self - a).infinity
|
||||||
|
|
||||||
|
def has_even_y(self):
|
||||||
|
"""Determine whether a non-infinity group element has an even y coordinate."""
|
||||||
|
assert not self.infinity
|
||||||
|
return self.y.is_even()
|
||||||
|
|
||||||
|
def to_bytes_compressed(self):
|
||||||
|
"""Convert a non-infinite group element to 33-byte compressed encoding."""
|
||||||
|
assert not self.infinity
|
||||||
|
return bytes([3 - self.y.is_even()]) + self.x.to_bytes()
|
||||||
|
|
||||||
|
def to_bytes_compressed_with_infinity(self):
|
||||||
|
"""Convert a group element to 33-byte compressed encoding, mapping infinity to zeros."""
|
||||||
|
if self.infinity:
|
||||||
|
return 33 * b"\x00"
|
||||||
|
return self.to_bytes_compressed()
|
||||||
|
|
||||||
|
def to_bytes_uncompressed(self):
|
||||||
|
"""Convert a non-infinite group element to 65-byte uncompressed encoding."""
|
||||||
|
assert not self.infinity
|
||||||
|
return b'\x04' + self.x.to_bytes() + self.y.to_bytes()
|
||||||
|
|
||||||
|
def to_bytes_xonly(self):
|
||||||
|
"""Convert (the x coordinate of) a non-infinite group element to 32-byte xonly encoding."""
|
||||||
|
assert not self.infinity
|
||||||
|
return self.x.to_bytes()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def lift_x(x):
|
||||||
|
"""Return group element with specified field element as x coordinate (and even y)."""
|
||||||
|
y = (FE(x)**3 + 7).sqrt()
|
||||||
|
if y is None:
|
||||||
|
raise ValueError
|
||||||
|
if not y.is_even():
|
||||||
|
y = -y
|
||||||
|
return GE(x, y)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_bytes_compressed(b):
|
||||||
|
"""Convert a compressed to a group element."""
|
||||||
|
assert len(b) == 33
|
||||||
|
if b[0] != 2 and b[0] != 3:
|
||||||
|
raise ValueError
|
||||||
|
x = FE.from_bytes_checked(b[1:])
|
||||||
|
r = GE.lift_x(x)
|
||||||
|
if b[0] == 3:
|
||||||
|
r = -r
|
||||||
|
return r
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_bytes_uncompressed(b):
|
||||||
|
"""Convert an uncompressed to a group element."""
|
||||||
|
assert len(b) == 65
|
||||||
|
if b[0] != 4:
|
||||||
|
raise ValueError
|
||||||
|
x = FE.from_bytes_checked(b[1:33])
|
||||||
|
y = FE.from_bytes_checked(b[33:])
|
||||||
|
if y**2 != x**3 + 7:
|
||||||
|
raise ValueError
|
||||||
|
return GE(x, y)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_bytes(b):
|
||||||
|
"""Convert a compressed or uncompressed encoding to a group element."""
|
||||||
|
assert len(b) in (33, 65)
|
||||||
|
if len(b) == 33:
|
||||||
|
return GE.from_bytes_compressed(b)
|
||||||
|
else:
|
||||||
|
return GE.from_bytes_uncompressed(b)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_bytes_xonly(b):
|
||||||
|
"""Convert a point given in xonly encoding to a group element."""
|
||||||
|
assert len(b) == 32
|
||||||
|
x = FE.from_bytes_checked(b)
|
||||||
|
r = GE.lift_x(x)
|
||||||
|
return r
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_valid_x(x):
|
||||||
|
"""Determine whether the provided field element is a valid X coordinate."""
|
||||||
|
return (FE(x)**3 + 7).is_square()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""Convert this group element to a string."""
|
||||||
|
if self.infinity:
|
||||||
|
return "(inf)"
|
||||||
|
return f"({self.x},{self.y})"
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
"""Get a string representation for this group element."""
|
||||||
|
if self.infinity:
|
||||||
|
return "GE()"
|
||||||
|
return f"GE(0x{int(self.x):x},0x{int(self.y):x})"
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
"""Compute a non-cryptographic hash of the group element."""
|
||||||
|
if self.infinity:
|
||||||
|
return 0 # 0 is not a valid x coordinate
|
||||||
|
return int(self.x)
|
||||||
|
|
||||||
|
|
||||||
|
# The secp256k1 generator point
|
||||||
|
G = GE.lift_x(0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798)
|
||||||
|
|
||||||
|
|
||||||
|
class FastGEMul:
|
||||||
|
"""Table for fast multiplication with a constant group element.
|
||||||
|
|
||||||
|
Speed up scalar multiplication with a fixed point P by using a precomputed lookup table with
|
||||||
|
its powers of 2:
|
||||||
|
|
||||||
|
table = [P, 2*P, 4*P, (2^3)*P, (2^4)*P, ..., (2^255)*P]
|
||||||
|
|
||||||
|
During multiplication, the points corresponding to each bit set in the scalar are added up,
|
||||||
|
i.e. on average ~128 point additions take place.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, p):
|
||||||
|
self.table = [p] # table[i] = (2^i) * p
|
||||||
|
for _ in range(255):
|
||||||
|
p = p + p
|
||||||
|
self.table.append(p)
|
||||||
|
|
||||||
|
def mul(self, a):
|
||||||
|
result = GE()
|
||||||
|
a = int(a)
|
||||||
|
for bit in range(a.bit_length()):
|
||||||
|
if a & (1 << bit):
|
||||||
|
result += self.table[bit]
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Precomputed table with multiples of G for fast multiplication
|
||||||
|
FAST_G = FastGEMul(G)
|
||||||
24
bip-0374/secp256k1lab/src/secp256k1lab/util.py
Normal file
24
bip-0374/secp256k1lab/src/secp256k1lab/util.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import hashlib
|
||||||
|
|
||||||
|
|
||||||
|
# This implementation can be sped up by storing the midstate after hashing
|
||||||
|
# tag_hash instead of rehashing it all the time.
|
||||||
|
def tagged_hash(tag: str, msg: bytes) -> bytes:
|
||||||
|
tag_hash = hashlib.sha256(tag.encode()).digest()
|
||||||
|
return hashlib.sha256(tag_hash + tag_hash + msg).digest()
|
||||||
|
|
||||||
|
|
||||||
|
def bytes_from_int(x: int) -> bytes:
|
||||||
|
return x.to_bytes(32, byteorder="big")
|
||||||
|
|
||||||
|
|
||||||
|
def xor_bytes(b0: bytes, b1: bytes) -> bytes:
|
||||||
|
return bytes(x ^ y for (x, y) in zip(b0, b1))
|
||||||
|
|
||||||
|
|
||||||
|
def int_from_bytes(b: bytes) -> int:
|
||||||
|
return int.from_bytes(b, byteorder="big")
|
||||||
|
|
||||||
|
|
||||||
|
def hash_sha256(b: bytes) -> bytes:
|
||||||
|
return hashlib.sha256(b).digest()
|
||||||
Loading…
x
Reference in New Issue
Block a user