2018-10-12 10:20:41 +00:00
|
|
|
# ed25519.py - Optimized version of the reference implementation of Ed25519
|
|
|
|
# downloaded from https://github.com/pyca/ed25519
|
|
|
|
#
|
|
|
|
# Written in 2011? by Daniel J. Bernstein <djb@cr.yp.to>
|
|
|
|
# 2013 by Donald Stufft <donald@stufft.io>
|
|
|
|
# 2013 by Alex Gaynor <alex.gaynor@gmail.com>
|
|
|
|
# 2013 by Greg Price <price@mit.edu>
|
|
|
|
#
|
|
|
|
# To the extent possible under law, the author(s) have dedicated all copyright
|
|
|
|
# and related and neighboring rights to this software to the public domain
|
|
|
|
# worldwide. This software is distributed without any warranty.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the CC0 Public Domain Dedication along
|
|
|
|
# with this software. If not, see
|
|
|
|
# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
|
|
|
|
|
|
|
"""
|
|
|
|
NB: This code is not safe for use with secret keys or secret data.
|
|
|
|
The only safe use of this code is for verifying signatures on public messages.
|
|
|
|
|
|
|
|
Functions for computing the public key of a secret key and for signing
|
|
|
|
a message are included, namely publickey_unsafe and signature_unsafe,
|
|
|
|
for testing purposes only.
|
|
|
|
|
|
|
|
The root of the problem is that Python's long-integer arithmetic is
|
|
|
|
not designed for use in cryptography. Specifically, it may take more
|
|
|
|
or less time to execute an operation depending on the values of the
|
|
|
|
inputs, and its memory access patterns may also depend on the inputs.
|
|
|
|
This opens it to timing and cache side-channel attacks which can
|
|
|
|
disclose data to an attacker. We rely on Python's long-integer
|
|
|
|
arithmetic, so we cannot handle secrets without risking their disclosure.
|
|
|
|
"""
|
2017-10-03 22:37:45 +00:00
|
|
|
|
|
|
|
import hashlib
|
2019-02-18 19:43:08 +00:00
|
|
|
from typing import List, NewType, Tuple
|
2018-05-28 12:17:11 +00:00
|
|
|
|
2018-10-12 10:20:41 +00:00
|
|
|
Point = NewType("Point", Tuple[int, int, int, int])
|
|
|
|
|
|
|
|
|
|
|
|
__version__ = "1.0.dev1"
|
|
|
|
|
2017-10-03 22:37:45 +00:00
|
|
|
|
|
|
|
b = 256
|
feat(python): add full type information
WIP - typing the trezorctl apps
typing functions trezorlib/cli
addressing most of mypy issue for trezorlib apps and _internal folder
fixing broken device tests by changing asserts in debuglink.py
addressing most of mypy issues in trezorlib/cli folder
adding types to some untyped functions, mypy section in setup.cfg
typing what can be typed, some mypy fixes, resolving circular import issues
importing type objects in "if TYPE_CHECKING:" branch
fixing CI by removing assert in emulator, better ignore comments
CI assert fix, style fixes, new config options
fixup! CI assert fix, style fixes, new config options
type fixes after rebasing on master
fixing python3.6 and 3.7 unittests by importing Literal from typing_extensions
couple mypy and style fixes
fixes and improvements from code review
silencing all but one mypy issues
trial of typing the tools.expect function
fixup! trial of typing the tools.expect function
@expect and @session decorators correctly type-checked
Optional args in CLI where relevant, not using general list/tuple/dict where possible
python/Makefile commands, adding them into CI, ignoring last mypy issue
documenting overload for expect decorator, two mypy fixes coming from that
black style fix
improved typing of decorators, pyright config file
addressing or ignoring pyright errors, replacing mypy in CI by pyright
fixing incomplete assert causing device tests to fail
pyright issue that showed in CI but not locally, printing pyright version in CI
fixup! pyright issue that showed in CI but not locally, printing pyright version in CI
unifying type:ignore statements for pyright usage
resolving PIL.Image issues, pyrightconfig not excluding anything
replacing couple asserts with TypeGuard on safe_issubclass
better error handling of usb1 import for webusb
better error handling of hid import
small typing details found out by strict pyright mode
improvements from code review
chore(python): changing List to Sequence for protobuf messages
small code changes to reflect the protobuf change to Sequence
importing TypedDict from typing_extensions to support 3.6 and 3.7
simplify _format_access_list function
fixup! simplify _format_access_list function
typing tools folder
typing helper-scripts folder
some click typing
enforcing all functions to have typed arguments
reverting the changed argument name in tools
replacing TransportType with Transport
making PinMatrixRequest.type protobuf attribute required
reverting the protobuf change, making argument into get_pin Optional
small fixes in asserts
solving the session decorator type issues
fixup! solving the session decorator type issues
improvements from code review
fixing new pyright errors introduced after version increase
changing -> Iterable to -> Sequence in enumerate_devices, change in wait_for_devices
style change in debuglink.py
chore(python): adding type annotation to Sequences in messages.py
better "self and cls" types on Transport
fixup! better "self and cls" types on Transport
fixing some easy things from strict pyright run
2021-11-03 22:12:53 +00:00
|
|
|
q: int = 2 ** 255 - 19
|
|
|
|
l: int = 2 ** 252 + 27742317777372353535851937790883648493
|
2017-10-03 22:37:45 +00:00
|
|
|
|
2018-10-12 10:20:41 +00:00
|
|
|
COORD_MASK = ~(1 + 2 + 4 + (1 << b - 1))
|
|
|
|
COORD_HIGH_BIT = 1 << b - 2
|
|
|
|
|
2017-10-03 22:37:45 +00:00
|
|
|
|
2018-05-28 12:17:11 +00:00
|
|
|
def H(m: bytes) -> bytes:
|
2017-10-03 22:37:45 +00:00
|
|
|
return hashlib.sha512(m).digest()
|
|
|
|
|
|
|
|
|
2018-10-12 10:20:41 +00:00
|
|
|
def pow2(x: int, p: int) -> int:
|
|
|
|
"""== pow(x, 2**p, q)"""
|
|
|
|
while p > 0:
|
|
|
|
x = x * x % q
|
|
|
|
p -= 1
|
|
|
|
return x
|
2017-10-03 22:37:45 +00:00
|
|
|
|
|
|
|
|
2018-10-12 10:20:41 +00:00
|
|
|
def inv(z: int) -> int:
|
|
|
|
"""$= z^{-1} mod q$, for z != 0"""
|
|
|
|
# Adapted from curve25519_athlon.c in djb's Curve25519.
|
|
|
|
z2 = z * z % q # 2
|
|
|
|
z9 = pow2(z2, 2) * z % q # 9
|
|
|
|
z11 = z9 * z2 % q # 11
|
|
|
|
z2_5_0 = (z11 * z11) % q * z9 % q # 31 == 2^5 - 2^0
|
|
|
|
z2_10_0 = pow2(z2_5_0, 5) * z2_5_0 % q # 2^10 - 2^0
|
|
|
|
z2_20_0 = pow2(z2_10_0, 10) * z2_10_0 % q # ...
|
|
|
|
z2_40_0 = pow2(z2_20_0, 20) * z2_20_0 % q
|
|
|
|
z2_50_0 = pow2(z2_40_0, 10) * z2_10_0 % q
|
|
|
|
z2_100_0 = pow2(z2_50_0, 50) * z2_50_0 % q
|
|
|
|
z2_200_0 = pow2(z2_100_0, 100) * z2_100_0 % q
|
|
|
|
z2_250_0 = pow2(z2_200_0, 50) * z2_50_0 % q # 2^250 - 2^0
|
|
|
|
return pow2(z2_250_0, 5) * z11 % q # 2^255 - 2^5 + 11 = q - 2
|
2017-10-03 22:37:45 +00:00
|
|
|
|
|
|
|
|
2018-10-12 10:20:41 +00:00
|
|
|
d = -121665 * inv(121666) % q
|
|
|
|
I = pow(2, (q - 1) // 4, q)
|
2017-10-03 22:37:45 +00:00
|
|
|
|
|
|
|
|
2018-05-28 12:17:11 +00:00
|
|
|
def xrecover(y: int) -> int:
|
2017-10-03 22:37:45 +00:00
|
|
|
xx = (y * y - 1) * inv(d * y * y + 1)
|
2018-10-12 10:20:41 +00:00
|
|
|
x = pow(xx, (q + 3) // 8, q)
|
|
|
|
|
2017-10-03 22:37:45 +00:00
|
|
|
if (x * x - xx) % q != 0:
|
|
|
|
x = (x * I) % q
|
2018-10-12 10:20:41 +00:00
|
|
|
|
2017-10-03 22:37:45 +00:00
|
|
|
if x % 2 != 0:
|
|
|
|
x = q - x
|
2018-10-12 10:20:41 +00:00
|
|
|
|
2017-10-03 22:37:45 +00:00
|
|
|
return x
|
|
|
|
|
|
|
|
|
|
|
|
By = 4 * inv(5)
|
|
|
|
Bx = xrecover(By)
|
2018-10-12 10:20:41 +00:00
|
|
|
B = Point((Bx % q, By % q, 1, (Bx * By) % q))
|
|
|
|
ident = Point((0, 1, 1, 0))
|
|
|
|
|
|
|
|
|
|
|
|
def edwards_add(P: Point, Q: Point) -> Point:
|
|
|
|
# This is formula sequence 'addition-add-2008-hwcd-3' from
|
|
|
|
# http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html
|
|
|
|
(x1, y1, z1, t1) = P
|
|
|
|
(x2, y2, z2, t2) = Q
|
|
|
|
|
|
|
|
a = (y1 - x1) * (y2 - x2) % q
|
|
|
|
b = (y1 + x1) * (y2 + x2) % q
|
|
|
|
c = t1 * 2 * d * t2 % q
|
|
|
|
dd = z1 * 2 * z2 % q
|
|
|
|
e = b - a
|
|
|
|
f = dd - c
|
|
|
|
g = dd + c
|
|
|
|
h = b + a
|
|
|
|
x3 = e * f
|
|
|
|
y3 = g * h
|
|
|
|
t3 = e * h
|
|
|
|
z3 = f * g
|
|
|
|
|
|
|
|
return Point((x3 % q, y3 % q, z3 % q, t3 % q))
|
|
|
|
|
|
|
|
|
|
|
|
def edwards_double(P: Point) -> Point:
|
|
|
|
# This is formula sequence 'dbl-2008-hwcd' from
|
|
|
|
# http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html
|
|
|
|
(x1, y1, z1, _) = P
|
|
|
|
|
|
|
|
a = x1 * x1 % q
|
|
|
|
b = y1 * y1 % q
|
|
|
|
c = 2 * z1 * z1 % q
|
|
|
|
# dd = -a
|
|
|
|
e = ((x1 + y1) * (x1 + y1) - a - b) % q
|
|
|
|
g = -a + b # dd + b
|
|
|
|
f = g - c
|
|
|
|
h = -a - b # dd - b
|
|
|
|
x3 = e * f
|
|
|
|
y3 = g * h
|
|
|
|
t3 = e * h
|
|
|
|
z3 = f * g
|
|
|
|
|
|
|
|
return Point((x3 % q, y3 % q, z3 % q, t3 % q))
|
2017-10-03 22:37:45 +00:00
|
|
|
|
|
|
|
|
2018-05-28 12:17:11 +00:00
|
|
|
def scalarmult(P: Point, e: int) -> Point:
|
2017-10-03 22:37:45 +00:00
|
|
|
if e == 0:
|
2018-10-12 10:20:41 +00:00
|
|
|
return ident
|
|
|
|
Q = scalarmult(P, e // 2)
|
|
|
|
Q = edwards_double(Q)
|
2017-10-03 22:37:45 +00:00
|
|
|
if e & 1:
|
2018-10-12 10:20:41 +00:00
|
|
|
Q = edwards_add(Q, P)
|
2017-10-03 22:37:45 +00:00
|
|
|
return Q
|
|
|
|
|
|
|
|
|
2018-10-12 10:20:41 +00:00
|
|
|
# Bpow[i] == scalarmult(B, 2**i)
|
2020-10-16 17:47:11 +00:00
|
|
|
Bpow: List[Point] = []
|
2018-10-12 10:20:41 +00:00
|
|
|
|
|
|
|
|
|
|
|
def make_Bpow() -> None:
|
|
|
|
P = B
|
|
|
|
for _ in range(253):
|
|
|
|
Bpow.append(P)
|
|
|
|
P = edwards_double(P)
|
|
|
|
|
|
|
|
|
|
|
|
make_Bpow()
|
|
|
|
|
|
|
|
|
|
|
|
def scalarmult_B(e: int) -> Point:
|
|
|
|
"""
|
|
|
|
Implements scalarmult(B, e) more efficiently.
|
|
|
|
"""
|
|
|
|
# scalarmult(B, l) is the identity
|
|
|
|
e = e % l
|
|
|
|
P = ident
|
|
|
|
for i in range(253):
|
|
|
|
if e & 1:
|
|
|
|
P = edwards_add(P, Bpow[i])
|
|
|
|
e = e // 2
|
|
|
|
assert e == 0, e
|
|
|
|
return P
|
|
|
|
|
|
|
|
|
2018-05-28 12:17:11 +00:00
|
|
|
def encodeint(y: int) -> bytes:
|
2018-10-12 10:20:41 +00:00
|
|
|
return y.to_bytes(b // 8, "little")
|
2017-10-03 22:37:45 +00:00
|
|
|
|
|
|
|
|
2018-05-28 12:17:11 +00:00
|
|
|
def encodepoint(P: Point) -> bytes:
|
2018-10-12 10:20:41 +00:00
|
|
|
(x, y, z, _) = P
|
|
|
|
zi = inv(z)
|
|
|
|
x = (x * zi) % q
|
|
|
|
y = (y * zi) % q
|
|
|
|
|
|
|
|
xbit = (x & 1) << (b - 1)
|
|
|
|
y_result = y & ~xbit # clear x bit
|
|
|
|
y_result |= xbit # set corret x bit value
|
|
|
|
return encodeint(y_result)
|
|
|
|
|
|
|
|
|
|
|
|
def decodeint(s: bytes) -> int:
|
|
|
|
return int.from_bytes(s, "little")
|
|
|
|
|
|
|
|
|
|
|
|
def decodepoint(s: bytes) -> Point:
|
|
|
|
y = decodeint(s) & ~(1 << b - 1) # y without the highest bit
|
|
|
|
x = xrecover(y)
|
|
|
|
if x & 1 != bit(s, b - 1):
|
|
|
|
x = q - x
|
|
|
|
P = Point((x, y, 1, (x * y) % q))
|
|
|
|
if not isoncurve(P):
|
|
|
|
raise ValueError("decoding point that is not on curve")
|
|
|
|
return P
|
|
|
|
|
|
|
|
|
|
|
|
def decodecoord(s: bytes) -> int:
|
|
|
|
a = decodeint(s[: b // 8])
|
|
|
|
# clear mask bits
|
|
|
|
a &= COORD_MASK
|
|
|
|
# set high bit
|
|
|
|
a |= COORD_HIGH_BIT
|
|
|
|
return a
|
2017-10-03 22:37:45 +00:00
|
|
|
|
|
|
|
|
2018-05-28 12:17:11 +00:00
|
|
|
def bit(h: bytes, i: int) -> int:
|
2018-10-12 10:20:41 +00:00
|
|
|
return (h[i // 8] >> (i % 8)) & 1
|
|
|
|
|
2017-10-03 22:37:45 +00:00
|
|
|
|
2018-10-12 10:20:41 +00:00
|
|
|
def publickey_unsafe(sk: bytes) -> bytes:
|
|
|
|
"""
|
|
|
|
Not safe to use with secret keys or secret data.
|
2017-10-03 22:37:45 +00:00
|
|
|
|
2018-10-12 10:20:41 +00:00
|
|
|
See module docstring. This function should be used for testing only.
|
|
|
|
"""
|
2017-10-03 22:37:45 +00:00
|
|
|
h = H(sk)
|
2018-10-12 10:20:41 +00:00
|
|
|
a = decodecoord(h)
|
|
|
|
A = scalarmult_B(a)
|
2017-10-03 22:37:45 +00:00
|
|
|
return encodepoint(A)
|
|
|
|
|
|
|
|
|
2018-05-28 12:17:11 +00:00
|
|
|
def Hint(m: bytes) -> int:
|
2018-10-12 10:20:41 +00:00
|
|
|
return decodeint(H(m))
|
2017-10-03 22:37:45 +00:00
|
|
|
|
|
|
|
|
2018-10-12 10:20:41 +00:00
|
|
|
def signature_unsafe(m: bytes, sk: bytes, pk: bytes) -> bytes:
|
|
|
|
"""
|
|
|
|
Not safe to use with secret keys or secret data.
|
|
|
|
|
|
|
|
See module docstring. This function should be used for testing only.
|
|
|
|
"""
|
2017-10-03 22:37:45 +00:00
|
|
|
h = H(sk)
|
2018-10-12 10:20:41 +00:00
|
|
|
a = decodecoord(h)
|
|
|
|
r = Hint(h[b // 8 : b // 4] + m)
|
|
|
|
R = scalarmult_B(r)
|
2017-10-03 22:37:45 +00:00
|
|
|
S = (r + Hint(encodepoint(R) + pk + m) * a) % l
|
|
|
|
return encodepoint(R) + encodeint(S)
|
|
|
|
|
|
|
|
|
2018-05-28 12:17:11 +00:00
|
|
|
def isoncurve(P: Point) -> bool:
|
2018-10-12 10:20:41 +00:00
|
|
|
(x, y, z, t) = P
|
|
|
|
return (
|
|
|
|
z % q != 0
|
|
|
|
and x * y % q == z * t % q
|
|
|
|
and (y * y - x * x - z * z - d * t * t) % q == 0
|
|
|
|
)
|
2017-10-03 22:37:45 +00:00
|
|
|
|
|
|
|
|
2018-10-12 10:20:41 +00:00
|
|
|
class SignatureMismatch(Exception):
|
|
|
|
pass
|
2017-10-03 22:37:45 +00:00
|
|
|
|
|
|
|
|
2018-05-28 12:17:11 +00:00
|
|
|
def checkvalid(s: bytes, m: bytes, pk: bytes) -> None:
|
2018-10-12 10:20:41 +00:00
|
|
|
"""
|
|
|
|
Not safe to use when any argument is secret.
|
|
|
|
|
|
|
|
See module docstring. This function should be used only for
|
|
|
|
verifying public signatures of public messages.
|
|
|
|
"""
|
|
|
|
if len(s) != b // 4:
|
2018-08-13 16:21:24 +00:00
|
|
|
raise ValueError("signature length is wrong")
|
2018-10-12 10:20:41 +00:00
|
|
|
|
|
|
|
if len(pk) != b // 8:
|
2018-08-13 16:21:24 +00:00
|
|
|
raise ValueError("public-key length is wrong")
|
2018-10-12 10:20:41 +00:00
|
|
|
|
|
|
|
R = decodepoint(s[: b // 8])
|
2017-10-03 22:37:45 +00:00
|
|
|
A = decodepoint(pk)
|
2018-10-12 10:20:41 +00:00
|
|
|
S = decodeint(s[b // 8 : b // 4])
|
2017-10-03 22:37:45 +00:00
|
|
|
h = Hint(encodepoint(R) + pk + m)
|
2018-10-12 10:20:41 +00:00
|
|
|
|
|
|
|
(x1, y1, z1, _) = P = scalarmult_B(S)
|
|
|
|
(x2, y2, z2, _) = Q = edwards_add(R, scalarmult(A, h))
|
|
|
|
|
|
|
|
if (
|
|
|
|
not isoncurve(P)
|
|
|
|
or not isoncurve(Q)
|
|
|
|
or (x1 * z2 - x2 * z1) % q != 0
|
|
|
|
or (y1 * z2 - y2 * z1) % q != 0
|
|
|
|
):
|
|
|
|
raise SignatureMismatch("signature does not pass verification")
|