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.
- blockchain_parser/__init__.py +12 -0
- blockchain_parser/address.py +83 -0
- blockchain_parser/block.py +104 -0
- blockchain_parser/block_header.py +90 -0
- blockchain_parser/blockchain.py +302 -0
- blockchain_parser/index.py +81 -0
- blockchain_parser/input.py +78 -0
- blockchain_parser/output.py +145 -0
- blockchain_parser/script.py +164 -0
- blockchain_parser/tests/__init__.py +10 -0
- blockchain_parser/tests/data/bech32_p2wpkh.txt +1 -0
- blockchain_parser/tests/data/bech32_p2wsh.txt +1 -0
- blockchain_parser/tests/data/bip69_false.txt +1 -0
- blockchain_parser/tests/data/bip69_true.txt +1 -0
- blockchain_parser/tests/data/genesis_block.txt +1 -0
- blockchain_parser/tests/data/invalid_tx.txt +1 -0
- blockchain_parser/tests/data/large_tx.txt +1 -0
- blockchain_parser/tests/data/scripts_invalid.txt +1 -0
- blockchain_parser/tests/data/segwit.txt +1 -0
- blockchain_parser/tests/data/size_non_segwit.txt +1 -0
- blockchain_parser/tests/data/size_segwit.txt +1 -0
- blockchain_parser/tests/test_address.py +45 -0
- blockchain_parser/tests/test_block.py +55 -0
- blockchain_parser/tests/test_blockchain.py +79 -0
- blockchain_parser/tests/test_index.py +48 -0
- blockchain_parser/tests/test_output.py +66 -0
- blockchain_parser/tests/test_script.py +171 -0
- blockchain_parser/tests/test_transaction.py +138 -0
- blockchain_parser/tests/test_undo.py +70 -0
- blockchain_parser/tests/test_utils.py +55 -0
- blockchain_parser/tests/utils.py +9 -0
- blockchain_parser/transaction.py +196 -0
- blockchain_parser/undo.py +198 -0
- blockchain_parser/utils.py +152 -0
- blockchain_parser/utils_taproot.py +143 -0
- blockchain_parser_2-0.1.6.dist-info/METADATA +143 -0
- blockchain_parser_2-0.1.6.dist-info/RECORD +40 -0
- blockchain_parser_2-0.1.6.dist-info/WHEEL +5 -0
- blockchain_parser_2-0.1.6.dist-info/licenses/LICENSE +177 -0
- 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)
|