Skip to content

Commit

Permalink
Merge a0937a4 into f9e2838
Browse files Browse the repository at this point in the history
  • Loading branch information
fberetta8 committed Sep 2, 2020
2 parents f9e2838 + a0937a4 commit 4e9e56c
Show file tree
Hide file tree
Showing 41 changed files with 1,285 additions and 789 deletions.
2 changes: 1 addition & 1 deletion HISTORY.md
Expand Up @@ -33,7 +33,7 @@ Major changes includes:
- added Integer as hex-string or bytes representation of an int
- adopted the function signature of dsa.sign for rfc6979.rfc6979 too
- added CURVES dictionary of all elliptic curves, e.g.:
from btclib.curves import CURVES; ec = CURVES['secp256k1']
from btclib.curve import CURVES; ec = CURVES['secp256k1']
- renamed prvkey_info_* as prvkeyinfo_*
- renamed pubkey_info_* as pubkeyinfo_*
- renamed bytes_from_key as pubkeyinfo_from_key
Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Expand Up @@ -27,3 +27,4 @@ recursive-exclude assets *
recursive-exclude _layouts *

recursive-exclude btclib/.mypy_cache *.json
recursive-exclude btclib/tests/.mypy_cache *.json
5 changes: 3 additions & 2 deletions btclib/bip32.py
Expand Up @@ -39,8 +39,7 @@
from . import bip39, electrum
from .alias import INF, BIP32Key, BIP32KeyDict, Octets, Path, Point
from .base58 import b58decode, b58encode
from .curvemult import mult
from .curves import secp256k1 as ec
from .curve import mult, secp256k1
from .mnemonic import Mnemonic
from .network import (
_NETWORKS,
Expand All @@ -53,6 +52,8 @@
from .secpoint import bytes_from_point, point_from_octets
from .utils import bytes_from_octets, hash160, hex_string

ec = secp256k1


def _check_version_key(version: bytes, key: bytes) -> None:

Expand Down
3 changes: 1 addition & 2 deletions btclib/bms.py
Expand Up @@ -141,8 +141,7 @@
from .base58address import h160_from_b58address, p2pkh, p2wpkh_p2sh
from .base58wif import wif_from_prvkey
from .bech32address import p2wpkh, witness_from_b32address
from .curvemult import mult
from .curves import secp256k1
from .curve import mult, secp256k1
from .network import NETWORKS
from .secpoint import bytes_from_point
from .to_prvkey import prvkeyinfo_from_prvkey
Expand Down
5 changes: 3 additions & 2 deletions btclib/borromean.py
Expand Up @@ -14,11 +14,12 @@
from typing import Dict, List, Sequence, Tuple

from .alias import Point, String
from .curvemult import double_mult, mult
from .curves import secp256k1 as ec # FIXME: any curve
from .curve import double_mult, mult, secp256k1
from .secpoint import bytes_from_point
from .utils import int_from_bits

ec = secp256k1 # FIXME: any curve


def _hash(m: bytes, R: bytes, i: int, j: int) -> bytes:
temp = m + R
Expand Down
129 changes: 124 additions & 5 deletions btclib/curve.py
Expand Up @@ -8,12 +8,22 @@
# No part of btclib including this file, may be copied, modified, propagated,
# or distributed except according to the terms contained in the LICENSE file.

"""Elliptic curve classes."""
"""Elliptic curve classes and functions."""

import json
from math import sqrt
from os import path
from typing import Dict, List, Sequence

from .alias import Integer, Point
from .curvegroup import _HEXTHRESHOLD, CurveGroup, _mult_aff
from .alias import Integer, JacPoint, Point
from .curvegroup import (
_HEXTHRESHOLD,
CurveGroup,
_double_mult,
_jac_from_aff,
_mult,
_multi_mult,
)
from .utils import hex_string, int_from_integer


Expand Down Expand Up @@ -98,8 +108,8 @@ def __init__(
if self.G[1] == 0:
m = "INF point cannot be a generator"
raise ValueError(m)
Inf = _mult_aff(n, self.G, self)
if Inf[1] != 0:
InfJ = _mult(n, self.GJ, self)
if InfJ[2] != 0:
err_msg = "n is not the group order: "
err_msg += f"{hex_string(n)}" if n > _HEXTHRESHOLD else f"{n}"
raise ValueError(err_msg)
Expand Down Expand Up @@ -138,3 +148,112 @@ def __repr__(self) -> str:
result += f", {self.h}"
result += ")"
return result


datadir = path.join(path.dirname(__file__), "data")

# Elliptic Curve Cryptography (ECC)
# Brainpool Standard Curves and Curve Generation
# https://tools.ietf.org/html/rfc5639
filename = path.join(datadir, "ec_Brainpool.json")
with open(filename, "r") as f:
Brainpool_params2 = json.load(f)
Brainpool: Dict[str, Curve] = {}
for ec_name in Brainpool_params2:
Brainpool[ec_name] = Curve(*Brainpool_params2[ec_name])


# FIPS PUB 186-4
# FEDERAL INFORMATION PROCESSING STANDARDS PUBLICATION
# Digital Signature Standard (DSS)
# https://oag.ca.gov/sites/all/files/agweb/pdfs/erds1/fips_pub_07_2013.pdf
filename = path.join(datadir, "ec_NIST.json")
with open(filename, "r") as f:
NIST_params2 = json.load(f)
NIST: Dict[str, Curve] = {}
for ec_name in NIST_params2:
NIST[ec_name] = Curve(*NIST_params2[ec_name])


# SEC 2 v.1 curves, removed from SEC 2 v.2 as insecure ones
# http://www.secg.org/SEC2-Ver-1.0.pdf
filename = path.join(datadir, "ec_SEC2v1_insecure.json")
with open(filename, "r") as f:
SEC2v1_params2 = json.load(f)
SEC2v1: Dict[str, Curve] = {}
for ec_name in SEC2v1_params2:
SEC2v1[ec_name] = Curve(*SEC2v1_params2[ec_name])


# curves included in both SEC 2 v.1 and SEC 2 v.2
# http://www.secg.org/sec2-v2.pdf
filename = path.join(datadir, "ec_SEC2v2.json")
with open(filename, "r") as f:
SEC2v2_params2 = json.load(f)
SEC2v2: Dict[str, Curve] = {}
for ec_name in SEC2v2_params2:
SEC2v2[ec_name] = Curve(*SEC2v2_params2[ec_name])
SEC2v1[ec_name] = Curve(*SEC2v2_params2[ec_name])

CURVES = SEC2v1
CURVES.update(NIST)
CURVES.update(Brainpool)

secp256k1 = CURVES["secp256k1"]


def mult(m: Integer, Q: Point = None, ec: Curve = secp256k1) -> Point:
"Elliptic curve scalar multiplication."
if Q is None:
QJ = ec.GJ
else:
ec.require_on_curve(Q)
QJ = _jac_from_aff(Q)

m = int_from_integer(m) % ec.n
R = _mult(m, QJ, ec)
return ec._aff_from_jac(R)


def double_mult(
u: Integer, H: Point, v: Integer, Q: Point, ec: Curve = secp256k1
) -> Point:
"Double scalar multiplication (u*H + v*Q)."

ec.require_on_curve(H)
HJ = _jac_from_aff(H)

ec.require_on_curve(Q)
QJ = _jac_from_aff(Q)

u = int_from_integer(u) % ec.n
v = int_from_integer(v) % ec.n
R = _double_mult(u, HJ, v, QJ, ec)
return ec._aff_from_jac(R)


def multi_mult(
scalars: Sequence[Integer], Points: Sequence[Point], ec: Curve = secp256k1
) -> Point:
"""Return the multi scalar multiplication u1*Q1 + ... + un*Qn.
Use Bos-Coster's algorithm for efficient computation.
"""

if len(scalars) != len(Points):
errMsg = "mismatch between number of scalars and points: "
errMsg += f"{len(scalars)} vs {len(Points)}"
raise ValueError(errMsg)

JPoints: List[JacPoint] = list()
ints: List[int] = list()
for P, i in zip(Points, scalars):
i = int_from_integer(i) % ec.n
if i == 0: # early optimization, even if not strictly necessary
continue
ints.append(i)
ec.require_on_curve(P)
JPoints.append(_jac_from_aff(P))

R = _multi_mult(ints, JPoints, ec)
return ec._aff_from_jac(R)
153 changes: 152 additions & 1 deletion btclib/curvegroup.py
Expand Up @@ -16,6 +16,7 @@
see the btclib.curve module.
"""

import functools
import heapq
from math import ceil
from typing import List, Sequence, Tuple, Union
Expand Down Expand Up @@ -443,6 +444,156 @@ def _mult_jac(m: int, Q: JacPoint, ec: CurveGroup) -> JacPoint:
return R[0]


def multiples(Q: JacPoint, size: int, ec: CurveGroup) -> List[JacPoint]:
"Return {k_i * Q} for k_i in {0, ..., size-1)"

if size < 2:
raise ValueError(f"size too low: {size}")

k, odd = divmod(size, 2)
T = [INFJ, Q]
for i in range(3, k * 2, 2):
T.append(ec._double_jac(T[(i - 1) // 2]))
T.append(ec._add_jac(T[-1], Q))

if odd:
T.append(ec._double_jac(T[(size - 1) // 2]))

return T


_MAX_W = 5


@functools.lru_cache() # least recently used cache
def cached_multiples(Q: JacPoint, ec: CurveGroup) -> List[JacPoint]:

T = [INFJ, Q]
for i in range(3, 2 ** _MAX_W, 2):
T.append(ec._double_jac(T[(i - 1) // 2]))
T.append(ec._add_jac(T[-1], Q))
return T


def convert_number_to_base(i: int, base: int) -> List[int]:
"Return the digits of an integer in the requested base."

digits: List[int] = []
while i or not digits:
i, idx = divmod(i, base)
digits.append(idx)
return digits[::-1]


def _mult_mont_ladder(m: int, Q: JacPoint, ec: CurveGroup) -> JacPoint:
"""Scalar multiplication using 'Montgomery ladder' algorithm.
This implementation uses
'Montgomery ladder' algorithm,
'left-to-right' binary decomposition of the m coefficient,
Jacobian coordinates.
It is constant-time and resistant to the FLUSH+RELOAD attack,
(see https://eprint.iacr.org/2014/140.pdf)
as it prevents branch prediction avoiding any if.
The input point is assumed to be on curve and
the m coefficient 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)}")

# R[0] is the running resultR[1] = R[0] + Q is an ancillary variable
R = [INFJ, Q]
for i in [int(i) for i in bin(m)[2:]]:
R[not i] = ec._add_jac(R[i], R[not i])
R[i] = ec._double_jac(R[i])
return R[0]


def _mult_base_3(m: int, Q: JacPoint, ec: CurveGroup) -> JacPoint:
"""Scalar multiplication using ternary decomposition of the scalar.
This implementation uses
'triple & add' algorithm,
'left-to-right' ternary decomposition of the m coefficient,
Jacobian coordinates.
The input point is assumed to be on curve and
the m coefficient 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)}")

# at each step one of the points in T will be added
T = [INFJ, Q, ec._double_jac(Q)]
# T = multiples(Q, 3, ec)
# T = cached_multiples(Q, ec)

digits = convert_number_to_base(m, 3)

R = T[digits[0]]
for i in digits[1:]:
# 'triple'
R2 = ec._double_jac(R)
R3 = ec._add_jac(R2, R)
# and 'add'
R = ec._add_jac(R3, T[i])
return R


def _mult_fixed_window(
m: int, Q: JacPoint, ec: CurveGroup, w: int = 4, cached: bool = False
) -> JacPoint:
"""Scalar multiplication using "fixed window".
This implementation uses
'multiple-double & add' algorithm,
'left-to-right' window decomposition of the m coefficient,
Jacobian coordinates.
For 256-bit scalars it is suggested to choose w=4 or w=5.
The input point is assumed to be on curve and
the m coefficient 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)}")

# a number cannot be written in basis 1 (ie w=0)
if w <= 0:
raise ValueError(f"non positive w: {w}")

# at each step one of the points in T will be added
# T = cached_multiples(Q, ec)
# T = multiples(Q, 2 ** w, ec)

if cached:
T = cached_multiples(Q, ec)
else:
T = multiples(Q, 2 ** w, ec)

digits = convert_number_to_base(m, 2 ** w)

R = T[digits[0]]
for i in digits[1:]:
# multiple 'double'
for _ in range(w):
R = ec._double_jac(R)
# and 'add'
R = ec._add_jac(R, T[i])
return R


_mult = _mult_fixed_window


def _double_mult(
u: int, HJ: JacPoint, v: int, QJ: JacPoint, ec: CurveGroup
) -> JacPoint:
Expand Down Expand Up @@ -534,4 +685,4 @@ def _multi_mult(
n1, p1 = -np1[0], np1[1]
# assert n1 < ec.n, "better to take the mod n"
# n1 %= ec.n
return _mult_jac(n1, p1, ec)
return _mult(n1, p1, ec)

0 comments on commit 4e9e56c

Please sign in to comment.