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")
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
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)
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
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}")
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
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
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