Esempio n. 1
0
def point_from_bip340pub_key(x_Q: BIP340PubKey,
                             ec: Curve = secp256k1) -> Point:
    """Return a verified-as-valid BIP340 public key as Point tuple.

    It supports:

    - BIP32 extended keys (bytes, string, or BIP32KeyData)
    - SEC Octets (bytes or hex-string, with 02, 03, or 04 prefix)
    - BIP340 Octets (bytes or hex-string, p-size Point x-coordinate)
    - native tuple
    """

    # BIP 340 key as integer
    if isinstance(x_Q, int):
        return x_Q, ec.y_even(x_Q)

    # (tuple) Point, (dict or str) BIP32Key, or 33/65 bytes
    try:
        x_Q = point_from_pub_key(x_Q, ec)[0]
        return x_Q, ec.y_even(x_Q)
    except BTClibValueError:
        pass

    # BIP 340 key as bytes or hex-string
    if isinstance(x_Q, (str, bytes)):
        Q = bytes_from_octets(x_Q, ec.p_size)
        x_Q = int.from_bytes(Q, "big", signed=False)
        return x_Q, ec.y_even(x_Q)

    raise BTClibTypeError("not a BIP340 public key")
Esempio n. 2
0
def _recover_pub_key_(c: int, r: int, s: int, ec: Curve) -> int:
    # Private function provided for testing purposes only.

    if c == 0:
        raise BTClibRuntimeError("invalid zero challenge")

    KJ = r, ec.y_even(r), 1

    e1 = mod_inv(c, ec.n)
    QJ = _double_mult(ec.n - e1, KJ, e1 * s, ec.GJ, ec)
    # edge case that cannot be reproduced in the test suite
    if QJ[2] == 0:
        err_msg = "invalid (INF) key"  # pragma: no cover
        raise BTClibRuntimeError(err_msg)  # pragma: no cover
    return ec.x_aff_from_jac(QJ)
Esempio n. 3
0
def gen_keys_(prv_key: Optional[PrvKey] = None,
              ec: Curve = secp256k1) -> Tuple[int, int, JacPoint]:
    "Return a BIP340 private/public (int, JacPoint) key-pair."

    if prv_key is None:
        q = 1 + secrets.randbelow(ec.n - 1)
    else:
        q = int_from_prv_key(prv_key, ec)

    QJ = _mult(q, ec.GJ, ec)
    x_Q, y_Q = ec.aff_from_jac(QJ)
    if y_Q % 2:
        q = ec.n - q
        QJ = ec.negate_jac(QJ)

    return q, x_Q, QJ
Esempio n. 4
0
def second_generator(ec: Curve = secp256k1, hf: HashF = sha256) -> Point:
    """Second (with respect to G) elliptic curve generator.

    Second (with respect to G) Nothing-Up-My-Sleeve (NUMS)
    elliptic curve generator.

    The hash of G is coerced it to a point (x_H, y_H).
    If the resulting point is not on the curve, keep on
    incrementing x_H until a valid curve point (x_H, y_H) is obtained.

    idea:
    https://crypto.stackexchange.com/questions/25581/second-generator-for-secp256k1-curve

    source:
    https://github.com/ElementsProject/secp256k1-zkp/blob/secp256k1-zkp/src/modules/rangeproof/main_impl.h
    """

    G_bytes = bytes_from_point(ec.G, ec, compressed=False)
    hash_ = hf()
    hash_.update(G_bytes)
    hash_digest = hash_.digest()
    x_H = int_from_bits(hash_digest, ec.nlen) % ec.n
    while True:
        try:
            y_H = ec.y_even(x_H)
            return x_H, y_H
        except BTClibValueError:
            x_H += 1
            x_H %= ec.p
Esempio n. 5
0
def point_from_octets(pub_key: Octets, ec: Curve = secp256k1) -> Point:
    """Return a tuple (x_Q, y_Q) that belongs to the curve.

    Return a tuple (x_Q, y_Q) that belongs to the curve according to
    SEC 1 v.2, section 2.3.4.
    """

    pub_key = bytes_from_octets(pub_key, (ec.p_size + 1, 2 * ec.p_size + 1))

    bsize = len(pub_key)  # bytes
    if pub_key[0] in (0x02, 0x03):  # compressed point
        if bsize != ec.p_size + 1:
            err_msg = "invalid size for compressed point: "
            err_msg += f"{bsize} instead of {ec.p_size + 1}"
            raise BTClibValueError(err_msg)
        x_Q = int.from_bytes(pub_key[1:], byteorder="big")
        try:
            y_Q = ec.y_even(x_Q)  # also check x_Q validity
            return x_Q, y_Q if pub_key[0] == 0x02 else ec.p - y_Q
        except BTClibValueError as e:
            msg = f"invalid x-coordinate: '{hex_string(x_Q)}'"
            raise BTClibValueError(msg) from e
    elif pub_key[0] == 0x04:  # uncompressed point
        if bsize != 2 * ec.p_size + 1:
            err_msg = "invalid size for uncompressed point: "
            err_msg += f"{bsize} instead of {2 * ec.p_size + 1}"
            raise BTClibValueError(err_msg)
        x_Q = int.from_bytes(pub_key[1:ec.p_size + 1],
                             byteorder="big",
                             signed=False)
        Q = x_Q, int.from_bytes(pub_key[ec.p_size + 1:],
                                byteorder="big",
                                signed=False)
        if Q[1] == 0:  # infinity point in affine coordinates
            raise BTClibValueError(
                "no bytes representation for infinity point")
        if ec.is_on_curve(Q):
            return Q
        raise BTClibValueError(f"point not on curve: {Q}")
    else:
        raise BTClibValueError(f"not a point: {pub_key!r}")
Esempio n. 6
0
def bytes_from_point(Q: Point,
                     ec: Curve = secp256k1,
                     compressed: bool = True) -> bytes:
    """Return a point as compressed/uncompressed octet sequence.

    Return a point as compressed (0x02, 0x03) or uncompressed (0x04)
    octet sequence, according to SEC 1 v.2, section 2.3.3.
    """

    # check that Q is a point and that is on curve
    ec.require_on_curve(Q)

    if Q[1] == 0:  # infinity point in affine coordinates
        raise BTClibValueError("no bytes representation for infinity point")

    bytes_ = Q[0].to_bytes(ec.p_size, byteorder="big", signed=False)
    if compressed:
        return (b"\x03" if (Q[1] & 1) else b"\x02") + bytes_

    return b"\x04" + bytes_ + Q[1].to_bytes(
        ec.p_size, byteorder="big", signed=False)
Esempio n. 7
0
def gen_keys(prv_key: Optional[PrvKey] = None,
             ec: Curve = secp256k1) -> Tuple[int, Point]:
    "Return a private/public (int, Point) key-pair."

    if prv_key is None:
        # q in the range [1, ec.n-1]
        q = 1 + secrets.randbelow(ec.n - 1)
    else:
        q = int_from_prv_key(prv_key, ec)

    QJ = _mult(q, ec.GJ, ec)
    Q = ec.aff_from_jac(QJ)
    return q, Q
Esempio n. 8
0
def output_pubkey(
    internal_pubkey: Optional[Key] = None,
    script_tree: Optional[TaprootScriptTree] = None,
    ec: Curve = secp256k1,
) -> Tuple[bytes, int]:
    if not internal_pubkey and not script_tree:
        raise BTClibValueError("Missing data")
    if internal_pubkey:
        pubkey = pub_keyinfo_from_key(internal_pubkey, compressed=True)[0][1:]
    else:
        h_str = "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"
        pubkey = bytes.fromhex(h_str)
    if script_tree:
        _, h = tree_helper(script_tree)
    else:
        h = tagged_hash(b"TapTweak", pubkey)
    t = int.from_bytes(tagged_hash(b"TapTweak", pubkey + h), "big")
    # edge case that cannot be reproduced in the test suite
    if t >= ec.n:
        raise BTClibValueError("Invalid script tree hash")  # pragma: no cover
    x = int.from_bytes(pubkey, "big")
    Q = ec.add((x, ec.y_even(x)), mult(t))
    return Q[0].to_bytes(32, "big"), Q[1] % 2
Esempio n. 9
0
def _assert_as_valid_(c: int, QJ: JacPoint, r: int, s: int, ec: Curve) -> None:
    # Private function for test/dev purposes
    # It raises Errors, while verify should always return True or False

    # Let K = sG - eQ.
    # in Jacobian coordinates
    KJ = _double_mult(ec.n - c, QJ, s, ec.GJ, ec)

    # Fail if infinite(KJ).
    # Fail if y_K is odd.
    if ec.y_aff_from_jac(KJ) % 2:
        raise BTClibRuntimeError("y_K is odd")

    # Fail if x_K ≠ r
    if KJ[0] != KJ[2] * KJ[2] * r % ec.p:
        raise BTClibRuntimeError("signature verification failed")
Esempio n. 10
0
def output_prvkey(
    internal_prvkey: PrvKey,
    script_tree: Optional[TaprootScriptTree] = None,
    ec: Curve = secp256k1,
) -> int:
    internal_prvkey = int_from_prv_key(internal_prvkey)
    P = mult(internal_prvkey)
    if script_tree:
        _, h = tree_helper(script_tree)
    else:
        h = b""
    has_even_y = ec.y_even(P[0]) == P[1]
    internal_prvkey = internal_prvkey if has_even_y else ec.n - internal_prvkey
    t = int.from_bytes(tagged_hash(b"TapTweak", P[0].to_bytes(32, "big") + h), "big")
    # edge case that cannot be reproduced in the test suite
    if t >= ec.n:
        raise BTClibValueError("Invalid script tree hash")  # pragma: no cover
    return (internal_prvkey + t) % ec.n
Esempio n. 11
0
def point_from_pub_key(pub_key: PubKey, ec: Curve = secp256k1) -> Point:
    "Return an elliptic curve point tuple from a public key."

    if isinstance(pub_key, tuple):
        if ec.is_on_curve(pub_key) and pub_key[1] != 0:
            return pub_key
        raise BTClibValueError(f"not a valid public key: {pub_key}")
    if isinstance(pub_key, BIP32KeyData):
        return _point_from_xpub(pub_key, ec)
    try:
        return _point_from_xpub(pub_key, ec)
    except (TypeError, BTClibValueError):
        pass

    # it must be octets
    try:
        return point_from_octets(pub_key, ec)
    except (TypeError, ValueError) as e:
        raise BTClibValueError(f"not a public key: {pub_key!r}") from e
Esempio n. 12
0
def _recover_pub_keys_(c: int, r: int, s: int, lower_s: bool,
                       ec: Curve) -> List[JacPoint]:
    # Private function provided for testing purposes only.

    # precomputations
    r_1 = mod_inv(r, ec.n)
    r1s = r_1 * s % ec.n
    r1e = -r_1 * c % ec.n
    keys: List[JacPoint] = []
    # r = K[0] % ec.n
    # if ec.n < K[0] < ec.p (likely when cofactor ec.cofactor > 1)
    # then both x_K=r and x_K=r+ec.n must be tested
    for j in range(ec.cofactor + 1):  # 1
        # affine x_K-coordinate of K (field element)
        x_K = (r + j * ec.n) % ec.p  # 1.1
        # two possible y_K-coordinates, i.e. two possible keys for each cycle
        try:
            # even root first for bitcoin message signing compatibility
            yodd = ec.y_even(x_K)
            KJ = x_K, yodd, 1  # 1.2, 1.3, and 1.4
            # 1.5 has been performed in the recover_pub_keys calling function
            QJ = _double_mult(r1s, KJ, r1e, ec.GJ, ec)  # 1.6.1
            try:
                _assert_as_valid_(c, QJ, r, s, lower_s, ec)  # 1.6.2
            except (BTClibValueError, BTClibRuntimeError):
                pass
            else:
                keys.append(QJ)  # 1.6.2
            KJ = x_K, ec.p - yodd, 1  # 1.6.3
            QJ = _double_mult(r1s, KJ, r1e, ec.GJ, ec)
            try:
                _assert_as_valid_(c, QJ, r, s, lower_s, ec)  # 1.6.2
            except (BTClibValueError, BTClibRuntimeError):
                pass
            else:
                keys.append(QJ)  # 1.6.2
        except (BTClibValueError,
                BTClibRuntimeError):  # K is not a curve point
            pass
    return keys
Esempio n. 13
0
def test_crack_prv_key() -> None:

    ec = CURVES["secp256k1"]

    q, _ = dsa.gen_keys(1)
    k = 1 + secrets.randbelow(ec.n - 1)

    msg1 = "Paolo is afraid of ephemeral random numbers".encode()
    m_1 = reduce_to_hlen(msg1)
    sig1 = dsa.sign_(m_1, q, k)

    msg2 = "and Paolo is right to be afraid".encode()
    m_2 = reduce_to_hlen(msg2)
    sig2 = dsa.sign_(m_2, q, k)

    q_cracked, k_cracked = dsa.crack_prv_key(msg1, sig1.serialize(), msg2,
                                             sig2)

    #  if the lower_s convention has changed only one of s1 and s2
    sig2 = dsa.Sig(sig2.r, ec.n - sig2.s)
    qc2, kc2 = dsa.crack_prv_key(msg1, sig1, msg2, sig2.serialize())

    assert (q == q_cracked and k in (k_cracked, ec.n - k_cracked)) or (
        q == qc2 and k in (kc2, ec.n - kc2))

    with pytest.raises(BTClibValueError, match="not the same r in signatures"):
        dsa.crack_prv_key(msg1, sig1, msg2, dsa.Sig(16, sig1.s))

    with pytest.raises(BTClibValueError, match="identical signatures"):
        dsa.crack_prv_key(msg1, sig1, msg1, sig1)

    a = ec._a  # pylint: disable=protected-access
    b = ec._b  # pylint: disable=protected-access
    alt_ec = Curve(ec.p, a, b, ec.double_aff(ec.G), ec.n, ec.cofactor)
    sig = dsa.Sig(sig1.r, sig1.s, alt_ec)
    with pytest.raises(BTClibValueError,
                       match="not the same curve in signatures"):
        dsa.crack_prv_key(msg1, sig, msg2, sig2)
Esempio n. 14
0
def _recover_pub_key_(key_id: int, c: int, r: int, s: int, lower_s: bool,
                      ec: Curve) -> JacPoint:
    # Private function provided for testing purposes only.

    # precomputations
    r_1 = mod_inv(r, ec.n)
    r1s = r_1 * s % ec.n
    r1e = -r_1 * c % ec.n
    # r = K[0] % ec.n
    # if ec.n < K[0] < ec.p (likely when cofactor ec.cofactor > 1)
    # then both x_K=r and x_K=r+ec.n must be tested
    j = key_id & 0b110  # allow for key_id in [0, 7]
    x_K = (r + j * ec.n) % ec.p  # 1.1

    # even root first for Bitcoin Core compatibility
    i = key_id & 0b01
    y_even = ec.y_even(x_K)
    y_K = ec.p - y_even if i else y_even
    KJ = x_K, y_K, 1  # 1.2, 1.3, and 1.4
    # 1.5 has been performed in the recover_pub_keys calling function
    QJ = _double_mult(r1s, KJ, r1e, ec.GJ, ec)  # 1.6.1
    _assert_as_valid_(c, QJ, r, s, lower_s, ec)  # 1.6.2
    return QJ
Esempio n. 15
0
def test_exceptions() -> None:

    # good curve
    Curve(13, 0, 2, (1, 9), 19, 1, False)

    with pytest.raises(BTClibValueError, match="p is not prime: "):
        Curve(15, 0, 2, (1, 9), 19, 1, False)

    with pytest.raises(BTClibValueError, match="negative a: "):
        Curve(13, -1, 2, (1, 9), 19, 1, False)

    with pytest.raises(BTClibValueError, match="p <= a: "):
        Curve(13, 13, 2, (1, 9), 19, 1, False)

    with pytest.raises(BTClibValueError, match="negative b: "):
        Curve(13, 0, -2, (1, 9), 19, 1, False)

    with pytest.raises(BTClibValueError, match="p <= b: "):
        Curve(13, 0, 13, (1, 9), 19, 1, False)

    with pytest.raises(BTClibValueError, match="zero discriminant"):
        Curve(11, 7, 7, (1, 9), 19, 1, False)

    err_msg = "Generator must a be a sequence\\[int, int\\]"
    with pytest.raises(BTClibValueError, match=err_msg):
        Curve(13, 0, 2, (1, 9, 1), 19, 1, False)  # type: ignore

    with pytest.raises(BTClibValueError,
                       match="Generator is not on the curve"):
        Curve(13, 0, 2, (2, 9), 19, 1, False)

    with pytest.raises(BTClibValueError, match="n is not prime: "):
        Curve(13, 0, 2, (1, 9), 20, 1, False)

    with pytest.raises(BTClibValueError, match="n not in "):
        Curve(13, 0, 2, (1, 9), 71, 1, False)

    with pytest.raises(BTClibValueError,
                       match="INF point cannot be a generator"):
        Curve(13, 0, 2, INF, 19, 1, False)

    with pytest.raises(BTClibValueError, match="n is not the group order: "):
        Curve(13, 0, 2, (1, 9), 17, 1, False)

    with pytest.raises(BTClibValueError, match="invalid cofactor: "):
        Curve(13, 0, 2, (1, 9), 19, 2, False)

    # n=p -> weak curve
    # missing

    with pytest.raises(UserWarning, match="weak curve"):
        Curve(11, 2, 7, (6, 9), 7, 2, True)
Esempio n. 16
0
import pytest

from btclib.alias import INF, INFJ
from btclib.ecc.curve import CURVES, Curve, double_mult, mult, multi_mult, secp256k1
from btclib.ecc.curve_group import jac_from_aff
from btclib.ecc.number_theory import mod_sqrt
from btclib.ecc.pedersen import second_generator
from btclib.exceptions import BTClibTypeError, BTClibValueError

# FIXME Curve repr should use "dedbeef 00000000", not "0xdedbeef00000000"
# FIXME test curves when n>p

# test curves: very low cardinality
low_card_curves: Dict[str, Curve] = {}
# 13 % 4 = 1; 13 % 8 = 5
low_card_curves["ec13_11"] = Curve(13, 7, 6, (1, 1), 11, 1, False)
low_card_curves["ec13_19"] = Curve(13, 0, 2, (1, 9), 19, 1, False)
# 17 % 4 = 1; 17 % 8 = 1
low_card_curves["ec17_13"] = Curve(17, 6, 8, (0, 12), 13, 2, False)
low_card_curves["ec17_23"] = Curve(17, 3, 5, (1, 14), 23, 1, False)
# 19 % 4 = 3; 19 % 8 = 3
low_card_curves["ec19_13"] = Curve(19, 0, 2, (4, 16), 13, 2, False)
low_card_curves["ec19_23"] = Curve(19, 2, 9, (0, 16), 23, 1, False)
# 23 % 4 = 3; 23 % 8 = 7
low_card_curves["ec23_19"] = Curve(23, 9, 7, (5, 4), 19, 1, False)
low_card_curves["ec23_31"] = Curve(23, 5, 1, (0, 1), 31, 1, False)

all_curves: Dict[str, Curve] = {}
all_curves.update(low_card_curves)
all_curves.update(CURVES)
Esempio n. 17
0
from btclib.ecc.curve import Curve

# low cardinality curves p<100
ec11_7 = Curve(11, 2, 7, (6, 9), 7, 2, False)
ec11_17 = Curve(11, 2, 4, (0, 9), 17, 1, False)
ec13_11 = Curve(13, 7, 6, (1, 1), 11, 1, False)
ec13_19 = Curve(13, 0, 2, (1, 9), 19, 1, False)
ec17_13 = Curve(17, 6, 8, (0, 12), 13, 2, False)
ec17_23 = Curve(17, 3, 5, (1, 14), 23, 1, False)
ec19_13 = Curve(19, 0, 2, (4, 16), 13, 2, False)
ec19_23 = Curve(19, 2, 9, (0, 16), 23, 1, False)
ec23_19 = Curve(23, 9, 7, (5, 4), 19, 1, False)
ec23_31 = Curve(23, 5, 1, (0, 1), 31, 1, False)
ec29_37 = Curve(29, 4, 9, (0, 26), 37, 1, False)
ec31_23 = Curve(31, 4, 7, (0, 10), 23, 1, False)
ec31_43 = Curve(31, 0, 3, (1, 2), 43, 1, False)
ec37_31 = Curve(37, 2, 8, (1, 23), 31, 1, False)
ec37_43 = Curve(37, 2, 9, (0, 34), 43, 1, False)
ec41_37 = Curve(41, 2, 6, (1, 38), 37, 1, False)
ec41_53 = Curve(41, 4, 4, (0, 2), 53, 1, False)
ec43_37 = Curve(43, 1, 5, (2, 31), 37, 1, False)
ec43_47 = Curve(43, 1, 3, (2, 23), 47, 1, False)
ec47_41 = Curve(47, 3, 9, (0, 3), 41, 1, False)
ec47_61 = Curve(47, 3, 5, (1, 3), 61, 1, False)
ec53_47 = Curve(53, 9, 4, (0, 51), 47, 1, False)
ec53_61 = Curve(53, 1, 8, (1, 13), 61, 1, False)
ec59_53 = Curve(59, 9, 3, (0, 48), 53, 1, False)
ec59_73 = Curve(59, 3, 3, (0, 48), 73, 1, False)
ec61_59 = Curve(61, 2, 5, (0, 35), 59, 1, False)
ec61_73 = Curve(61, 1, 9, (0, 58), 73, 1, False)
ec67_61 = Curve(67, 3, 8, (2, 25), 61, 1, False)