Beispiel #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")
Beispiel #2
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
Beispiel #3
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)
Beispiel #4
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
Beispiel #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}")
Beispiel #6
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
Beispiel #7
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
Beispiel #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