blockchain-parser-2 0.1.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. blockchain_parser/__init__.py +12 -0
  2. blockchain_parser/address.py +83 -0
  3. blockchain_parser/block.py +104 -0
  4. blockchain_parser/block_header.py +90 -0
  5. blockchain_parser/blockchain.py +302 -0
  6. blockchain_parser/index.py +81 -0
  7. blockchain_parser/input.py +78 -0
  8. blockchain_parser/output.py +145 -0
  9. blockchain_parser/script.py +164 -0
  10. blockchain_parser/tests/__init__.py +10 -0
  11. blockchain_parser/tests/data/bech32_p2wpkh.txt +1 -0
  12. blockchain_parser/tests/data/bech32_p2wsh.txt +1 -0
  13. blockchain_parser/tests/data/bip69_false.txt +1 -0
  14. blockchain_parser/tests/data/bip69_true.txt +1 -0
  15. blockchain_parser/tests/data/genesis_block.txt +1 -0
  16. blockchain_parser/tests/data/invalid_tx.txt +1 -0
  17. blockchain_parser/tests/data/large_tx.txt +1 -0
  18. blockchain_parser/tests/data/scripts_invalid.txt +1 -0
  19. blockchain_parser/tests/data/segwit.txt +1 -0
  20. blockchain_parser/tests/data/size_non_segwit.txt +1 -0
  21. blockchain_parser/tests/data/size_segwit.txt +1 -0
  22. blockchain_parser/tests/test_address.py +45 -0
  23. blockchain_parser/tests/test_block.py +55 -0
  24. blockchain_parser/tests/test_blockchain.py +79 -0
  25. blockchain_parser/tests/test_index.py +48 -0
  26. blockchain_parser/tests/test_output.py +66 -0
  27. blockchain_parser/tests/test_script.py +171 -0
  28. blockchain_parser/tests/test_transaction.py +138 -0
  29. blockchain_parser/tests/test_undo.py +70 -0
  30. blockchain_parser/tests/test_utils.py +55 -0
  31. blockchain_parser/tests/utils.py +9 -0
  32. blockchain_parser/transaction.py +196 -0
  33. blockchain_parser/undo.py +198 -0
  34. blockchain_parser/utils.py +152 -0
  35. blockchain_parser/utils_taproot.py +143 -0
  36. blockchain_parser_2-0.1.6.dist-info/METADATA +143 -0
  37. blockchain_parser_2-0.1.6.dist-info/RECORD +40 -0
  38. blockchain_parser_2-0.1.6.dist-info/WHEEL +5 -0
  39. blockchain_parser_2-0.1.6.dist-info/licenses/LICENSE +177 -0
  40. blockchain_parser_2-0.1.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,196 @@
1
+ # Copyright (C) 2015-2016 The bitcoin-blockchain-parser developers
2
+ #
3
+ # This file is part of bitcoin-blockchain-parser.
4
+ #
5
+ # It is subject to the license terms in the LICENSE file found in the top-level
6
+ # directory of this distribution.
7
+ #
8
+ # No part of bitcoin-blockchain-parser, including this file, may be copied,
9
+ # modified, propagated, or distributed except according to the terms contained
10
+ # in the LICENSE file.
11
+
12
+ from math import ceil
13
+
14
+ from .utils import decode_compactsize, decode_uint32, double_sha256, format_hash
15
+ from .input import Input
16
+ from .output import Output
17
+
18
+
19
+ def bip69_sort(data):
20
+ return list(sorted(data, key=lambda t: (t[0], t[1])))
21
+
22
+
23
+ class Transaction(object):
24
+ """Represents a bitcoin transaction"""
25
+
26
+ def __init__(self, raw_hex):
27
+ self._hash = None
28
+ self._txid = None
29
+ self.inputs = None
30
+ self.outputs = None
31
+ self._version = None
32
+ self._locktime = None
33
+ self._size = None
34
+ self.n_inputs = 0
35
+ self.n_outputs = 0
36
+ self.is_segwit = False
37
+
38
+ offset = 4
39
+
40
+ # adds basic support for segwit transactions
41
+ # - https://bitcoincore.org/en/segwit_wallet_dev/
42
+ # - https://en.bitcoin.it/wiki/Protocol_documentation#BlockTransactions
43
+ if b'\x00\x01' == raw_hex[offset:offset + 2]:
44
+ self.is_segwit = True
45
+ offset += 2
46
+
47
+ self.n_inputs, varint_size = decode_compactsize(raw_hex[offset:])
48
+ offset += varint_size
49
+
50
+ self.inputs = []
51
+ for i in range(self.n_inputs):
52
+ input = Input.from_hex(raw_hex[offset:])
53
+ offset += input.size
54
+ self.inputs.append(input)
55
+
56
+ self.n_outputs, varint_size = decode_compactsize(raw_hex[offset:])
57
+ offset += varint_size
58
+
59
+ self.outputs = []
60
+ for i in range(self.n_outputs):
61
+ output = Output.from_hex(raw_hex[offset:])
62
+ offset += output.size
63
+ self.outputs.append(output)
64
+
65
+ if self.is_segwit:
66
+ self._offset_before_tx_witnesses = offset
67
+ for inp in self.inputs:
68
+ tx_witnesses_n, varint_size = decode_compactsize(raw_hex[offset:])
69
+ offset += varint_size
70
+ for j in range(tx_witnesses_n):
71
+ component_length, varint_size = decode_compactsize(
72
+ raw_hex[offset:])
73
+ offset += varint_size
74
+ witness = raw_hex[offset:offset + component_length]
75
+ inp.add_witness(witness)
76
+ offset += component_length
77
+
78
+ self._size = offset + 4
79
+ self.hex = raw_hex[:self._size]
80
+
81
+ if self._size != len(self.hex):
82
+ raise Exception("Incomplete transaction!")
83
+
84
+ def __repr__(self):
85
+ return "Transaction(%s)" % self.hash
86
+
87
+ @classmethod
88
+ def from_hex(cls, hex):
89
+ return cls(hex)
90
+
91
+ @property
92
+ def version(self):
93
+ """Returns the transaction's version number"""
94
+ if self._version is None:
95
+ self._version = decode_uint32(self.hex[:4])
96
+ return self._version
97
+
98
+ @property
99
+ def locktime(self):
100
+ """Returns the transaction's locktime as an int"""
101
+ if self._locktime is None:
102
+ self._locktime = decode_uint32(self.hex[-4:])
103
+ return self._locktime
104
+
105
+ @property
106
+ def hash(self):
107
+ """Returns the transaction's id. Equivalent to the hash for non SegWit transactions,
108
+ it differs from it for SegWit ones. """
109
+ if self._hash is None:
110
+ self._hash = format_hash(double_sha256(self.hex))
111
+
112
+ return self._hash
113
+
114
+ @property
115
+ def size(self):
116
+ """Returns the transactions size in bytes including the size of the
117
+ witness data if there is any."""
118
+ return self._size
119
+
120
+ @property
121
+ def vsize(self):
122
+ """Returns the transaction size in virtual bytes."""
123
+ if not self.is_segwit:
124
+ return self._size
125
+ else:
126
+ # the witness is the last element in a transaction before the
127
+ # 4 byte locktime and self._offset_before_tx_witnesses is the
128
+ # position where the witness starts
129
+ witness_size = self._size - self._offset_before_tx_witnesses - 4
130
+
131
+ # size of the transaction without the segwit marker (2 bytes) and
132
+ # the witness
133
+ stripped_size = self._size - (2 + witness_size)
134
+ weight = stripped_size * 3 + self._size
135
+
136
+ # vsize is weight / 4 rounded up
137
+ return ceil(weight / 4)
138
+
139
+ @property
140
+ def txid(self):
141
+ """Returns the transaction's id. Equivalent to the hash for non SegWit transactions,
142
+ it differs from it for SegWit ones. """
143
+ if self._txid is None:
144
+ # segwit transactions have two transaction ids/hashes, txid and wtxid
145
+ # txid is a hash of all of the legacy transaction fields only
146
+ if self.is_segwit:
147
+ txid_data = self.hex[:4] + self.hex[
148
+ 6:self._offset_before_tx_witnesses] + self.hex[
149
+ -4:]
150
+ else:
151
+ txid_data = self.hex
152
+ self._txid = format_hash(double_sha256(txid_data))
153
+
154
+ return self._txid
155
+
156
+ def is_coinbase(self):
157
+ """Returns whether the transaction is a coinbase transaction"""
158
+ for input in self.inputs:
159
+ if input.transaction_hash == "0" * 64:
160
+ return True
161
+ return False
162
+
163
+ def uses_replace_by_fee(self):
164
+ """Returns whether the transaction opted-in for RBF"""
165
+ # Coinbase transactions may have a sequence number that signals RBF
166
+ # but they cannot use it as it's only enforced for non-coinbase txs
167
+ if self.is_coinbase():
168
+ return False
169
+
170
+ # A transactions opts-in for RBF when having an input
171
+ # with a sequence number < MAX_INT - 1
172
+ for input in self.inputs:
173
+ if input.sequence_number < 4294967294:
174
+ return True
175
+ return False
176
+
177
+ def uses_bip69(self):
178
+ """Returns whether the transaction complies to BIP-69,
179
+ lexicographical ordering of inputs and outputs"""
180
+ # Quick check
181
+ if self.n_inputs == 1 and self.n_outputs == 1:
182
+ return True
183
+
184
+ # BIP69 compares previous outpoints in display byte order, then index.
185
+ input_keys = [
186
+ (i.hex[:32][::-1], i.transaction_index)
187
+ for i in self.inputs
188
+ ]
189
+
190
+ if bip69_sort(input_keys) != input_keys:
191
+ return False
192
+
193
+ # Output scripts are compared as raw scriptPubKey bytes.
194
+ output_keys = [(o.value, o.script.hex) for o in self.outputs]
195
+
196
+ return bip69_sort(output_keys) == output_keys
@@ -0,0 +1,198 @@
1
+ # Copyright (C) 2015-2020 The bitcoin-blockchain-parser developers
2
+ #
3
+ # This file is part of bitcoin-blockchain-parser.
4
+ #
5
+ # It is subject to the license terms in the LICENSE file found in the top-level
6
+ # directory of this distribution.
7
+ #
8
+ # No part of bitcoin-blockchain-parser, including this file, may be copied,
9
+ # modified, propagated, or distributed except according to the terms contained
10
+ # in the LICENSE file.
11
+
12
+ from .script import Script
13
+ from .utils import decode_varint, decode_compactsize, decompress_txout_amt
14
+
15
+ _SECP256K1_P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
16
+ _NSPECIAL_SCRIPTS = 6
17
+
18
+
19
+ def _decompress_pubkey(prefix, x_bytes):
20
+ """Recover the uncompressed SEC pubkey from a compressed x coordinate."""
21
+ if prefix not in (2, 3) or len(x_bytes) != 32:
22
+ raise ValueError("invalid compressed pubkey")
23
+ x = int.from_bytes(x_bytes, "big")
24
+ if x >= _SECP256K1_P:
25
+ raise ValueError("pubkey x coordinate is out of range")
26
+ y_squared = (pow(x, 3, _SECP256K1_P) + 7) % _SECP256K1_P
27
+ y = pow(y_squared, (_SECP256K1_P + 1) // 4, _SECP256K1_P)
28
+ if pow(y, 2, _SECP256K1_P) != y_squared:
29
+ raise ValueError("pubkey x coordinate is not on the curve")
30
+ if (y & 1) != (prefix & 1):
31
+ y = _SECP256K1_P - y
32
+ return b"\x04" + x_bytes + y.to_bytes(32, "big")
33
+
34
+
35
+ def decompress_script(raw_hex):
36
+ """Return scriptPubKey bytes stored in Bitcoin Core's compressed form.
37
+
38
+ The encoding is the one in bitcoin/src/compressor.cpp. Special types
39
+ 0 and 1 are 20-byte hashes, types 2 and 3 are compressed pubkeys, types
40
+ 4 and 5 are x-only coordinates of uncompressed pubkeys, and every larger
41
+ size code is ``len(script) + 6`` followed by the raw script.
42
+ """
43
+ if not raw_hex:
44
+ raise ValueError("empty compressed script")
45
+ script_type, type_len = decode_varint(raw_hex)
46
+ payload = raw_hex[type_len:]
47
+
48
+ if script_type == 0:
49
+ if len(payload) < 20:
50
+ raise ValueError("Compressed script has wrong size")
51
+ program = payload[:20]
52
+ return b"\x76\xa9\x14" + program + b"\x88\xac"
53
+ if script_type == 1:
54
+ if len(payload) < 20:
55
+ raise ValueError("Compressed script has wrong size")
56
+ program = payload[:20]
57
+ return b"\xa9\x14" + program + b"\x87"
58
+ if script_type in (2, 3):
59
+ if len(payload) < 32:
60
+ raise ValueError("Compressed script has wrong size")
61
+ pubkey = bytes([script_type]) + payload[:32]
62
+ return bytes([33]) + pubkey + b"\xac"
63
+ if script_type in (4, 5):
64
+ if len(payload) < 32:
65
+ raise ValueError("Compressed script has wrong size")
66
+ pubkey = _decompress_pubkey(script_type - 2, payload[:32])
67
+ return bytes([65]) + pubkey + b"\xac"
68
+ if script_type < _NSPECIAL_SCRIPTS:
69
+ raise ValueError("unknown compressed script type")
70
+ script_len = script_type - _NSPECIAL_SCRIPTS
71
+ if len(payload) < script_len:
72
+ raise ValueError("Compressed script has wrong size")
73
+ return payload[:script_len]
74
+
75
+
76
+ def _script_from_compressed(raw_hex):
77
+ return Script.from_hex(decompress_script(raw_hex))
78
+
79
+
80
+ class BlockUndo(object):
81
+ """
82
+ Represents a block of spent transaction outputs (coins), as encoded
83
+ in the undo rev*.dat files
84
+ """
85
+ def __init__(self, raw_hex):
86
+ self._raw_hex = raw_hex
87
+ self.spends = []
88
+ num_txs, pos = decode_compactsize(raw_hex)
89
+ # print("found %d" % num_txs + " transactions")
90
+ for i in range(num_txs):
91
+ # print("calling SpentOutput with raw_hex %s", raw_hex)
92
+ txn = SpentTransaction(raw_hex=raw_hex[pos:])
93
+ self.spends.append(txn)
94
+ # print("found transaction #%d length %d hex: " % (i, txn.len), raw_hex[pos:pos+txn.len].hex())
95
+ pos += txn.len
96
+
97
+
98
+ class SpentTransaction(object):
99
+ """Represents the script portion of a spent Transaction output"""
100
+ def __init__(self, raw_hex=None):
101
+ self._raw_hex = raw_hex
102
+ self.outputs = []
103
+ # print("decoding compactsize for hex: ", raw_hex.hex())
104
+ self.output_len, pos = decode_compactsize(raw_hex)
105
+ # print("found %d" % self.output_len + " outputs")
106
+ for i in range(self.output_len):
107
+ output = SpentOutput(raw_hex=raw_hex[pos:])
108
+ self.outputs.append(output)
109
+ # print("found output #%d length %d hex: " % (i, output.len), raw_hex[pos:pos+output.len].hex())
110
+ pos += output.len
111
+ self.len = pos
112
+
113
+ @classmethod
114
+ def from_hex(cls, hex_):
115
+ return cls(hex_)
116
+
117
+
118
+ class SpentOutput(object):
119
+ """Represents a spent Transaction output"""
120
+
121
+ def __init__(self, raw_hex=None):
122
+ self._raw_hex = raw_hex
123
+ self._script = None
124
+ pos = 0
125
+
126
+ # decode height code
127
+ height_code, height_code_len = decode_varint(raw_hex[pos:])
128
+ if height_code % 2 == 1:
129
+ self.is_coinbase = True
130
+ height_code -= 1
131
+ else:
132
+ self.is_coinbase = False
133
+ self.height = height_code // 2
134
+
135
+ # Bitcoin Core writes a compatibility byte only when height > 0.
136
+ pos = height_code_len
137
+ if self.height > 0:
138
+ pos += 1
139
+
140
+ # decode compressed txout amount
141
+ compressed_amt, compressed_amt_len = decode_varint(raw_hex[pos:])
142
+ self.amt = decompress_txout_amt(compressed_amt)
143
+ pos += compressed_amt_len
144
+
145
+ # get script
146
+ script_hex, script_len = SpentScriptPubKey.extract_from_hex(raw_hex[pos:])
147
+ self.script_pub_key_compressed = SpentScriptPubKey(script_hex)
148
+ self.len = pos + script_len
149
+
150
+ @classmethod
151
+ def from_hex(cls, hex_):
152
+ return cls(hex_)
153
+
154
+
155
+ @property
156
+ def script(self):
157
+ if self._script is None:
158
+ raw_hex = self.script_pub_key_compressed._raw_hex
159
+ self._script = _script_from_compressed(raw_hex)
160
+ return self._script
161
+
162
+
163
+
164
+ class SpentScriptPubKey(object):
165
+ """Represents the script portion of a spent Transaction output"""
166
+ def __init__(self, raw_hex=None):
167
+ self._raw_hex = raw_hex
168
+ self._script = None
169
+ self.len = len(raw_hex)
170
+
171
+ @classmethod
172
+ def from_hex(cls, hex_):
173
+ return cls(hex_)
174
+
175
+ @classmethod
176
+ def extract_from_hex(cls, raw_hex):
177
+ """Return the compressed script bytes and the number of bytes consumed."""
178
+ if not raw_hex:
179
+ raise ValueError("empty compressed script")
180
+ if raw_hex[0] in (0x00, 0x01):
181
+ size = 21
182
+ elif raw_hex[0] in (0x02, 0x03, 0x04, 0x05):
183
+ size = 33
184
+ else:
185
+ script_len_code, script_len_code_len = decode_varint(raw_hex)
186
+ real_script_len = script_len_code - _NSPECIAL_SCRIPTS
187
+ size = script_len_code_len + real_script_len
188
+ if real_script_len < 0:
189
+ raise ValueError("invalid compressed script size")
190
+ if size > len(raw_hex):
191
+ raise ValueError("Compressed script has wrong size")
192
+ return (raw_hex[:size], size)
193
+
194
+ @property
195
+ def script(self):
196
+ if self._script is None:
197
+ self._script = _script_from_compressed(self._raw_hex)
198
+ return self._script
@@ -0,0 +1,152 @@
1
+ # Copyright (C) 2015-2016 The bitcoin-blockchain-parser developers
2
+ #
3
+ # This file is part of bitcoin-blockchain-parser.
4
+ #
5
+ # It is subject to the license terms in the LICENSE file found in the top-level
6
+ # directory of this distribution.
7
+ #
8
+ # No part of bitcoin-blockchain-parser, including this file, may be copied,
9
+ # modified, propagated, or distributed except according to the terms contained
10
+ # in the LICENSE file.
11
+
12
+ import hashlib
13
+ import struct
14
+
15
+ try:
16
+ from ripemd import ripemd160 as _ripemd160
17
+ except ImportError:
18
+ _ripemd160 = None
19
+
20
+ def btc_ripemd160(data):
21
+ """Computes ripemd160(sha256(data))"""
22
+
23
+ hashed = hashlib.sha256(data).digest()
24
+ if _ripemd160 is not None:
25
+ digest = _ripemd160.new()
26
+ digest.update(hashed)
27
+ return digest.digest()
28
+ return hashlib.new("ripemd160", hashed).digest()
29
+
30
+
31
+ def double_sha256(data):
32
+ return hashlib.sha256(hashlib.sha256(data).digest()).digest()
33
+
34
+
35
+ def format_hash(hash_):
36
+ return hash_[::-1].hex()
37
+
38
+
39
+ def decode_uint32(data):
40
+ assert(len(data) == 4)
41
+ return struct.unpack("<I", data)[0]
42
+
43
+
44
+ def decode_uint64(data):
45
+ assert(len(data) == 8)
46
+ return struct.unpack("<Q", data)[0]
47
+
48
+
49
+ def decode_compactsize(data):
50
+ assert(len(data) > 0)
51
+ size = int(data[0])
52
+ assert(size <= 255)
53
+
54
+ if size < 253:
55
+ return size, 1
56
+
57
+ if size == 253:
58
+ format_ = '<H'
59
+ elif size == 254:
60
+ format_ = '<I'
61
+ elif size == 255:
62
+ format_ = '<Q'
63
+ else:
64
+ # Should never be reached
65
+ assert 0, "unknown format_ for size : %s" % size
66
+
67
+ size = struct.calcsize(format_)
68
+ return struct.unpack(format_, data[1:size+1])[0], size + 1
69
+
70
+
71
+ def decode_varint(raw_hex):
72
+ """
73
+ Reads the weird format of VarInt present in src/serialize.h of bitcoin core
74
+ and being used for storing data in the leveldb.
75
+ This is not the VARINT format described for general bitcoin serialization
76
+ use.
77
+ """
78
+ n = 0
79
+ pos = 0
80
+ while True:
81
+ try:
82
+ data = raw_hex[pos]
83
+ except IndexError as e:
84
+ print("IndexError caught on raw_hex: ", raw_hex, e)
85
+ raise e
86
+ pos += 1
87
+ n = (n << 7) | (data & 0x7f)
88
+ if data & 0x80 == 0:
89
+ return n, pos
90
+ n += 1
91
+
92
+
93
+ def decompress_txout_amt(amount_compressed_int):
94
+ # (this function stolen from https://github.com/sr-gi/bitcoin_tools and modified to remove bug)
95
+ # No need to do any work if it's zero.
96
+ if amount_compressed_int == 0:
97
+ return 0
98
+
99
+ # The decompressed amount is either of the following two equations:
100
+ # x = 1 + 10*(9*n + d - 1) + e
101
+ # x = 1 + 10*(n - 1) + 9
102
+ amount_compressed_int -= 1
103
+
104
+ # The decompressed amount is now one of the following two equations:
105
+ # x = 10*(9*n + d - 1) + e
106
+ # x = 10*(n - 1) + 9
107
+ exponent = amount_compressed_int % 10
108
+
109
+ # integer division
110
+ amount_compressed_int //= 10
111
+
112
+ # The decompressed amount is now one of the following two equations:
113
+ # x = 9*n + d - 1 | where e < 9
114
+ # x = n - 1 | where e = 9
115
+ n = 0
116
+ if exponent < 9:
117
+ lastDigit = amount_compressed_int%9 + 1
118
+ # integer division
119
+ amount_compressed_int //= 9
120
+ n = amount_compressed_int*10 + lastDigit
121
+ else:
122
+ n = amount_compressed_int + 1
123
+
124
+ # Apply the exponent.
125
+ return n * 10**exponent
126
+
127
+
128
+ def compress_txout_amt(n):
129
+ """ Compresses the Satoshi amount of a UTXO to be stored in the LevelDB. Code is a port from the Bitcoin Core C++
130
+ source:
131
+ https://github.com/bitcoin/bitcoin/blob/v0.13.2/src/compressor.cpp#L133#L160
132
+ :param n: Satoshi amount to be compressed.
133
+ :type n: int
134
+ :return: The compressed amount of Satoshis.
135
+ :rtype: int
136
+ (this function stolen from https://github.com/sr-gi/bitcoin_tools and modified to remove bug)
137
+ """
138
+
139
+ if n == 0:
140
+ return 0
141
+ e = 0
142
+ while ((n % 10) == 0) and e < 9:
143
+ n //= 10
144
+ e += 1
145
+
146
+ if e < 9:
147
+ d = (n % 10)
148
+ assert (1 <= d <= 9)
149
+ n //= 10
150
+ return 1 + (n * 9 + d - 1) * 10 + e
151
+ else:
152
+ return 1 + (n - 1) * 10 + 9
@@ -0,0 +1,143 @@
1
+ # Copyright (C) 2015-2016 The bitcoin-blockchain-parser developers
2
+ #
3
+ # This file is part of bitcoin-blockchain-parser.
4
+ #
5
+ # It is subject to the license terms in the LICENSE file found in the top-level
6
+ # directory of this distribution.
7
+ #
8
+ # No part of bitcoin-blockchain-parser, including this file, may be copied,
9
+ # modified, propagated, or distributed except according to the terms contained
10
+ # in the LICENSE file.
11
+ #
12
+ # Encoding/Decoding written by Pieter Wuille (2017)
13
+ # and adapted by Anton Wahrstätter (2022)
14
+ # https://github.com/Bytom/python-bytomlib/blob/master/pybtmsdk/segwit_addr.py
15
+
16
+ from enum import Enum
17
+
18
+
19
+ class Encoding(Enum):
20
+ """Enumeration type to list the various supported encodings."""
21
+ BECH32 = 1
22
+ BECH32M = 2
23
+
24
+
25
+ CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
26
+ BECH32M_CONST = 0x2bc830a3
27
+
28
+
29
+ def bech32_polymod(values):
30
+ """Internal function that computes the Bech32 checksum."""
31
+ generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
32
+ chk = 1
33
+ for value in values:
34
+ top = chk >> 25
35
+ chk = (chk & 0x1ffffff) << 5 ^ value
36
+ for i in range(5):
37
+ chk ^= generator[i] if ((top >> i) & 1) else 0
38
+ return chk
39
+
40
+
41
+ def bech32_hrp_expand(hrp):
42
+ """Expand the HRP into values for checksum computation."""
43
+ return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp]
44
+
45
+
46
+ def bech32_verify_checksum(hrp, data):
47
+ """Verify a checksum given HRP and converted data characters."""
48
+ const = bech32_polymod(bech32_hrp_expand(hrp) + data)
49
+ if const == 1:
50
+ return Encoding.BECH32
51
+ if const == BECH32M_CONST:
52
+ return Encoding.BECH32M
53
+ return None
54
+
55
+
56
+ def bech32_create_checksum(hrp, data, spec):
57
+ """Compute the checksum values given HRP and data."""
58
+ values = bech32_hrp_expand(hrp) + data
59
+ const = BECH32M_CONST if spec == Encoding.BECH32M else 1
60
+ polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ const
61
+ return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)]
62
+
63
+
64
+ def bech32_encode(hrp, data, spec):
65
+ """Compute a Bech32 string given HRP and data values."""
66
+ combined = data + bech32_create_checksum(hrp, data, spec)
67
+ return hrp + '1' + ''.join([CHARSET[d] for d in combined])
68
+
69
+
70
+ def bech32_decode(bech):
71
+ """Validate a Bech32/Bech32m string, and determine HRP and data."""
72
+ if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or
73
+ (bech.lower() != bech and bech.upper() != bech)):
74
+ return (None, None, None)
75
+ bech = bech.lower()
76
+ pos = bech.rfind('1')
77
+ if pos < 1 or pos + 7 > len(bech) or len(bech) > 90:
78
+ return (None, None, None)
79
+ if not all(x in CHARSET for x in bech[pos+1:]):
80
+ return (None, None, None)
81
+ hrp = bech[:pos]
82
+ data = [CHARSET.find(x) for x in bech[pos+1:]]
83
+ spec = bech32_verify_checksum(hrp, data)
84
+ if spec is None:
85
+ return (None, None, None)
86
+ return (hrp, data[:-6], spec)
87
+
88
+
89
+ def convertbits(data, frombits, tobits, pad=True):
90
+ """General power-of-2 base conversion."""
91
+ acc = 0
92
+ bits = 0
93
+ ret = []
94
+ maxv = (1 << tobits) - 1
95
+ max_acc = (1 << (frombits + tobits - 1)) - 1
96
+ for value in data:
97
+ if value < 0 or (value >> frombits):
98
+ return None
99
+ acc = ((acc << frombits) | value) & max_acc
100
+ bits += frombits
101
+ while bits >= tobits:
102
+ bits -= tobits
103
+ ret.append((acc >> bits) & maxv)
104
+ if pad:
105
+ if bits:
106
+ ret.append((acc << (tobits - bits)) & maxv)
107
+ elif bits >= frombits or ((acc << (tobits - bits)) & maxv):
108
+ return None
109
+ return ret
110
+
111
+
112
+ def decode(hrp, addr):
113
+ """Decode a segwit address."""
114
+ hrpgot, data, spec = bech32_decode(addr)
115
+ if hrpgot != hrp:
116
+ return (None, None)
117
+ decoded = convertbits(data[1:], 5, 8, False)
118
+ if decoded is None or len(decoded) < 2 or len(decoded) > 40:
119
+ return (None, None)
120
+ if data[0] > 16:
121
+ return (None, None)
122
+ if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32:
123
+ return (None, None)
124
+ if data[0] == 0 and spec != Encoding.BECH32 \
125
+ or data[0] != 0 and spec != Encoding.BECH32M:
126
+ return (None, None)
127
+ return (data[0], decoded)
128
+
129
+
130
+ def encode(witprog):
131
+ hrp, witver = "bc", 1
132
+ """Encode a segwit address."""
133
+ spec = Encoding.BECH32M
134
+ ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5), spec)
135
+ if decode(hrp, ret) == (None, None):
136
+ return None
137
+ return ret
138
+
139
+
140
+ def from_taproot(tpk):
141
+ """Input Tweaked Public Key."""
142
+ tpk = [int(tpk[i:i+2], 16) for i in range(0, len(tpk), 2)]
143
+ return encode(tpk)