From f1a0f723df2da7ce5963f27b3a854ce7e7802dde Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Fri, 6 Apr 2018 20:24:16 +0200 Subject: [PATCH 1/3] add missing dev reqs --- dev_requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dev_requirements.txt b/dev_requirements.txt index 82386fc36..2e7d9678c 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -4,3 +4,6 @@ pytest>=2.9.0 pytest-catchlog==1.2.2 pytest-timeout==1.0.0 py-ecc +bitcoin +ethereum-serpent +pyepm From d9248b63219379884bf522c76ae69207343ff96b Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Fri, 6 Apr 2018 20:26:47 +0200 Subject: [PATCH 2/3] add ed25519 precompile --- ethereum/ed25519.py | 448 +++++++++++++++++++++++++++++++++++++++++++ ethereum/opcodes.py | 2 + ethereum/specials.py | 45 +++++ 3 files changed, 495 insertions(+) create mode 100644 ethereum/ed25519.py diff --git a/ethereum/ed25519.py b/ethereum/ed25519.py new file mode 100644 index 000000000..276efaaf1 --- /dev/null +++ b/ethereum/ed25519.py @@ -0,0 +1,448 @@ +# "python-pure25519" (https://github.com/warner/python-pure25519/) +# +# Copyright (c) 2015 Brian Warner and other contributors +# +# 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. + +import os +import hashlib +import binascii +import itertools + +__all__ = ('verify',) + +# +# code below from: +# https://github.com/warner/python-pure25519/blob/master/pure25519/basic.py +# + +Q = 2**255 - 19 +L = 2**252 + 27742317777372353535851937790883648493 + +def inv(x): + return pow(x, Q-2, Q) + +d = -121665 * inv(121666) +I = pow(2,(Q-1)//4,Q) + +def xrecover(y): + xx = (y*y-1) * inv(d*y*y+1) + x = pow(xx,(Q+3)//8,Q) + if (x*x - xx) % Q != 0: x = (x*I) % Q + if x % 2 != 0: x = Q-x + return x + +By = 4 * inv(5) +Bx = xrecover(By) +B = [Bx % Q,By % Q] + +# Extended Coordinates: x=X/Z, y=Y/Z, x*y=T/Z +# http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html + +def xform_affine_to_extended(pt): + (x, y) = pt + return (x%Q, y%Q, 1, (x*y)%Q) # (X,Y,Z,T) + +def xform_extended_to_affine(pt): + (x, y, z, _) = pt + return ((x*inv(z))%Q, (y*inv(z))%Q) + +def double_element(pt): # extended->extended + # dbl-2008-hwcd + (X1, Y1, Z1, _) = pt + A = (X1*X1) + B = (Y1*Y1) + C = (2*Z1*Z1) + D = (-A) % Q + J = (X1+Y1) % Q + E = (J*J-A-B) % Q + G = (D+B) % Q + F = (G-C) % Q + H = (D-B) % Q + X3 = (E*F) % Q + Y3 = (G*H) % Q + Z3 = (F*G) % Q + T3 = (E*H) % Q + return (X3, Y3, Z3, T3) + +def add_elements(pt1, pt2): # extended->extended + # add-2008-hwcd-3 . Slightly slower than add-2008-hwcd-4, but -3 is + # unified, so it's safe for general-purpose addition + (X1, Y1, Z1, T1) = pt1 + (X2, Y2, Z2, T2) = pt2 + A = ((Y1-X1)*(Y2-X2)) % Q + B = ((Y1+X1)*(Y2+X2)) % Q + C = T1*(2*d)*T2 % Q + D = Z1*2*Z2 % Q + E = (B-A) % Q + F = (D-C) % Q + G = (D+C) % Q + H = (B+A) % Q + X3 = (E*F) % Q + Y3 = (G*H) % Q + T3 = (E*H) % Q + Z3 = (F*G) % Q + return (X3, Y3, Z3, T3) + +def scalarmult_element_safe_slow(pt, n): + # this form is slightly slower, but tolerates arbitrary points, including + # those which are not in the main 1*L subgroup. This includes points of + # order 1 (the neutral element Zero), 2, 4, and 8. + assert n >= 0 + if n==0: + return xform_affine_to_extended((0,1)) + _ = double_element(scalarmult_element_safe_slow(pt, n>>1)) + return add_elements(_, pt) if n&1 else _ + +def _add_elements_nonunfied(pt1, pt2): # extended->extended + # add-2008-hwcd-4 : NOT unified, only for pt1!=pt2. About 10% faster than + # the (unified) add-2008-hwcd-3, and safe to use inside scalarmult if you + # aren't using points of order 1/2/4/8 + (X1, Y1, Z1, T1) = pt1 + (X2, Y2, Z2, T2) = pt2 + A = ((Y1-X1)*(Y2+X2)) % Q + B = ((Y1+X1)*(Y2-X2)) % Q + C = (Z1*2*T2) % Q + D = (T1*2*Z2) % Q + E = (D+C) % Q + F = (B-A) % Q + G = (B+A) % Q + H = (D-C) % Q + X3 = (E*F) % Q + Y3 = (G*H) % Q + Z3 = (F*G) % Q + T3 = (E*H) % Q + return (X3, Y3, Z3, T3) + +def scalarmult_element(pt, n): # extended->extended + # This form only works properly when given points that are a member of + # the main 1*L subgroup. It will give incorrect answers when called with + # the points of order 1/2/4/8, including point Zero. (it will also work + # properly when given points of order 2*L/4*L/8*L) + assert n >= 0 + if n==0: + return xform_affine_to_extended((0,1)) + _ = double_element(scalarmult_element(pt, n>>1)) + return _add_elements_nonunfied(_, pt) if n&1 else _ + +# points are encoded as 32-bytes little-endian, b255 is sign, b2b1b0 are 0 + +def encodepoint(P): + x = P[0] + y = P[1] + # MSB of output equals x.b0 (=x&1) + # rest of output is little-endian y + assert 0 <= y < (1<<255) # always < 0x7fff..ff + if x & 1: + y += 1<<255 + return binascii.unhexlify("%064x" % y)[::-1] + +def isoncurve(P): + x = P[0] + y = P[1] + return (-x*x + y*y - 1 - d*x*x*y*y) % Q == 0 + +class NotOnCurve(Exception): + pass + +def decodepoint(s): + unclamped = int(binascii.hexlify(s[:32][::-1]), 16) + clamp = (1 << 255) - 1 + y = unclamped & clamp # clear MSB + x = xrecover(y) + if bool(x & 1) != bool(unclamped & (1<<255)): x = Q-x + P = [x,y] + if not isoncurve(P): raise NotOnCurve("decoding point that is not on curve") + return P + +# scalars are encoded as 32-bytes little-endian + +def bytes_to_scalar(s): + assert len(s) == 32, len(s) + return int(binascii.hexlify(s[::-1]), 16) + +def bytes_to_clamped_scalar(s): + # Ed25519 private keys clamp the scalar to ensure two things: + # 1: integer value is in L/2 .. L, to avoid small-logarithm + # non-wraparaound + # 2: low-order 3 bits are zero, so a small-subgroup attack won't learn + # any information + # set the top two bits to 01, and the bottom three to 000 + a_unclamped = bytes_to_scalar(s) + AND_CLAMP = (1<<254) - 1 - 7 + OR_CLAMP = (1<<254) + a_clamped = (a_unclamped & AND_CLAMP) | OR_CLAMP + return a_clamped + +def random_scalar(entropy_f): # 0..L-1 inclusive + # reduce the bias to a safe level by generating 256 extra bits + oversized = int(binascii.hexlify(entropy_f(32+32)), 16) + return oversized % L + +def password_to_scalar(pw): + oversized = hashlib.sha512(pw).digest() + return int(binascii.hexlify(oversized), 16) % L + +def scalar_to_bytes(y): + y = y % L + assert 0 <= y < 2**256 + return binascii.unhexlify("%064x" % y)[::-1] + +# Elements, of various orders + +def is_extended_zero(XYTZ): + # catch Zero + (X, Y, Z, T) = XYTZ + Y = Y % Q + Z = Z % Q + if X==0 and Y==Z and Y!=0: + return True + return False + +class ElementOfUnknownGroup: + # This is used for points of order 2,4,8,2*L,4*L,8*L + def __init__(self, XYTZ): + assert isinstance(XYTZ, tuple) + assert len(XYTZ) == 4 + self.XYTZ = XYTZ + + def add(self, other): + if not isinstance(other, ElementOfUnknownGroup): + raise TypeError("elements can only be added to other elements") + sum_XYTZ = add_elements(self.XYTZ, other.XYTZ) + if is_extended_zero(sum_XYTZ): + return Zero + return ElementOfUnknownGroup(sum_XYTZ) + + def scalarmult(self, s): + if isinstance(s, ElementOfUnknownGroup): + raise TypeError("elements cannot be multiplied together") + assert s >= 0 + product = scalarmult_element_safe_slow(self.XYTZ, s) + return ElementOfUnknownGroup(product) + + def to_bytes(self): + return encodepoint(xform_extended_to_affine(self.XYTZ)) + def __eq__(self, other): + return self.to_bytes() == other.to_bytes() + def __ne__(self, other): + return not self == other + +class Element(ElementOfUnknownGroup): + # this only holds elements in the main 1*L subgroup. It never holds Zero, + # or elements of order 1/2/4/8, or 2*L/4*L/8*L. + + def add(self, other): + if not isinstance(other, ElementOfUnknownGroup): + raise TypeError("elements can only be added to other elements") + sum_element = ElementOfUnknownGroup.add(self, other) + if sum_element is Zero: + return sum_element + if isinstance(other, Element): + # adding two subgroup elements results in another subgroup + # element, or Zero, and we've already excluded Zero + return Element(sum_element.XYTZ) + # not necessarily a subgroup member, so assume not + return sum_element + + def scalarmult(self, s): + if isinstance(s, ElementOfUnknownGroup): + raise TypeError("elements cannot be multiplied together") + # scalarmult of subgroup members can be done modulo the subgroup + # order, and using the faster non-unified function. + s = s % L + # scalarmult(s=0) gets you Zero + if s == 0: + return Zero + # scalarmult(s=1) gets you self, which is a subgroup member + # scalarmult(s Date: Fri, 6 Apr 2018 20:30:47 +0200 Subject: [PATCH 3/3] add (non-working) unit test --- ethereum/tests/test_contracts.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/ethereum/tests/test_contracts.py b/ethereum/tests/test_contracts.py index da4247e43..9550739c9 100644 --- a/ethereum/tests/test_contracts.py +++ b/ethereum/tests/test_contracts.py @@ -1232,6 +1232,24 @@ def test_ecrecover(): assert result == addr +ed25519verify_code = """ +def main(msg:bytes32, vk:bytes32, sig1:bytes32, sig2:bytes32): + return(ed25519verify(msg + vk + sig1 + sig2)) +""" + +def test_ed25519verify(): + c = tester.Chain() + x = c.contract(ed25519verify_code, language='serpent') + msg = b'\0' * 32 + vk = b'\0' * 32 + sig1 = b'\0' * 32 + sig2 = b'\0' * 32 + res = x.main(msg, vk, sig1, sig2) + + # invalid sig should fail + assert x.main() != 0 + + sha256_code = """ def main(): return([sha256(0, chars=0), sha256(3), sha256(text("doge"), chars=3), sha256(text("dog"):str), sha256([0,0,0,0,0]:arr), sha256([0,0,0,0,0,0], items=5)]:arr)