Skip to content

Commit f190c27

Browse files
committed
wip
1 parent 0d7c984 commit f190c27

File tree

1 file changed

+157
-0
lines changed

1 file changed

+157
-0
lines changed

scripts/utils.py

+157
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# License for original (reference) implementation:
2+
#
3+
# Copyright (c) 2017, 2020 Pieter Wuille
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in
13+
# all copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
# THE SOFTWARE.
22+
23+
"""Reference implementation for Bech32/Bech32m and segwit addresses."""
24+
"""This implementation removes the constrain of maximum 90 characters."""
25+
26+
27+
from collections.abc import ByteString
28+
from enum import Enum
29+
from typing import NamedTuple
30+
31+
32+
class Encoding(Enum):
33+
"""Enumeration type to list the various supported encodings."""
34+
35+
BECH32 = 1
36+
BECH32M = 2
37+
38+
39+
CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
40+
BECH32M_CONST = 0x2BC830A3
41+
42+
43+
class DecodeError(ValueError):
44+
pass
45+
46+
47+
class HrpDoesNotMatch(DecodeError):
48+
pass
49+
50+
51+
class DecodedAddress(NamedTuple):
52+
witver: int
53+
witprog: bytes
54+
55+
56+
def bech32_polymod(values: ByteString) -> int:
57+
"""Internal function that computes the Bech32 checksum."""
58+
generator = [0x3B6A57B2, 0x26508E6D, 0x1EA119FA, 0x3D4233DD, 0x2A1462B3]
59+
chk = 1
60+
for value in values:
61+
top = chk >> 25
62+
chk = (chk & 0x1FFFFFF) << 5 ^ value
63+
for i in range(5):
64+
chk ^= generator[i] if ((top >> i) & 1) else 0
65+
return chk
66+
67+
68+
def bech32_hrp_expand(hrp: str) -> bytes:
69+
"""Expand the HRP into values for checksum computation."""
70+
return bytes([ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp])
71+
72+
73+
def bech32_verify_checksum(hrp: str, data: bytes) -> Encoding:
74+
"""Verify a checksum given HRP and converted data characters."""
75+
const = bech32_polymod(bech32_hrp_expand(hrp) + data)
76+
if const == 1:
77+
return Encoding.BECH32
78+
if const == BECH32M_CONST:
79+
return Encoding.BECH32M
80+
# Invalid checksum
81+
raise DecodeError()
82+
83+
84+
def bech32_decode(bech: str) -> tuple[str, memoryview, Encoding]:
85+
"""Validate a Bech32/Bech32m string, and determine HRP and data."""
86+
if (any(ord(x) < 33 or ord(x) > 126 for x in bech)) or (
87+
bech.lower() != bech and bech.upper() != bech
88+
):
89+
# HRP character out of range
90+
raise DecodeError()
91+
bech = bech.lower()
92+
pos = bech.rfind("1")
93+
if pos < 1 or pos + 7 > len(bech):
94+
# No separator character / Empty HRP / overall max length exceeded
95+
raise DecodeError()
96+
if not all(x in CHARSET for x in bech[pos + 1 :]):
97+
# Invalid data character
98+
raise DecodeError()
99+
hrp = bech[:pos]
100+
data = memoryview(bytes(CHARSET.find(x) for x in bech[pos + 1 :]))
101+
spec = bech32_verify_checksum(hrp, data)
102+
return (hrp, data[:-6], spec)
103+
104+
105+
def convertbits(data: ByteString, frombits: int, tobits: int, pad: bool = True) -> bytearray:
106+
"""General power-of-2 base conversion."""
107+
acc = 0
108+
bits = 0
109+
ret = bytearray()
110+
maxv = (1 << tobits) - 1
111+
max_acc = (1 << (frombits + tobits - 1)) - 1
112+
for value in data:
113+
if value < 0 or (value >> frombits):
114+
# XXX Not covered by tests
115+
raise DecodeError()
116+
acc = ((acc << frombits) | value) & max_acc
117+
bits += frombits
118+
while bits >= tobits:
119+
bits -= tobits
120+
ret.append((acc >> bits) & maxv)
121+
if pad:
122+
if bits:
123+
ret.append((acc << (tobits - bits)) & maxv)
124+
elif bits >= frombits or ((acc << (tobits - bits)) & maxv):
125+
# More than 4 padding bits / Non-zero padding in 8-to-5 conversion
126+
raise DecodeError()
127+
return ret
128+
129+
130+
def decode(hrp: str, addr: str) -> DecodedAddress:
131+
"""Decode a segwit address."""
132+
hrpgot, data, spec = bech32_decode(addr)
133+
if hrpgot != hrp:
134+
raise HrpDoesNotMatch()
135+
witprog = convertbits(data[1:], 5, 8, False)
136+
if len(witprog) < 2 or len(witprog) > 40:
137+
# Invalid program length
138+
raise DecodeError()
139+
witver = data[0]
140+
if witver > 16:
141+
# Invalid witness version
142+
raise DecodeError()
143+
if witver == 0 and len(witprog) != 20 and len(witprog) != 32:
144+
# Invalid program length for witness version 0 (per BIP141)
145+
raise DecodeError()
146+
if witver == 0 and spec != Encoding.BECH32 or witver != 0 and spec != Encoding.BECH32M:
147+
# Invalid checksum algorithm
148+
raise DecodeError()
149+
return DecodedAddress(witver, witprog)
150+
151+
152+
def is_valid_bech32m(data, hrp):
153+
try:
154+
this_hrp, _, _ = bech32_decode(data)
155+
return this_hrp == hrp
156+
except Exception:
157+
return False

0 commit comments

Comments
 (0)