Skip to content

Commit

Permalink
Merge af8fac1 into 39a086e
Browse files Browse the repository at this point in the history
  • Loading branch information
giacomocaironi committed May 28, 2020
2 parents 39a086e + af8fac1 commit bec849f
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 34 deletions.
74 changes: 46 additions & 28 deletions btclib/curve.py
Expand Up @@ -221,6 +221,52 @@ def _add_jac(self, Q: JacPoint, R: JacPoint) -> JacPoint:
Z = (V * Q[2] * R[2]) % self.p
return X, Y, Z

def _unsafe_add_jac(self, Q: JacPoint, R: JacPoint) -> JacPoint:
# R and Q must be different

RZ2 = R[2] * R[2]
RZ3 = RZ2 * R[2]
QZ2 = Q[2] * Q[2]
QZ3 = QZ2 * Q[2]
if Q[0] * RZ2 % self.p == R[0] * QZ2 % self.p:
if Q[1] * RZ3 % self.p != R[1] * QZ3 % self.p:
return INFJ

if Q[2] == 0: # Infinity point in Jacobian coordinates
return R
if R[2] == 0: # Infinity point in Jacobian coordinates
return Q

T = (Q[1] * RZ3) % self.p
U = (R[1] * QZ3) % self.p
W = (U - T) % self.p

M = (Q[0] * RZ2) % self.p
N = (R[0] * QZ2) % self.p
V = (N - M) % self.p

V2 = V * V
V3 = V2 * V
MV2 = M * V2
X = (W * W - V3 - 2 * MV2) % self.p
Y = (W * (MV2 - X) - T * V3) % self.p
Z = (V * Q[2] * R[2]) % self.p
return X, Y, Z

def _double_jac(self, Q: JacPoint) -> JacPoint:

if Q[2] == 0:
return INFJ

QZ2 = Q[2] * Q[2]
QY2 = Q[1] * Q[1]
W = (3 * Q[0] * Q[0] + self._a * QZ2 * QZ2) % self.p
V = (4 * Q[0] * QY2) % self.p
X = (W * W - 2 * V) % self.p
Y = (W * (V - X) - 8 * QY2 * QY2) % self.p
Z = (2 * Q[1] * Q[2]) % self.p
return X, Y, Z

def _add_aff(self, Q: Point, R: Point) -> Point:
# points are assumed to be on curve
if R[1] == 0: # Infinity point in affine coordinates
Expand Down Expand Up @@ -352,34 +398,6 @@ def _mult_aff(m: int, Q: Point, ec: CurveGroup) -> Point:
return R


def _mult_jac(m: int, Q: JacPoint, ec: CurveGroup) -> JacPoint:
"""Scalar multiplication of a curve point in Jacobian coordinates.
This implementation uses 'double & add' algorithm,
binary decomposition of m,
affine coordinates.
It is not constant-time.
The input point is assumed to be on curve,
m is assumed to have been reduced mod n if appropriate
(e.g. cyclic groups of order n).
"""

if m < 0:
raise ValueError(f"negative m: {hex(m)}")

# there is not a compelling reason to optimize for INFJ, even if possible
# if Q[2] == 1: # Infinity point, Jacobian coordinates
# return INFJ # return Infinity point
R = INFJ # initialize as infinity point
while m > 0: # use binary representation of m
if m & 1: # if least significant bit is 1
R = ec._add_jac(R, Q) # then add current Q
m = m >> 1 # remove the bit just accounted for
Q = ec._add_jac(Q, Q) # double Q for next step
return R


class CurveSubGroup(CurveGroup):
"Subgroup of the points of an elliptic curve over Fp generated by G."

Expand Down
61 changes: 59 additions & 2 deletions btclib/curvemult.py
Expand Up @@ -14,12 +14,69 @@
from typing import List, Sequence, Tuple

from .alias import INFJ, Integer, JacPoint, Point
from .curve import Curve, CurveGroup, _jac_from_aff, _mult_jac
from .curve import Curve, CurveGroup, _jac_from_aff
from .curves import secp256k1
from .utils import int_from_integer


def mult(m: Integer, Q: Point = None, ec: Curve = secp256k1) -> Point:
def _mult_jac(m: int, Q: JacPoint, ec: CurveGroup) -> JacPoint:
"""Scalar multiplication of a curve point in Jacobian coordinates.
This implementation uses 'double & add' algorithm,
binary decomposition of m,
jacobian coordinates.
It is not constant-time.
The input point is assumed to be on curve,
m is assumed to have been reduced mod n if appropriate
(e.g. cyclic groups of order n).
"""

if m < 0:
raise ValueError(f"negative m: {hex(m)}")

if Q == INFJ: # no need to multiply if Q is 0
return Q

R = INFJ # initialize as infinity point
while m > 0: # use binary representation of m
if m & 1: # if least significant bit is 1
R = ec._add_jac(R, Q) # then add current Q
m = m >> 1 # remove the bit just accounted for
Q = ec._double_jac(Q) # double Q for next step
return R


def _constant_time_mult_jac(m: int, Q: JacPoint, ec: CurveGroup) -> JacPoint:
"""Scalar multiplication of a curve point in Jacobian coordinates.
This implementation uses 'montgomery ladder' algorithm,
jacobian coordinates.
It is constant-time if the binary size of Q remains the same.
The input point is assumed to be on curve,
m is assumed to have been reduced mod n if appropriate
(e.g. cyclic groups of order n).
"""

if m < 0:
raise ValueError(f"negative m: {hex(m)}")

if Q == INFJ:
return Q

R = INFJ # initialize as infinity point
for m in [int(i) for i in bin(m)[2:]]: # goes through binary digits
if m == 0:
Q = ec._add_jac(R, Q)
R = ec._double_jac(R)
else:
R = ec._add_jac(R, Q)
Q = ec._double_jac(Q)
return R


def mult(m: int, Q: Point = None, ec: Curve = secp256k1) -> Point:
"""Point multiplication, implemented using 'double and add'.
Computations use Jacobian coordinates and binary decomposition of m.
Expand Down
7 changes: 6 additions & 1 deletion btclib/tests/test_curve.py
Expand Up @@ -15,7 +15,8 @@
import pytest

from btclib.alias import INF
from btclib.curve import Curve, _jac_from_aff, _mult_aff, _mult_jac
from btclib.curve import Curve, _jac_from_aff, _mult_aff
from btclib.curvemult import _mult_jac, _constant_time_mult_jac


def test_exceptions():
Expand Down Expand Up @@ -82,3 +83,7 @@ def test_jac():
assert ec._jac_equality(QJ, _jac_from_aff(Q))
assert not ec._jac_equality(QJ, ec.negate(QJ))
assert not ec._jac_equality(QJ, ec.GJ)
QJ2 = _constant_time_mult_jac(q, ec.GJ, ec)
assert ec._jac_equality(QJ2, _jac_from_aff(Q))
assert not ec._jac_equality(QJ2, ec.negate(QJ2))
assert not ec._jac_equality(QJ2, ec.GJ)
11 changes: 9 additions & 2 deletions btclib/tests/test_curvemult.py
Expand Up @@ -15,8 +15,15 @@
import pytest

from btclib.alias import INF, INFJ
from btclib.curve import _jac_from_aff, _mult_jac
from btclib.curvemult import _double_mult, _multi_mult, double_mult, mult, multi_mult
from btclib.curve import _jac_from_aff
from btclib.curvemult import (
_double_mult,
_multi_mult,
double_mult,
mult,
multi_mult,
_mult_jac,
)
from btclib.curves import secp256k1
from btclib.pedersen import second_generator
from btclib.tests.test_curves import low_card_curves
Expand Down
6 changes: 5 additions & 1 deletion btclib/tests/test_curves.py
Expand Up @@ -16,7 +16,8 @@
import pytest

from btclib.alias import INF, INFJ
from btclib.curve import Curve, _jac_from_aff, _mult_aff, _mult_jac
from btclib.curve import Curve, _jac_from_aff, _mult_aff
from btclib.curvemult import _mult_jac, _constant_time_mult_jac
from btclib.curves import CURVES
from btclib.numbertheory import mod_sqrt

Expand Down Expand Up @@ -288,6 +289,9 @@ def test_mult():
for q in range(ec.n):
Q = _mult_aff(q, ec.G, ec)
QJ = _mult_jac(q, ec.GJ, ec)
QJ2 = _constant_time_mult_jac(q, ec.GJ, ec)
assert Q == ec._aff_from_jac(QJ)
assert Q == ec._aff_from_jac(QJ2)
assert INF == _mult_aff(q, INF, ec)
assert INFJ == _mult_jac(q, INFJ, ec)
assert INFJ == _constant_time_mult_jac(q, INFJ, ec)

0 comments on commit bec849f

Please sign in to comment.