# -*- encoding:utf-8 -*-
"""
Pure python implementation for ``scp256k1`` curve algebra and associated
``ECDSA - SCHNORR`` signatures.
>>> from dposlib.ark import secp256k1
>>> G = secp256k1.Point(0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2\
815B16F81798)
>>> G.y
32670510020758816978083085130507043184471273380659243275938904335757337482424
>>> G
<secp256k1 point:
x:79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
y:483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8
>
>>> G+G == 2*G
True
>>> secp256k1.PublicKey.from_int(secp256k1.int_from_bytes(secp256k1.hash_sha25\
6("secret")))
<secp256k1 public key:
x:a02b9d5fdd1307c2ee4652ba54d492d1fd11a7d1bb3f3a44c4a05e79f19de933
y:924aa2580069952b0140d88de21c367ee4af7c4a906e1498f20ab8f62e4c2921
>
>>> secp256k1.PublicKey.from_seed(secp256k1.hash_sha256("secret"))
<secp256k1 public key:
x:a02b9d5fdd1307c2ee4652ba54d492d1fd11a7d1bb3f3a44c4a05e79f19de933
y:924aa2580069952b0140d88de21c367ee4af7c4a906e1498f20ab8f62e4c2921
>
>>> secp256k1.PublicKey.from_secret("secret")
<secp256k1 public key:
x:a02b9d5fdd1307c2ee4652ba54d492d1fd11a7d1bb3f3a44c4a05e79f19de933
y:924aa2580069952b0140d88de21c367ee4af7c4a906e1498f20ab8f62e4c2921
>
Sources:
- `BIP schnorr <https://github.com/sipa/bips/blob/bip-schnorr/bip-schnorr.me\
diawiki>`_
- `Python reference <https://github.com/sipa/bips/blob/bip-schnorr/bip-schno\
rr/reference.py>`_
- `Bcrypto 4.10 schnorr scheme <https://github.com/bcoin-org/bcrypto/blob/v4\
.1.0/lib/js/schnorr.js>`_
Variables:
- ``secret`` (:class:`str`): passphrase
- ``secret0`` (:class:`bytes`): private key
- ``P`` (:class:`list`): public key as ``secp256k1`` curve point
- ``pubkey`` (:class:`bytes`): compressed - encoded public key
- ``pubkeyB`` (:class:`bytes`): compressed - encoded public key according
to bip schnorr spec
- ``msg`` (:class:`bytes`): sha256 hash of message to sign
- Uppercase variables refer to points on the curve with equation ``y²=x³+7``
over the integers modulo p
"""
import hmac
import random
import future
import hashlib
from builtins import int, bytes, pow
p = int(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f)
n = int(0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141)
[docs]def hash_sha256(b):
"""
Args:
b (:class:`bytes` or :class:`str`): sequence to be hashed
Returns:
:class:`bytes`: sha256 hash
"""
return hashlib.sha256(
b if isinstance(b, bytes) else b.encode("utf-8")
).digest()
# precomputed hashtag
HASHED_TAGS = {
"BIPSchnorrDerive": hash_sha256("BIPSchnorrDerive"),
"BIPSchnorr": hash_sha256("BIPSchnorr"),
}
[docs]def tagged_hash(tag, msg):
"""
Return ``sha256(sha256(tag) || sha256(tag) || msg)``. Tagged hash
are registered to speed up code execution.
Args:
tag (:class:`str`): tag to use
msg (:class:`bytes`): sha256 hash of message to sign
Returns:
:class:`bytes`: tagged hash
"""
tag_hash = HASHED_TAGS.get(tag, False)
if not tag_hash:
tag_hash = hash_sha256(tag)
HASHED_TAGS[tag] = tag_hash
return hash_sha256(tag_hash + tag_hash + msg)
def is_infinity(P):
return P is None
[docs]def x(P):
"""
Return :class:`P.x` or :class:`P[0]`.
Args:
P (:class:`list`): ``secp256k1`` point
Returns:
:class:`int`: x
"""
return P[0]
[docs]def y(P):
"""
Return :class:`P.y` or :class:`P[1]`.
Args:
P (:class:`list`): ``secp256k1`` point
Returns:
:class:`int`: y
"""
return P[1]
[docs]def y_from_x(x):
"""
Compute :class:`P.y` from :class:`P.x` according to ``y²=x³+7``.
"""
y_sq = (pow(x, 3, p) + 7) % p
y = pow(y_sq, (p + 1) // 4, p)
if pow(y, 2, p) != y_sq:
return None
return y
[docs]def point_add(P1, P2):
"""
Add ``secp256k1`` points.
Args:
P1 (:class:`list`): first ``secp256k1`` point
P2 (:class:`list`): second ``secp256k1`` point
Returns:
:class:`list`: ``secp256k1`` point
"""
if (P1 is None):
return P2
if (P2 is None):
return P1
if (x(P1) == x(P2) and y(P1) != y(P2)):
raise ValueError("One of the point is not on the curve")
if (P1 == P2):
lam = (3 * x(P1) * x(P1) * pow(2 * y(P1), p - 2, p)) % p
else:
lam = ((y(P2) - y(P1)) * pow(x(P2) - x(P1), p - 2, p)) % p
x3 = (lam * lam - x(P1) - x(P2)) % p
return [x3, (lam * (x(P1) - x3) - y(P1)) % p]
[docs]def point_mul(P, n):
"""
Multiply ``secp256k1`` point with scalar.
Args:
P (:class:`list`): ``secp256k1`` point
n (:class:`int`): scalar
Returns:
:class:`list`: ``secp256k1`` point
"""
R = None
for i in range(256):
if ((n >> i) & 1):
R = point_add(R, P)
P = point_add(P, P)
return R
def bytes_from_int(x):
return int(x).to_bytes(32, byteorder="big")
def int_from_bytes(b):
return int.from_bytes(b, byteorder="big")
def jacobi(x):
return pow(x, (p - 1) // 2, p)
# def is_square(x):
# return jacobi(x) == 1
def is_quad(x):
return jacobi(x) == 1
def has_square_y(P):
return not is_infinity(P) and is_quad(y(P))
[docs]def encoded_from_point(P):
"""
Encode and compress a ``secp256k1`` point:
* ``bytes(2) || bytes(x)`` if y is even
* ``bytes(3) || bytes(x)`` if y is odd
Args:
P (:class:`list`): ``secp256k1`` point
Returns:
:class:`bytes`: compressed and encoded point
"""
return (b"\x03" if y(P) & 1 else b"\x02") + bytes_from_int(x(P))
[docs]def point_from_encoded(pubkey):
"""
Decode and decompress a ``secp256k1`` point.
Args:
pubkey (:class:`bytes`): compressed and encoded point
Returns:
:class:`list`: ``secp256k1`` point
"""
pubkey = bytearray(pubkey)
x = int_from_bytes(pubkey[1:])
y = y_from_x(x)
if y is None:
raise ValueError("Point not on ``secp256k1`` curve")
elif y % 2 != pubkey[0] - 2:
y = -y % p
return [x, y]
[docs]def der_from_sig(r, s):
"""
Encode a signature according ``DER`` spec.
Args:
r (:class:`int`): signature part #1
s (:class:`int`): signature part #2
Returns:
:class:`bytes`: encoded signature
"""
r = bytes_from_int(r)
s = bytes_from_int(s)
r = (b'\x00' if (r[0] & 0x80) == 0x80 else b'') + r
s = (b'\x00' if (s[0] & 0x80) == 0x80 else b'') + s
return b'\x30' + int((len(r)+len(s)+4)).to_bytes(1, 'big') + \
b'\x02' + int(len(r)).to_bytes(1, 'big') + r + \
b'\x02' + int(len(s)).to_bytes(1, 'big') + s
[docs]def sig_from_der(der):
"""
Decode a ``DER`` signature.
Args:
der (:class:`bytes`): encoded signature
Returns:
(:class:`int`, :class:`int`): signature (r, s)
"""
sig = bytearray(der)
sig_len = sig[1] + 2
r_offset, r_len = 4, sig[3]
s_offset, s_len = 4+r_len+2, sig[4+r_len+1]
if (
sig[0] != 0x30 or sig_len != r_len+s_len+6 or sig[r_offset-2] != 0x02
or sig[s_offset-2] != 0x02
):
return None, None
return (
int_from_bytes(sig[r_offset:r_offset+r_len]),
int_from_bytes(sig[s_offset:s_offset+s_len])
)
[docs]def rand_k():
"""Generate a random nonce."""
while True:
k = random.getrandbits(p.bit_length())
if k < p:
return k
[docs]def rfc6979_k(msg, secret0, V=None):
"""
Generate a deterministic nonce according to
`rfc6979 spec <https://tools.ietf.org/html/rfc6979#section-3.2>`_.
Args:
msg (:class:`bytes`): 32-bytes sequence
secret0 (:class:`bytes`): private key
V (:class:`bytes`):
Returns:
:class:`int`: deterministic nonce
"""
hasher = hashlib.sha256
if (V is None):
# a. Process m through the hash function H, yielding: h1 = H(m)
h1 = msg
hsize = len(h1)
# b. Set: V = 0x01 0x01 0x01 ... 0x01
V = b'\x01'*hsize
# c. Set: K = 0x00 0x00 0x00 ... 0x00
K = b'\x00'*hsize
# d. Set: K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1))
x = secret0
K = hmac.new(K, V + b'\x00' + x + h1, hasher).digest()
# e. Set: V = HMAC_K(V)
V = hmac.new(K, V, hasher).digest()
# f. Set: K = HMAC_K(V || 0x01 || int2octets(x) || bits2octets(h1))
K = hmac.new(K, V + b'\x01' + x + h1, hasher).digest()
# g. Set: V = HMAC_K(V)
V = hmac.new(K, V, hasher).digest()
# h. Apply the following algorithm until a proper value is found for k:
while True:
#
# 1. Set T to the empty sequence. The length of T (in bits) is
# denoted tlen; thus, at that point, tlen = 0.
T = b''
# 2. While tlen < qlen, do the following:
# V = HMAC_K(V)
# T = T || V
p_blen = p.bit_length()
while len(T)*8 < p_blen:
V = hmac.new(K, V, hasher).digest()
T = T + V
# 3. Compute:
k = int_from_bytes(T)
k_blen = k.bit_length()
if k_blen > p_blen:
k = k >> (k_blen - p_blen)
# If that value of k is within the [1,q-1] range, and is
# suitable for DSA or ECDSA (i.e., it results in an r value
# that is not 0; see Section 3.4), then the generation of k is
# finished. The obtained value of k is used in DSA or ECDSA.
if k > 0 and k < (p-1):
return k, V
# Otherwise, compute:
# K = HMAC_K(V || 0x00)
# V = HMAC_K(V)
# and loop (try to generate a new T, and so on).
K = hmac.new(K, V+b'\x00', hasher).digest()
V = hmac.new(K, V, hasher).digest()
[docs]class Point(list):
"""
``secp256k1`` point . Initialization can be done with sole ``x`` value.
:class:`Point` overrides ``*`` and ``+`` operators which accepts
:class:`list` as argument and returns :class:`Point`.
"""
x = property(
lambda cls: list.__getitem__(cls, 0),
lambda cls, v: [
list.__setitem__(cls, 0, int(v)),
list.__setitem__(cls, 1, y_from_x(int(v)))
],
None, "Return list item #0"
)
y = property(
lambda cls: list.__getitem__(cls, 1),
None, None, "Return list item #1"
)
def __init__(self, *xy):
if len(xy) == 0:
xy = (0, None)
elif len(xy) == 1:
xy += (y_from_x(int(xy[0])), )
list.__init__(self, [int(e) if e is not None else e for e in xy[:2]])
def __mul__(self, k):
if isinstance(k, int):
return Point(*point_mul(self, k))
else:
raise TypeError("'%s' should be an int" % k)
__rmul__ = __mul__
def __add__(self, P):
if isinstance(P, list):
return Point(*point_add(self, P))
else:
raise TypeError("'%s' should be a 2-int-length list" % P)
__radd__ = __add__
def __repr__(self):
return "<secp256k1 point:\n x:%064x\n y:%064x\n>" % tuple(self)
[docs] @staticmethod
def decode(pubkey):
"""
See :func:`point_from_encoded`.
"""
return Point(*point_from_encoded(pubkey))
[docs] def encode(self):
"""
See :func:`encoded_from_point`.
"""
return encoded_from_point(self)
[docs]class PublicKey(Point):
"""
:class:`Point` extension providing specific initialization methods.
"""
[docs] @staticmethod
def from_int(value):
"""
Compute a public key from :class:`int` value.
Arguments:
value (:class:`int`): scalar to use
Returns:
:class:`PublicKey`: the public key
"""
if (1 <= value <= n - 1):
return PublicKey(*(G * int(value)))
else:
raise ValueError(
'The secret key must be an integer in the range 1..n-1.'
)
[docs] @staticmethod
def from_seed(seed):
"""
Compute a public key from :class:`bytes` value.
Arguments:
value (:class:`bytes`): bytes sequence to use
Returns:
:class:`PublicKey`: the public key
"""
return PublicKey.from_int(int_from_bytes(seed))
[docs] @staticmethod
def from_secret(secret):
"""
Compute a public key from secret passphrase.
Arguments:
value (:class:`str`): secret passphrase to use
Returns:
:class:`PublicKey`: the public key
"""
return PublicKey.from_seed(hash_sha256(secret))
def __repr__(self):
return "<secp256k1 public key:\n x:%064x\n y:%064x\n>" % tuple(self)
G = Point(0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)