def _prv_keyinfo_from_xprv(xprv: BIP32Key, network: Optional[str] = None, compressed: Optional[bool] = None) -> PrvkeyInfo: """Return prv_key tuple (int, compressed, network) from BIP32 xprv. BIP32Key is always compressed and includes network information: here the 'network, compressed' input parameters are passed only to allow consistency checks. """ compressed = True if compressed is None else compressed if not compressed: raise BTClibValueError("uncompressed SEC / compressed BIP32 mismatch") if isinstance(xprv, BIP32KeyData): xprv.assert_valid() else: xprv = BIP32KeyData.b58decode(xprv) if xprv.key[0] != 0: err_msg = f"not a private key: {xprv.b58encode()}" raise BTClibValueError(err_msg) if network is None: network = network_from_xkeyversion(xprv.version) allowed_versions = xprvversions_from_network(network) if xprv.version not in allowed_versions: err_msg = f"not a {network} key: " err_msg += f"{xprv.b58encode()}" raise BTClibValueError(err_msg) q = int.from_bytes(xprv.key[1:], byteorder="big") return q, network, True
def mult_sliding_window(m: int, Q: JacPoint, ec: CurveGroup, w: int = 4) -> JacPoint: """Scalar multiplication using "sliding window". It has the benefit that the pre-computation stage is roughly half as complex as the normal windowed method. It is not constant time. For 256-bit scalars 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 BTClibValueError(f"negative m: {hex(m)}") # a number cannot be written in basis 1 (ie w=0) if w <= 0: raise BTClibValueError(f"non positive w: {w}") k = w - 1 p = pow(2, k) # at each step one of the points in T will be added P = Q for _ in range(k): P = ec.double_jac(P) T = [P] for i in range(1, p): T.append(ec.add_jac(T[i - 1], Q)) digits = convert_number_to_base(m, 2) R = INFJ i = 0 while i < len(digits): if digits[i] == 0: R = ec.double_jac(R) i += 1 else: j = len(digits) - i if (len(digits) - i) < w else w t = digits[i] for a in range(1, j): t = 2 * t + digits[i + a] if j < w: for b in range(i, (i + j)): R = ec.double_jac(R) if digits[b] == 1: R = ec.add_jac(R, Q) return R for _ in range(w): R = ec.double_jac(R) R = ec.add_jac(R, T[t - p]) i += j return R
def prv_keyinfo_from_prv_key(prv_key: PrvKey, network: Optional[str] = None, compressed: Optional[bool] = None) -> PrvkeyInfo: compr = True if compressed is None else compressed net = "mainnet" if network is None else network ec = NETWORKS[net].curve if isinstance(prv_key, int): q = prv_key elif isinstance(prv_key, BIP32KeyData): return _prv_keyinfo_from_xprv(prv_key, network, compressed) else: try: return _prv_keyinfo_from_xprvwif(prv_key, network, compressed) # FIXME: except the NotPrvKeyError only, let InvalidPrvKey go through except ValueError: pass # it must be octets try: prv_key = bytes_from_octets(prv_key, ec.n_size) q = int.from_bytes(prv_key, byteorder="big", signed=False) except ValueError as e: raise BTClibValueError(f"not a private key: {prv_key!r}") from e if not 0 < q < ec.n: raise BTClibValueError(f"private key not in 1..n-1: {hex(q).upper()}") return q, net, compr
def assert_signable(self) -> None: self.assert_valid() for i, tx_in in enumerate(self.tx.vin): non_witness_utxo = self.inputs[i].non_witness_utxo witness_utxo = self.inputs[i].witness_utxo redeem_script = self.inputs[i].redeem_script if witness_utxo: script_pub_key = witness_utxo.script_pub_key script_type, payload = type_and_payload(script_pub_key.script) if script_type == "p2sh": script_type, _ = type_and_payload(redeem_script) if script_type not in ("p2wpkh", "p2wsh"): raise BTClibValueError( "script type not it ('p2wpkh', 'p2wsh')") elif non_witness_utxo: script_pub_key = non_witness_utxo.vout[ tx_in.prev_out.vout].script_pub_key _, payload = type_and_payload(script_pub_key.script) else: err_msg = "missing script_pub_key" raise BTClibValueError(err_msg) if redeem_script and payload != hash160(redeem_script): raise BTClibValueError("invalid redeem script hash160") if self.inputs[i].witness_script: if redeem_script: _, payload = type_and_payload(redeem_script) if payload != sha256(self.inputs[i].witness_script): raise BTClibValueError("invalid witness script sha256")
def parse(cls: Type["Sig"], data: BinaryData, check_validity: bool = True) -> "Sig": """Return a Sig by parsing binary data. Deserialize a strict ASN.1 DER representation of an ECDSA signature. """ stream = bytesio_from_binarydata(data) ec = secp256k1 # [0x30] [data-size][0x02][r-size][r][0x02][s-size][s] marker = stream.read(1) if marker != _DER_SIG_MARKER: err_msg = f"invalid compound header: {marker.hex()}" err_msg += f", instead of DER sequence tag {_DER_SIG_MARKER.hex()}" raise BTClibValueError(err_msg) # [data-size][0x02][r-size][r][0x02][s-size][s] sig_data = var_bytes.parse(stream, forbid_zero_size=True) # [0x02][r-size][r][0x02][s-size][s] sig_data_substream = bytesio_from_binarydata(sig_data) r = _deserialize_scalar(sig_data_substream) s = _deserialize_scalar(sig_data_substream) # to prevent malleability # the sig_data_substream must have been consumed entirely if sig_data_substream.read(1) != b"": err_msg = "invalid DER sequence length" raise BTClibValueError(err_msg) return cls(r, s, ec, check_validity)
def crack_prv_key_( msg_hash1: Octets, sig1: Union[Sig, Octets], msg_hash2: Octets, sig2: Union[Sig, Octets], hf: HashF = sha256, ) -> Tuple[int, int]: if isinstance(sig1, Sig): sig1.assert_valid() else: sig1 = Sig.parse(sig1) if isinstance(sig2, Sig): sig2.assert_valid() else: sig2 = Sig.parse(sig2) ec = sig2.ec if sig1.ec != ec: raise BTClibValueError("not the same curve in signatures") if sig1.r != sig2.r: raise BTClibValueError("not the same r in signatures") if sig1.s == sig2.s: raise BTClibValueError("identical signatures") c_1 = challenge_(msg_hash1, ec, hf) c_2 = challenge_(msg_hash2, ec, hf) nonce = (c_1 - c_2) * mod_inv(sig1.s - sig2.s, ec.n) % ec.n q = (sig2.s * nonce - c_2) * mod_inv(sig1.r, ec.n) % ec.n return q, nonce
def b58decode(v: String, out_size: Optional[int] = None) -> bytes: """Decode a Base58Check encoded bytes-like object or ASCII string. Optionally, it also ensures required output size. """ if isinstance(v, str): # do not trim spaces v = v.encode("ascii") result = _b58decode(v) if len(result) < 4: err_msg = "not enough bytes for checksum, " err_msg += f"invalid base58 decoded size: {len(result)}" raise BTClibValueError(err_msg) result, checksum = result[:-4], result[-4:] h256 = hash256(result) if checksum != h256[:4]: err_msg = f"invalid checksum: 0x{checksum.hex()} instead of 0x{h256[:4].hex()}" raise BTClibValueError(err_msg) if out_size is None or len(result) == out_size: return result err_msg = "valid checksum, invalid decoded size: " err_msg += f"{len(result)} bytes instead of {out_size}" raise BTClibValueError(err_msg)
def load_lang(self, lang: str, filename: Optional[str] = None) -> None: """Load/add a language word-list if not loaded/added yet. The language file has to be provided for adding new languages beyond those already provided. """ # a new language, unknown before if lang not in self.languages: if filename is None: raise BTClibValueError(f"Missing file for language '{lang}'") # initialize the new language self.languages.append(lang) self.language_files[lang] = filename self._wordlist[lang] = [] self._bits_per_word[lang] = 0 self._language_length[lang] = 0 # language has not been loaded yet if self._language_length[lang] == 0: with open(self.language_files[lang], "r", encoding="ascii") as file_: lines = file_.readlines() nwords = len(lines) # http://www.graphics.stanford.edu/~seander/bithacks.html if nwords & (nwords - 1) != 0: err_msg = f"invalid wordlist length: {nwords}, not a power of two" raise BTClibValueError(err_msg) self._language_length[lang] = nwords # clean up and normalization are missing, but removal of \n self._wordlist[lang] = [line[:-1] for line in lines]
def _rootxprv_from_seed( seed: Octets, version: Octets = NETWORKS["mainnet"].bip32_prv ) -> BIP32KeyData: """Return BIP32 root master extended private key from seed.""" seed = bytes_from_octets(seed) bitlenght = len(seed) * 8 if bitlenght < 128: raise BTClibValueError( f"too few bits for seed: {bitlenght} in '{hex_string(seed)}'" ) if bitlenght > 512: raise BTClibValueError( f"too many bits for seed: {bitlenght} in '{hex_string(seed)}'" ) hmac_ = hmac.new(b"Bitcoin seed", seed, "sha512").digest() k = b"\x00" + hmac_[:32] v = bytes_from_octets(version, 4) return BIP32KeyData( version=v, depth=0, parent_fingerprint=b"\x00" * 4, index=0, chain_code=hmac_[32:], key=k, )
def assert_valid(self) -> None: # must be a 4-bytes _signed_ integer if not 0 < self.version <= 0x7FFFFFFF: raise BTClibValueError(f"invalid version: {hex(self.version)}") if self.time.timestamp() < 1231006505: err_msg = "invalid timestamp (before genesis)" date = datetime.fromtimestamp(self.time.timestamp(), timezone.utc) err_msg += f": {date}" raise BTClibValueError(err_msg) # TODO: check for max 4-bytes timestamp for key, size in _KEY_SIZE: value = bytes(getattr(self, key)) if len(value) != size: err_msg = f"invalid {key} length: " err_msg += f"{len(value)} bytes" err_msg += f" instead of {size}" raise BTClibValueError(err_msg) self.nonce = int(self.nonce) if not 0 < self.nonce <= 0xFFFFFFFF: raise BTClibValueError(f"invalid nonce: {hex(self.nonce)}") self.assert_valid_pow()
def _pub_keyinfo_from_xpub(xpub: BIP32Key, network: Optional[str] = None, compressed: Optional[bool] = None) -> PubkeyInfo: """Return the pub_key tuple (SEC-bytes, network) from a BIP32 xpub. BIP32Key is always compressed and includes network information: here the 'network, compressed' input parameters are passed only to allow consistency checks. """ compressed = True if compressed is None else compressed if not compressed: raise BTClibValueError("Uncompressed SEC / compressed BIP32 mismatch") if isinstance(xpub, BIP32KeyData): xpub.assert_valid() else: xpub = BIP32KeyData.b58decode(xpub) if xpub.key[0] not in (2, 3): err_msg = f"not a public key: {xpub.b58encode()}" raise BTClibValueError(err_msg) if network is None: return xpub.key, network_from_xkeyversion(xpub.version) allowed_versions = xpubversions_from_network(network) if xpub.version not in allowed_versions: err_msg = f"Not a {network} key: " err_msg += f"{xpub.b58encode()}" raise BTClibValueError(err_msg) return xpub.key, network
def witness_from_address(b32addr: String) -> Tuple[int, bytes, str]: "Return the witness from a bech32 native SegWit address." if isinstance(b32addr, str): b32addr = b32addr.strip() # the following check was originally in b32decode # but it does not pertain there if len(b32addr) > 90: raise BTClibValueError( f"invalid bech32 address length: {len(b32addr)} > 90") hrp, data = b32decode(b32addr) # check that it is a known SegWit address type network = network_from_key_value("hrp", hrp) if network is None: raise BTClibValueError(f"invalid hrp: {hrp}") if len(data) == 0: raise BTClibValueError(f"empty data in bech32 address: {b32addr!r}") wit_ver = data[0] wit_prg = bytes(power_of_2_base_conversion(data[1:], 5, 8, False)) return wit_ver, check_witness(wit_ver, wit_prg), network
def address_from_xpub(xpub: BIP32Key) -> str: """Return the SLIP132 base58/bech32 address. The address is always derived from the compressed public key, as this is the default public key representation in BIP32. """ if not isinstance(xpub, BIP32KeyData): xpub = BIP32KeyData.b58decode(xpub) if xpub.key[0] not in (2, 3): err_msg = f"not a public key: {xpub.b58encode()}" raise BTClibValueError(err_msg) function_list: List[Callable[[Any, str], str]] = [ b58.p2pkh, b32.p2wpkh, b58.p2wpkh_p2sh, ] version_list: List[str] = [ "bip32_pub", "slip132_p2wpkh_pub", "slip132_p2wpkh_p2sh_pub", ] for version, function in zip(version_list, function_list): # with pytohn>=3.8 use walrus operator # if network := network_from_key_value(version, xpub.version): network = network_from_key_value(version, xpub.version) if network: return function(xpub, network) err_msg = f"unknown xpub version: {xpub.version.hex()}" # pragma: no cover raise BTClibValueError(err_msg) # pragma: no cover
def power_of_2_base_conversion(data: Iterable[int], from_bits: int, to_bits: int, pad: bool = True) -> List[int]: "Convert a power-of-two digit sequence to another power-of-two base." acc = 0 bits = 0 ret = [] maxv = (1 << to_bits) - 1 max_acc = (1 << (from_bits + to_bits - 1)) - 1 for value in data: if value < 0 or (value >> from_bits): raise BTClibValueError(f"invalid value: {value}") acc = ((acc << from_bits) | value) & max_acc bits += from_bits while bits >= to_bits: bits -= to_bits ret.append((acc >> bits) & maxv) if pad: if bits: ret.append((acc << (to_bits - bits)) & maxv) elif bits >= from_bits: err_msg = f"zero padding of more than {from_bits-1} bits" err_msg += f" in {from_bits}-to-{to_bits} conversion" raise BTClibValueError(err_msg) elif (acc << (to_bits - bits)) & maxv: err_msg = f"non-zero padding in {from_bits}-to-{to_bits} conversion" raise BTClibValueError(err_msg) return ret
def assert_valid(self) -> None: # r is a scalar, fail if r is not in [1, n-1] if not 0 < self.r < self.ec.n: err_msg = "scalar r not in 1..n-1: " err_msg += f"'{hex_string(self.r)}'" if self.r > 0xFFFFFFFF else f"{self.r}" raise BTClibValueError(err_msg) # ensure r is congruent to a valid x-coordinate r = self.r congruence_not_found = True while congruence_not_found and r < self.ec.p: try: self.ec.y(r) congruence_not_found = False except BTClibValueError: r += self.ec.n if congruence_not_found: err_msg = "r is not (congruent to) a valid x-coordinate: " err_msg += f"'{hex_string(self.r)}'" if self.r > 0xFFFFFFFF else f"{self.r}" raise BTClibValueError(err_msg) # s is a scalar, fail if s is not in [1, n-1] if not 0 < self.s < self.ec.n: err_msg = "scalar s not in 1..n-1: " err_msg += f"'{hex_string(self.s)}'" if self.s > 0xFFFFFFFF else f"{self.s}" raise BTClibValueError(err_msg)
def check_output_pubkey(q: Octets, script: Octets, control: Octets, ec: Curve = secp256k1) -> bool: q = bytes_from_octets(q) script = bytes_from_octets(script) control = bytes_from_octets(control) if len(control) > 4129: # 33 + 32 * 128 raise BTClibValueError("Control block too long") m = (len(control) - 33) // 32 if len(control) != 33 + 32 * m: raise BTClibValueError("Invalid control block length") leaf_version = control[0] & 0xFE preimage = leaf_version.to_bytes(1, "big") + var_bytes.serialize(script) k = tagged_hash(b"TapLeaf", preimage) for j in range(m): e = control[33 + 32 * j:65 + 32 * j] if k < e: k = tagged_hash(b"TapBranch", k + e) else: k = tagged_hash(b"TapBranch", e + k) p_bytes = control[1:33] t_bytes = tagged_hash(b"TapTweak", p_bytes + k) p = int.from_bytes(p_bytes, "big") t = int.from_bytes(t_bytes, "big") # edge case that cannot be reproduced in the test suite if t >= ec.n: raise BTClibValueError("Invalid script tree hash") # pragma: no cover P = (p, secp256k1.y_even(p)) Q = secp256k1.add(P, mult(t)) return Q[0] == int.from_bytes(q, "big") and control[0] & 1 == Q[1] % 2
def _assert_valid_version(version: int) -> None: # must be a 4-bytes int if not 0 <= version <= 0xFFFFFFFF: raise BTClibValueError(f"invalid version: {version}") # actually the only version that is currently handled is zero if version != 0: raise BTClibValueError(f"invalid non-zero version: {version}")
def assert_valid(self) -> None: if len(self.master_fingerprint) != 4: err_msg = "invalid master fingerprint length: " err_msg += f"{len(self.master_fingerprint)}" raise BTClibValueError(err_msg) if len(self) > 255: raise BTClibValueError(f"invalid der_path size: {len(self.der_path)}") if any(not 0 <= i <= 0xFFFFFFFF for i in self.der_path): raise BTClibValueError("invalid der_path element")
def str_from_index_int(i: int, hardening: str = _HARDENING) -> str: if hardening not in ("'", "h", "H"): raise BTClibValueError(f"invalid hardening symbol: {hardening}") if not 0 <= i <= 0xFFFFFFFF: raise BTClibValueError(f"invalid index: {i}") if i < 0x80000000: return str(i) return str(i - 0x80000000) + hardening
def is_on_curve(self, Q: Point) -> bool: """Return True if the point is on the curve.""" if len(Q) != 2: raise BTClibValueError("point must be a tuple[int, int]") if Q[1] == 0: # Infinity point in affine coordinates return True if not 0 < Q[1] < self.p: # y cannot be zero raise BTClibValueError( f"y-coordinate not in 1..p-1: '{hex_string(Q[1])}'") return self._y2(Q[0]) == (Q[1] * Q[1] % self.p)
def assert_valid(self) -> None: if len(self.tx_id) != 32: err_msg = f"invalid OutPoint tx_id: {len(self.tx_id)}" err_msg += " instead of 32 bytes" raise BTClibValueError(err_msg) # must be a 4-bytes int if not 0 <= self.vout <= 0xFFFFFFFF: raise BTClibValueError(f"invalid vout: {self.vout}") # not a coinbase, not a regular OutPoint if (self.tx_id == b"\x00" * 32) ^ (self.vout == 0xFFFFFFFF): raise BTClibValueError("invalid OutPoint")
def deserialize_tx( k: bytes, v: bytes, type_: str, include_witness: Optional[bool] = True ) -> Tx: "Return the dataclass element from its binary representation." if len(k) != 1: err_msg = f"invalid {type_} key length: {len(k)}" raise BTClibValueError(err_msg) tx = Tx.parse(v) if not include_witness and tx.serialize(include_witness=False) != v: raise BTClibValueError("wrong tx serialization format") return tx
def parse(cls: Type["Psbt"], psbt_bin: Octets, check_validity: bool = True) -> "Psbt": "Return a Psbt by parsing binary data." # FIXME: psbt_bin should be BinaryData # stream = bytesio_from_binarydata(psbt_bin) # and the deserialization should happen reading the stream # not slicing bytes tx = Tx(check_validity=False) version = 0 hd_key_paths: Dict[Octets, BIP32KeyOrigin] = {} unknown: Dict[Octets, Octets] = {} # psbt_bin = bytes_from_octets(psbt_bin) stream = bytesio_from_binarydata(psbt_bin) if stream.read(4) != PSBT_MAGIC_BYTES: raise BTClibValueError("malformed psbt: missing magic bytes") if stream.read(1) != PSBT_SEPARATOR: raise BTClibValueError("malformed psbt: missing separator") global_map, stream = deserialize_map(stream) for k, v in global_map.items(): if k[:1] == PSBT_GLOBAL_UNSIGNED_TX: tx = deserialize_tx(k, v, "global unsigned tx", False) elif k[:1] == PSBT_GLOBAL_VERSION: version = deserialize_int(k, v, "global version") elif k[:1] == PSBT_GLOBAL_XPUB: hd_key_paths[k[1:]] = BIP32KeyOrigin.parse(v) else: # unknown unknown[k] = v inputs: List[PsbtIn] = [] for _ in tx.vin: input_map, stream = deserialize_map(stream) inputs.append(PsbtIn.parse(input_map)) outputs: List[PsbtOut] = [] for _ in tx.vout: output_map, stream = deserialize_map(stream) outputs.append(PsbtOut.parse(output_map)) return cls( tx, inputs, outputs, version, hd_key_paths, unknown, check_validity, )
def assert_p2wsh(script_pub_key: Octets) -> None: script_pub_key = bytes_from_octets(script_pub_key, 34) # p2wsh [OP_0, redeem_script hash] # 0x0020{32-byte redeem_script hash} if script_pub_key[0] != 0: err_msg = f"invalid witness version: {script_pub_key[0]}" err_msg += f" instead of {0}" raise BTClibValueError(err_msg) if script_pub_key[1] != 0x20: err_msg = f"invalid redeem script hash length marker: {script_pub_key[1]}" err_msg += f" instead of {0x20}" raise BTClibValueError(err_msg)
def assert_p2wpkh(script_pub_key: Octets) -> None: script_pub_key = bytes_from_octets(script_pub_key, 22) # p2wpkh [OP_0, pub_key hash] # 0x0014{20-byte pub_key hash} if script_pub_key[0] != 0: err_msg = f"invalid witness version: {script_pub_key[0]}" err_msg += f" instead of {0}" raise BTClibValueError(err_msg) if script_pub_key[1] != 0x14: err_msg = f"invalid pub_key hash length marker: {script_pub_key[1]}" err_msg += f" instead of {0x14}" raise BTClibValueError(err_msg)
def assert_p2sh(script_pub_key: Octets) -> None: script_pub_key = bytes_from_octets(script_pub_key, 23) # p2sh [OP_HASH160, redeem_script hash, OP_EQUAL] # 0xA914{20-byte redeem_script hash}87 if script_pub_key[-1] != 0x87: raise BTClibValueError("missing final OP_EQUAL") if script_pub_key[0] != 0xA9: raise BTClibValueError("missing leading OP_HASH160") if script_pub_key[1] != 0x14: err_msg = f"invalid redeem script hash length marker: {script_pub_key[1]}" err_msg += f" instead of {0x14}" raise BTClibValueError(err_msg)
def assert_p2pkh(script_pub_key: Octets) -> None: script_pub_key = bytes_from_octets(script_pub_key, 25) # p2pkh [OP_DUP, OP_HASH160, pub_key hash, OP_EQUALVERIFY, OP_CHECKSIG] # 0x76A914{20-byte pub_key_hash}88AC if script_pub_key[-2:] != b"\x88\xac": raise BTClibValueError("missing final OP_EQUALVERIFY, OP_CHECKSIG") if script_pub_key[:2] != b"\x76\xa9": raise BTClibValueError("missing leading OP_DUP, OP_HASH160") if script_pub_key[2] != 0x14: err_msg = f"invalid pub_key hash length marker: {script_pub_key[2]}" err_msg += f" instead of {0x14}" raise BTClibValueError(err_msg)
def __init__(self, p: Integer, a: Integer, b: Integer, G: Point) -> None: super().__init__(p, a, b) # 2. check that xG and yG are integers in the interval [0, p−1] # 4. Check that yG^2 = xG^3 + a*xG + b (mod p) if len(G) != 2: raise BTClibValueError("Generator must a be a sequence[int, int]") self.G = (int_from_integer(G[0]), int_from_integer(G[1])) if not self.is_on_curve(self.G): raise BTClibValueError("Generator is not on the curve") self.GJ = self.G[0], self.G[1], 1 # Jacobian coordinates
def mult_w_NAF(m: int, Q: JacPoint, ec: CurveGroup, w: int = 4) -> JacPoint: """Scalar multiplication in Jacobian coordinates using wNAF. This implementation uses the same method called "w-ary non-adjacent form" (wNAF) we make use of the fact that point subtraction is as easy as point addition to perform fewer operations compared to sliding-window In fact, on Weierstrass curves, known P, -P can be computed on the fly. 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). 'right-to-left' method. FIXME: - Make it constant time (if necessary) - Try to avoid exception in negation for w=1 """ if m < 0: raise BTClibValueError(f"negative m: {hex(m)}") # a number cannot be written in basis 1 (ie w=0) if w <= 0: raise BTClibValueError(f"non positive w: {w}") M = wNAF_of_m(m, w) p = len(M) b = pow(2, w) Q2 = ec.double_jac(Q) T = [Q] for i in range(1, (b // 4)): T.append(ec.add_jac(T[i - 1], Q2)) for i in range((b // 4), (b // 2)): T.append(ec.negate_jac(T[i - (b // 4)])) R = INFJ for j in range(p - 1, -1, -1): R = ec.double_jac(R) if M[j] != 0: if M[j] > 0: # It adds the element jQ R = ec.add_jac(R, T[(M[j] - 1) // 2]) else: # In this case it adds the opposite, ie -jQ if w != 1: R = ec.add_jac(R, T[(b // 4) - ((M[j] + 1) // 2)]) else: # Case w=1 must be studied on its own for now R = R = ec.add_jac(R, T[1]) return R
def from_tx(prevouts: List[TxOut], tx: Tx, vin_i: int, hash_type: int) -> bytes: script = prevouts[vin_i].script_pub_key.script if is_p2tr(script): annex = b"" witness = tx.vin[vin_i].script_witness if len(witness.stack) >= 2 and witness.stack[-1][0] == 0x50: annex = witness.stack[-1] witness.stack = witness.stack[:-1] if len(witness.stack) == 0: raise BTClibValueError("Empty stack") ext = b"" if len(witness.stack) > 1: leaf_version = witness.stack[-1][0] & 0xFE preimage = leaf_version.to_bytes(1, "big") preimage += var_bytes.serialize(witness.stack[-2]) tapleaf_hash = tagged_hash(b"TapLeaf", preimage) ext = tapleaf_hash + b"\x00\xff\xff\xff\xff" return taproot( tx, vin_i, [x.value for x in prevouts], [x.script_pub_key for x in prevouts], hash_type, int(bool(ext)), annex, ext, ) # handle all p2sh-wrapped scripts if is_p2sh(script): script = tx.vin[vin_i].script_sig if is_p2wpkh(script): script_ = witness_v0_script(script)[0] return segwit_v0(script_, tx, vin_i, hash_type, prevouts[vin_i].value) if is_p2wsh(script): # the real script is contained in the witness script_ = witness_v0_script(tx.vin[vin_i].script_witness.stack[-1])[0] return segwit_v0(script_, tx, vin_i, hash_type, prevouts[vin_i].value) if is_p2tr(script): raise BTClibValueError("Taproot scripts cannot be wrapped in p2sh") script_ = legacy_script(script)[0] return legacy(script_, tx, vin_i, hash_type)