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 tree_helper(script_tree) -> Tuple[Any, bytes]: if len(script_tree) == 1: leaf_version, script = script_tree[0] leaf_version = leaf_version & 0xFE preimage = leaf_version.to_bytes(1, "big") preimage += var_bytes.serialize(serialize(script)) h = tagged_hash(b"TapLeaf", preimage) return ([((leaf_version, script), bytes())], h) left, left_h = tree_helper(script_tree[0]) right, right_h = tree_helper(script_tree[1]) info = [(leaf, c + right_h) for leaf, c in left] info += [(leaf, c + left_h) for leaf, c in right] if right_h < left_h: left_h, right_h = right_h, left_h return (info, tagged_hash(b"TapBranch", left_h + right_h))
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 = tagged_hash(b"TapTweak", P[0].to_bytes(32, "big")) 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 _det_nonce_( msg_hash: bytes, q: int, Q: int, aux: bytes, ec: Curve, hf: HashF ) -> int: # assume the random oracle model for the hash function, # i.e. hash values can be considered uniformly random # Note that in general, taking a uniformly random integer # modulo the curve order n would produce a biased result. # However, if the order n is sufficiently close to 2^hf_len, # then the bias is not observable: # e.g. for secp256k1 and sha256 1-n/2^256 it is about 1.27*2^-128 # # the unbiased implementation is provided here, # which works also for very-low-cardinality test curves randomizer = tagged_hash("BIP0340/aux".encode(), aux, hf) xor = q ^ int.from_bytes(randomizer, "big", signed=False) max_len = max(ec.n_size, hf().digest_size) t = b"".join( [ xor.to_bytes(max_len, byteorder="big", signed=False), Q.to_bytes(ec.p_size, byteorder="big", signed=False), msg_hash, ] ) nonce_tag = "BIP0340/nonce".encode() while True: t = tagged_hash(nonce_tag, t, hf) # The following lines would introduce a bias # nonce = int.from_bytes(t, 'big') % ec.n # nonce = int_from_bits(t, ec.nlen) % ec.n # In general, taking a uniformly random integer (like those # obtained from a hash function in the random oracle model) # modulo the curve order n would produce a biased result. # However, if the order n is sufficiently close to 2^hf_len, # then the bias is not observable: e.g. # for secp256k1 and sha256 1-n/2^256 it is about 1.27*2^-128 nonce = int_from_bits(t, ec.nlen) # candidate nonce if 0 < nonce < ec.n: # acceptable value for nonce return nonce # successful candidate
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)
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
def challenge_(msg_hash: Octets, x_Q: int, x_K: int, ec: Curve, hf: HashF) -> int: # the message msg_hash: a hf_len array hf_len = hf().digest_size msg_hash = bytes_from_octets(msg_hash, hf_len) t = b"".join([ x_K.to_bytes(ec.p_size, byteorder="big", signed=False), x_Q.to_bytes(ec.p_size, byteorder="big", signed=False), msg_hash, ]) t = tagged_hash("BIP0340/challenge".encode(), t, hf) c = int_from_bits(t, ec.nlen) % ec.n if c == 0: raise BTClibRuntimeError("invalid zero challenge") # pragma: no cover return c
def test_tagged_hash() -> None: s = "deadbeef" b = s.encode() assert tagged_hash("btclib", s) == tagged_hash("btclib", b)
def taproot( transaction: Tx, input_index: int, amounts: List[int], scriptpubkeys: List[ScriptPubKey], hashtype: int, ext_flag: int, annex: bytes, message_extension: bytes, ) -> bytes: if hashtype not in SIG_HASH_TYPES: raise BTClibValueError(f"Unknown hash type: {hashtype}") if hashtype & 0x03 == SINGLE and input_index >= len(transaction.vout): raise BTClibValueError("Sighash single wihout a corresponding output") preimage = b"\x00" preimage += hashtype.to_bytes(1, "little") preimage += transaction.nVersion.to_bytes(4, "little") preimage += transaction.nLockTime.to_bytes(4, "little") if hashtype & 0x80 != ANYONECANPAY: sha_prevouts = b"" sha_amounts = b"" sha_scriptpubkeys = b"" sha_sequences = b"" for i, vin in enumerate(transaction.vin): sha_prevouts += vin.prev_out.serialize() sha_amounts += amounts[i].to_bytes(8, "little") sha_scriptpubkeys += var_bytes.serialize(scriptpubkeys[i].script) sha_sequences += vin.nSequence.to_bytes(4, "little") preimage += sha256(sha_prevouts) preimage += sha256(sha_amounts) preimage += sha256(sha_scriptpubkeys) preimage += sha256(sha_sequences) if hashtype & 0x03 not in [NONE, SINGLE]: sha_outputs = b"" for vout in transaction.vout: sha_outputs += vout.serialize() preimage += sha256(sha_outputs) annex_present = int(bool(annex)) preimage += (2 * ext_flag + annex_present).to_bytes(1, "little") if hashtype & 0x80 == ANYONECANPAY: preimage += transaction.vin[input_index].prev_out.serialize() preimage += amounts[input_index].to_bytes(8, "little") preimage += var_bytes.serialize(scriptpubkeys[input_index].script) preimage += transaction.vin[input_index].nSequence.to_bytes( 4, "little") else: preimage += input_index.to_bytes(4, "little") if annex_present: sha_annex = var_bytes.serialize(annex) preimage += sha256(sha_annex) if hashtype & 0x03 == SINGLE: preimage += sha256(transaction.vout[input_index].serialize()) preimage += message_extension sig_hash = tagged_hash(b"TapSighash", preimage) return sig_hash