• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

btclib-org / btclib / 7253730440

18 Dec 2023 08:59PM UTC coverage: 99.894%. First build
7253730440

Pull #119

github

web-flow
Merge e0eb44a07 into da0cfc146
Pull Request #119: [pre-commit.ci] pre-commit autoupdate

12250 of 12263 relevant lines covered (99.89%)

17.97 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.08
/btclib/script/engine/tapscript.py
1
#!/usr/bin/env python3
2

3
# Copyright (C) 2017-2021 The btclib developers
4
#
5
# This file is part of btclib. It is subject to the license terms in the
6
# LICENSE file found in the top-level directory of this distribution.
7
#
8
# No part of btclib including this file, may be copied, modified, propagated,
9
# or distributed except according to the terms contained in the LICENSE file.
10
"""Bitcoin Script engine."""
18✔
11

12
from __future__ import annotations
18✔
13

14
from typing import Callable, Mapping
18✔
15

16
try:
18✔
17
    from btclib_libsecp256k1.ssa import verify as ssa_verify
18✔
18
except ImportError:
×
19
    from btclib.ecc.ssa import verify_ as ssa_verify  # type: ignore
×
20

21
from btclib import var_bytes
18✔
22
from btclib.alias import ScriptList
18✔
23
from btclib.exceptions import BTClibValueError
18✔
24
from btclib.hashes import tagged_hash
18✔
25
from btclib.script import sig_hash
18✔
26
from btclib.script.engine import script_op_codes
18✔
27
from btclib.script.engine.script import check_balanced_if
18✔
28
from btclib.script.engine.script_op_codes import _from_num
18✔
29
from btclib.script.op_codes_tapscript import OP_CODE_NAMES
18✔
30
from btclib.script.script_pub_key import type_and_payload
18✔
31
from btclib.script.taproot import parse
18✔
32
from btclib.script.taproot import serialize as serialize_script
18✔
33
from btclib.tx.tx import Tx
18✔
34
from btclib.tx.tx_out import TxOut
18✔
35
from btclib.utils import bytesio_from_binarydata
18✔
36

37

38
def get_hashtype(signature: bytes) -> int:
18✔
39
    sighash_type = 0  # all
18✔
40
    if len(signature) == 65:
18✔
41
        sighash_type = signature[-1]
18✔
42
        if sighash_type == 0:
18✔
43
            raise BTClibValueError()
18✔
44
    return sighash_type
18✔
45

46

47
def op_checksigadd(
18✔
48
    stack: list[bytes], altstack: list[bytes], flags: list[str]
49
) -> ScriptList:
50
    stack[-2], stack[-3] = stack[-3], stack[-2]
18✔
51
    return ["OP_CHECKSIG", "OP_ADD"]
18✔
52

53

54
def verify_key_path(
18✔
55
    script_pub_key: bytes,
56
    stack: list[bytes],
57
    prevouts: list[TxOut],
58
    tx: Tx,
59
    i: int,
60
    annex: bytes,
61
) -> None:
62
    sighash_type = get_hashtype(stack[0])
18✔
63
    signature = stack[0][:64]
18✔
64
    pub_key = type_and_payload(script_pub_key)[1]
18✔
65
    msg_hash = sig_hash.taproot(tx, i, prevouts, sighash_type, 0, annex, b"")
18✔
66

67
    if not ssa_verify(msg_hash, pub_key, signature[:64]):
18✔
68
        raise BTClibValueError()
18✔
69

70

71
def op_checksig(
18✔
72
    stack: list[bytes],
73
    script_bytes: bytes,
74
    codesep_pos: int,
75
    tx: Tx,
76
    i: int,
77
    prevouts: list[TxOut],
78
    annex: bytes,
79
    budget: int,
80
) -> int:
81
    pub_key = stack.pop()
18✔
82
    signature = stack.pop()
18✔
83
    if len(pub_key) == 0:
18✔
84
        raise BTClibValueError()
18✔
85
    if signature:
18✔
86
        budget -= 50
18✔
87
        if budget < 0:
18✔
88
            raise BTClibValueError()
18✔
89
    if len(pub_key) == 32 and signature:
18✔
90
        sighash_type = get_hashtype(signature)
18✔
91
        preimage = b"\xc0"
18✔
92
        preimage += var_bytes.serialize(script_bytes)
18✔
93
        tapleaf_hash = tagged_hash(b"TapLeaf", preimage)
18✔
94
        ext = tapleaf_hash + b"\x00" + codesep_pos.to_bytes(4, "little")
18✔
95
        msg_hash = sig_hash.taproot(tx, i, prevouts, sighash_type, 1, annex, ext)
18✔
96
        if not ssa_verify(msg_hash, pub_key, signature[:64]):
18✔
97
            raise BTClibValueError()
18✔
98
    stack.append(_from_num(int(bool(signature))))
18✔
99
    return budget
18✔
100

101

102
def verify_script_path_vc0(
18✔
103
    script_bytes: bytes,
104
    stack: list[bytes],
105
    prevouts: list[TxOut],
106
    tx: Tx,
107
    i: int,
108
    annex: bytes,
109
    sigops_budget: int,
110
    flags: list[str],
111
) -> None:
112
    if any(len(x) > 520 for x in stack):
18✔
113
        raise BTClibValueError()
18✔
114

115
    script = parse(script_bytes, exit_on_op_success=True)
18✔
116

117
    check_balanced_if(script)
18✔
118

119
    if script == ["OP_SUCCESS"]:
18✔
120
        return
18✔
121

122
    codesep_pos = 0xFFFFFFFF
18✔
123

124
    operations: Mapping[str, Callable] = {
18✔
125
        "OP_DUP": script_op_codes.op_dup,
126
        "OP_2DUP": script_op_codes.op_2dup,
127
        "OP_DROP": script_op_codes.op_drop,
128
        "OP_2DROP": script_op_codes.op_2drop,
129
        "OP_SWAP": script_op_codes.op_swap,
130
        "OP_IF": script_op_codes.op_if,
131
        "OP_NOTIF": script_op_codes.op_notif,
132
        "OP_1NEGATE": script_op_codes.op_1negate,
133
        "OP_VERIFY": script_op_codes.op_verify,
134
        "OP_EQUAL": script_op_codes.op_equal,
135
        "OP_CHECKSIGVERIFY": script_op_codes.op_checksigverify,
136
        "OP_CHECKSIGADD": op_checksigadd,
137
        "OP_EQUALVERIFY": script_op_codes.op_equalverify,
138
        "OP_RETURN": script_op_codes.op_return,
139
        "OP_SIZE": script_op_codes.op_size,
140
        "OP_RIPEMD160": script_op_codes.op_ripemd160,
141
        "OP_SHA1": script_op_codes.op_sha1,
142
        "OP_SHA256": script_op_codes.op_sha256,
143
        "OP_HASH160": script_op_codes.op_hash160,
144
        "OP_HASH256": script_op_codes.op_hash256,
145
        "OP_1ADD": script_op_codes.op_1add,
146
        "OP_1SUB": script_op_codes.op_1sub,
147
        "OP_NEGATE": script_op_codes.op_negate,
148
        "OP_ABS": script_op_codes.op_abs,
149
        "OP_NOT": script_op_codes.op_not,
150
        "OP_0NOTEQUAL": script_op_codes.op_0notequal,
151
        "OP_ADD": script_op_codes.op_add,
152
        "OP_SUB": script_op_codes.op_sub,
153
        "OP_BOOLAND": script_op_codes.op_booland,
154
        "OP_BOOLOR": script_op_codes.op_boolor,
155
        "OP_NUMEQUAL": script_op_codes.op_numequal,
156
        "OP_NUMEQUALVERIFY": script_op_codes.op_numequalverify,
157
        "OP_NUMNOTEQUAL": script_op_codes.op_numnotequal,
158
        "OP_LESSTHAN": script_op_codes.op_lessthan,
159
        "OP_GREATERTHAN": script_op_codes.op_greaterthan,
160
        "OP_LESSTHANOREQUAL": script_op_codes.op_lessthanorequal,
161
        "OP_GREATERTHANOREQUAL": script_op_codes.op_greaterthanorequal,
162
        "OP_MIN": script_op_codes.op_min,
163
        "OP_MAX": script_op_codes.op_max,
164
        "OP_WITHIN": script_op_codes.op_within,
165
        "OP_TOALTSTACK": script_op_codes.op_toaltstack,
166
        "OP_FROMALTSTACK": script_op_codes.op_fromaltstack,
167
        "OP_IFDUP": script_op_codes.op_ifdup,
168
        "OP_DEPTH": script_op_codes.op_depth,
169
        "OP_NIP": script_op_codes.op_nip,
170
        "OP_OVER": script_op_codes.op_over,
171
        "OP_PICK": script_op_codes.op_pick,
172
        "OP_ROLL": script_op_codes.op_roll,
173
        "OP_ROT": script_op_codes.op_rot,
174
        "OP_TUCK": script_op_codes.op_tuck,
175
        "OP_3DUP": script_op_codes.op_3dup,
176
        "OP_2OVER": script_op_codes.op_2over,
177
        "OP_2ROT": script_op_codes.op_2rot,
178
        "OP_2SWAP": script_op_codes.op_2swap,
179
    }
180

181
    altstack: list[bytes] = []
18✔
182
    condition_stack: list[bool] = [True]
18✔
183

184
    script_index = -1
18✔
185

186
    op_conditions = [99, 100, 103, 104]  # ["OP_IF", "OP_NOTIF", "OP_ELSE", "OP_ENDIF"]
18✔
187

188
    s = bytesio_from_binarydata(script_bytes)
18✔
189
    while True:
9✔
190
        script_index += 1
18✔
191

192
        skip_execution = not all(condition_stack)
18✔
193

194
        if len(stack) + len(altstack) > 1000:
18✔
195
            raise BTClibValueError()
18✔
196

197
        b = s.read(1)
18✔
198
        if not b:
18✔
199
            break
18✔
200
        t = b[0]
18✔
201
        if 0 < t <= 78:  # pushdata
18✔
202
            if t < 76:
18✔
203
                data_length = t
18✔
204
            else:
205
                data_length = int.from_bytes(s.read(2 ** (t - 76)), byteorder="little")
18✔
206
            a = s.read(data_length)
18✔
207
            if skip_execution:
18✔
208
                continue
18✔
209
            if "MINIMALDATA" in flags:
18✔
210
                if len(a) == 1 and (a[0] == 129 or 0 < a[0] <= 16) or len(a) == 0:
×
211
                    raise BTClibValueError()
×
212
                if serialize_script([a])[0] != t:
×
213
                    raise BTClibValueError()
×
214
            stack.append(a)
18✔
215
            continue
18✔
216
        if skip_execution and t not in op_conditions:
18✔
217
            continue
18✔
218
        op = OP_CODE_NAMES[t]
18✔
219

220
        if op == "OP_CHECKSIG":
18✔
221
            sigops_budget = op_checksig(
18✔
222
                stack,
223
                script_bytes,
224
                codesep_pos,
225
                tx,
226
                i,
227
                prevouts,
228
                annex,
229
                sigops_budget,
230
            )
231

232
        elif op == "OP_CHECKLOCKTIMEVERIFY":
18✔
233
            script_op_codes.op_checklocktimeverify(stack, tx, i, flags)
×
234
        elif op == "OP_CHECKSEQUENCEVERIFY":
18✔
235
            script_op_codes.op_checksequenceverify(stack, tx, i, flags)
×
236
        elif op[3:].isdigit():
18✔
237
            stack.append(_from_num(int(op[3:])))
18✔
238
        elif op == "OP_CODESEPARATOR":
18✔
239
            codesep_pos = script_index
18✔
240
        elif op == "OP_IF":
18✔
241
            script_op_codes.op_if(stack, condition_stack, flags, 1)
18✔
242
        elif op == "OP_NOTIF":
18✔
243
            script_op_codes.op_notif(stack, condition_stack, flags, 1)
18✔
244
        elif op == "OP_ELSE":
18✔
245
            script_op_codes.op_else(condition_stack)
18✔
246
        elif op == "OP_ENDIF":
18✔
247
            script_op_codes.op_endif(condition_stack)
18✔
248
        elif op == "OP_NOP":
18✔
249
            pass
18✔
250
        elif "OP_NOP" in op:
18✔
251
            script_op_codes.op_nop(flags)
×
252
        elif op in operations:
18✔
253
            r = operations[op](stack, altstack, flags)
18✔
254
            if r:
18✔
255
                script_index -= len(r)
18✔
256
                s = bytesio_from_binarydata(serialize_script(r) + s.read())
18✔
257
        else:
258
            raise BTClibValueError()
18✔
259

260
    script_op_codes.op_verify(stack, [], flags)
18✔
261

262
    if stack:
18✔
263
        raise BTClibValueError()
18✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc