File: _script.py

package info (click to toggle)
python-ledger-bitcoin 0.4.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 716 kB
  • sloc: python: 9,357; makefile: 2
file content (154 lines) | stat: -rw-r--r-- 4,265 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
"""
Original version: https://raw.githubusercontent.com/bitcoin-core/HWI/3fe369d0379212fae1c72729a179d133b0adc872/hwilib/_script.py
Distributed under the MIT License.

Bitcoin Script utilities
************************
"""

from typing import (
    Optional,
    Sequence,
    Tuple,
)


def is_opreturn(script: bytes) -> bool:
    """
    Determine whether a script is an OP_RETURN output script.

    :param script: The script
    :returns: Whether the script is an OP_RETURN output script
    """
    return script[0] == 0x6a


def is_p2sh(script: bytes) -> bool:
    """
    Determine whether a script is a P2SH output script.

    :param script: The script
    :returns: Whether the script is a P2SH output script
    """
    return len(script) == 23 and script[0] == 0xa9 and script[1] == 0x14 and script[22] == 0x87


def is_p2pkh(script: bytes) -> bool:
    """
    Determine whether a script is a P2PKH output script.

    :param script: The script
    :returns: Whether the script is a P2PKH output script
    """
    return len(script) == 25 and script[0] == 0x76 and script[1] == 0xa9 and script[2] == 0x14 and script[23] == 0x88 and script[24] == 0xac


def is_p2pk(script: bytes) -> bool:
    """
    Determine whether a script is a P2PK output script.

    :param script: The script
    :returns: Whether the script is a P2PK output script
    """
    return (len(script) == 35 or len(script) == 67) and (script[0] == 0x21 or script[0] == 0x41) and script[-1] == 0xac


def is_p2tr(script: bytes) -> bool:
    """
    Determine whether a script is a P2TR output script.

    :param script: The script
    :returns: Whether the script is a P2TR output script
    """
    return len(script) == 34 and script[0] == 0x51 and script[1] == 0x20


def is_witness(script: bytes) -> Tuple[bool, int, bytes]:
    """
    Determine whether a script is a segwit output script.
    If so, also returns the witness version and witness program.

    :param script: The script
    :returns: A tuple of a bool indicating whether the script is a segwit output script,
        an int representing the witness version,
        and the bytes of the witness program.
    """
    if len(script) < 4 or len(script) > 42:
        return (False, 0, b"")

    if script[0] != 0 and (script[0] < 81 or script[0] > 96):
        return (False, 0, b"")

    if script[1] + 2 == len(script):
        return (True, script[0] - 0x50 if script[0] else 0, script[2:])

    return (False, 0, b"")


def is_p2wpkh(script: bytes) -> bool:
    """
    Determine whether a script is a P2WPKH output script.

    :param script: The script
    :returns: Whether the script is a P2WPKH output script
    """
    is_wit, wit_ver, wit_prog = is_witness(script)
    if not is_wit:
        return False
    elif wit_ver != 0:
        return False
    return len(wit_prog) == 20


def is_p2wsh(script: bytes) -> bool:
    """
    Determine whether a script is a P2WSH output script.

    :param script: The script
    :returns: Whether the script is a P2WSH output script
    """
    is_wit, wit_ver, wit_prog = is_witness(script)
    if not is_wit:
        return False
    elif wit_ver != 0:
        return False
    return len(wit_prog) == 32


# Only handles up to 15 of 15. Returns None if this script is not a
# multisig script. Returns (m, pubkeys) otherwise.
def parse_multisig(script: bytes) -> Optional[Tuple[int, Sequence[bytes]]]:
    """
    Determine whether a script is a multisig script. If so, determine the parameters of that multisig.

    :param script: The script
    :returns: ``None`` if the script is not multisig.
        If multisig, returns a tuple of the number of signers required,
        and a sequence of public key bytes.
    """
    # Get m
    m = script[0] - 80
    if m < 1 or m > 15:
        return None

    # Get pubkeys
    pubkeys = []
    offset = 1
    while True:
        pubkey_len = script[offset]
        if pubkey_len != 33:
            break
        offset += 1
        pubkeys.append(script[offset:offset + 33])
        offset += 33

    # Check things at the end
    n = script[offset] - 80
    if n != len(pubkeys):
        return None
    offset += 1
    op_cms = script[offset]
    if op_cms != 174:
        return None

    return (m, pubkeys)