Skip to content

Commit e0c7db1

Browse files
jimmysongclaude
andcommitted
Add 166 unit tests to increase coverage across 7 modules
New test files for previously untested modules: - test_siphash.py: 64 official test vectors + incremental update/copy/digest - test_witness.py: parse/serialize roundtrip, clone, has_annex, edge cases - test_pbkdf2.py: RFC 6070 vectors, hexread, close, input validation Expanded coverage for under-tested modules: - test_hash.py: 1 -> 11 tests (all tagged hash functions) - test_bech32.py: 1 -> 20 tests (bc32, CBOR, convertbits, error handling) - test_mnemonic.py: 2 -> 16 tests (BIP39 vectors, WordList, invalid inputs) - test_op.py: 5 -> 62 tests (encode/decode_num, stack/arithmetic/crypto ops) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c0b7d57 commit e0c7db1

7 files changed

Lines changed: 1104 additions & 7 deletions

File tree

buidl/test/test_bech32.py

Lines changed: 151 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
11
from unittest import TestCase
22

33
from buidl.bech32 import (
4-
encode_bech32_checksum,
5-
decode_bech32,
64
BECH32_ALPHABET,
5+
bc32decode,
6+
bc32encode,
7+
bech32_create_checksum,
8+
bech32_verify_checksum,
9+
bech32m_create_checksum,
10+
bech32m_verify_checksum,
11+
cbor_decode,
12+
cbor_encode,
13+
convertbits,
14+
decode_bech32,
15+
encode_bech32_checksum,
16+
group_32,
17+
uses_only_bech32_chars,
718
)
819

920

@@ -87,3 +98,141 @@ def test_bech32(self):
8798
self.assertEqual(got_network, network)
8899
self.assertEqual(got_version, version)
89100
self.assertEqual(got_raw, raw[2:])
101+
102+
103+
class Bc32Test(TestCase):
104+
def test_bc32_roundtrip(self):
105+
data = b"hello world"
106+
encoded = bc32encode(data)
107+
self.assertEqual(encoded, "dpjkcmr0ypmk7unvvsrvvse8")
108+
decoded = bc32decode(encoded)
109+
self.assertEqual(decoded, data)
110+
111+
def test_bc32_empty(self):
112+
encoded = bc32encode(b"")
113+
decoded = bc32decode(encoded)
114+
self.assertEqual(decoded, b"")
115+
116+
def test_bc32_roundtrip_binary(self):
117+
data = bytes(range(256))
118+
encoded = bc32encode(data)
119+
decoded = bc32decode(encoded)
120+
self.assertEqual(decoded, data)
121+
122+
def test_bc32_invalid_mixed_case(self):
123+
# bc32 strings should be single case
124+
result = bc32decode("DpJkCmR0")
125+
self.assertIsNone(result)
126+
127+
def test_bc32_invalid_chars(self):
128+
result = bc32decode("INVALIDCHARS!!!")
129+
self.assertIsNone(result)
130+
131+
def test_bc32_bad_checksum(self):
132+
encoded = bc32encode(b"test")
133+
# corrupt last character
134+
corrupted = encoded[:-1] + ("q" if encoded[-1] != "q" else "p")
135+
result = bc32decode(corrupted)
136+
self.assertIsNone(result)
137+
138+
139+
class CborTest(TestCase):
140+
def test_cbor_short(self):
141+
"""Length <= 23 uses single-byte prefix."""
142+
data = b"\x01\x02\x03"
143+
encoded = cbor_encode(data)
144+
self.assertEqual(encoded, bytes.fromhex("43010203"))
145+
self.assertEqual(cbor_decode(encoded), data)
146+
147+
def test_cbor_medium(self):
148+
"""Length 24-255 uses 0x58 prefix."""
149+
data = bytes(range(30))
150+
encoded = cbor_encode(data)
151+
self.assertEqual(encoded[0], 0x58)
152+
self.assertEqual(encoded[1], 30)
153+
self.assertEqual(cbor_decode(encoded), data)
154+
155+
def test_cbor_large(self):
156+
"""Length 256-65535 uses 0x59 prefix."""
157+
data = bytes([0xAB]) * 300
158+
encoded = cbor_encode(data)
159+
self.assertEqual(encoded[0], 0x59)
160+
self.assertEqual(int.from_bytes(encoded[1:3], "big"), 300)
161+
self.assertEqual(cbor_decode(encoded), data)
162+
163+
def test_cbor_roundtrip_empty(self):
164+
data = b""
165+
self.assertEqual(cbor_decode(cbor_encode(data)), data)
166+
167+
def test_cbor_roundtrip_boundary_23(self):
168+
data = bytes(range(23))
169+
self.assertEqual(cbor_decode(cbor_encode(data)), data)
170+
171+
def test_cbor_roundtrip_boundary_24(self):
172+
data = bytes(range(24))
173+
encoded = cbor_encode(data)
174+
self.assertEqual(encoded[0], 0x58)
175+
self.assertEqual(cbor_decode(encoded), data)
176+
177+
178+
class ConvertbitsTest(TestCase):
179+
def test_8_to_5(self):
180+
result = convertbits([0, 1, 2, 3], 8, 5)
181+
self.assertEqual(result, [0, 0, 0, 16, 4, 0, 24])
182+
183+
def test_roundtrip(self):
184+
original = list(range(20))
185+
five_bit = convertbits(original, 8, 5)
186+
back = convertbits(five_bit, 5, 8, False)
187+
self.assertEqual(back, original)
188+
189+
def test_invalid_value(self):
190+
result = convertbits([-1], 8, 5)
191+
self.assertIsNone(result)
192+
193+
def test_value_too_large(self):
194+
result = convertbits([256], 8, 5)
195+
self.assertIsNone(result)
196+
197+
198+
class Group32Test(TestCase):
199+
def test_basic(self):
200+
result = group_32(b"\x00\x01\x02")
201+
self.assertEqual(result, [0, 0, 0, 16, 4])
202+
203+
204+
class UsesOnlyBech32CharsTest(TestCase):
205+
def test_valid(self):
206+
self.assertTrue(uses_only_bech32_chars("qpzry9x8gf2tvdw0s3jn54khce6mua7l"))
207+
208+
def test_valid_uppercase(self):
209+
self.assertTrue(uses_only_bech32_chars("QPZRY9X8GF2TVDW0S3JN54KHCE6MUA7L"))
210+
211+
def test_invalid(self):
212+
self.assertFalse(uses_only_bech32_chars("boi"))
213+
214+
def test_empty(self):
215+
self.assertTrue(uses_only_bech32_chars(""))
216+
217+
218+
class Bech32ChecksumTest(TestCase):
219+
def test_bech32_checksum_roundtrip(self):
220+
hrp = "bc"
221+
data = [0, 14, 20, 15, 7, 13, 26, 0]
222+
checksum = bech32_create_checksum(hrp, data)
223+
self.assertTrue(bech32_verify_checksum(hrp, data + checksum))
224+
225+
def test_bech32m_checksum_roundtrip(self):
226+
hrp = "bc"
227+
data = [1, 14, 20, 15, 7, 13, 26, 0]
228+
checksum = bech32m_create_checksum(hrp, data)
229+
self.assertTrue(bech32m_verify_checksum(hrp, data + checksum))
230+
231+
def test_decode_invalid_network(self):
232+
with self.assertRaises(ValueError):
233+
decode_bech32("xx1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq0l98cr")
234+
235+
def test_encode_invalid_network(self):
236+
raw = bytes.fromhex("00140000000000000000000000000000000000000000")
237+
with self.assertRaises(ValueError):
238+
encode_bech32_checksum(raw, network="invalid")

buidl/test/test_hash.py

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,130 @@
11
from unittest import TestCase
22

3-
from buidl.hash import hash_keyaggcoef
3+
from buidl.hash import (
4+
hash_aux,
5+
hash_challenge,
6+
hash_keyaggcoef,
7+
hash_keyagglist,
8+
hash_musignonce,
9+
hash_nonce,
10+
hash_tapbranch,
11+
hash_tapleaf,
12+
hash_tapsighash,
13+
hash_taptweak,
14+
)
415

516

617
class HashTest(TestCase):
7-
def test_keyaggcoef(self):
18+
def test_hash_keyaggcoef(self):
819
want = "55a02026378a033a97431c5ac6a72eeec43069940a330431216895c11eff3cc7"
920
self.assertEqual(hash_keyaggcoef(b"").hex(), want)
21+
want2 = "611b0f73920e178c3ff2465c26050d88ec53e37e94f532e9389e22e8b22527f5"
22+
self.assertEqual(hash_keyaggcoef(b"hello").hex(), want2)
23+
24+
def test_hash_aux(self):
25+
self.assertEqual(
26+
hash_aux(b"").hex(),
27+
"07fab5f97e680abb8389d1fa164281e124439468f5bd699fcbd1ae86e6405d69",
28+
)
29+
self.assertEqual(
30+
hash_aux(b"hello").hex(),
31+
"28c62e0c486da0ba439f836f30bc05cba5c9e39ff2722c362722c8fc7aecdd64",
32+
)
33+
34+
def test_hash_challenge(self):
35+
self.assertEqual(
36+
hash_challenge(b"").hex(),
37+
"c216d352f5818b7b4beacd4ae0a26fe888080823d2a598856661bcd54f1b3713",
38+
)
39+
self.assertEqual(
40+
hash_challenge(b"hello").hex(),
41+
"a97ff4dc59e2e158c00a7d9cf1e7d60fb090ecf5f728b6d17be7cbbb0fc572dd",
42+
)
43+
44+
def test_hash_nonce(self):
45+
self.assertEqual(
46+
hash_nonce(b"").hex(),
47+
"5301f1001a8be6253a3583927793565cef360de8bac2bdcbf37b195e699435a8",
48+
)
49+
self.assertEqual(
50+
hash_nonce(b"hello").hex(),
51+
"11a805465ee0853d8c12cbaa6d431a0ff9e5adfa0c92d9a9a027a3f54b9e8eab",
52+
)
53+
54+
def test_hash_tapbranch(self):
55+
self.assertEqual(
56+
hash_tapbranch(b"").hex(),
57+
"53c373ec4d6f3c53c1f5fb2ff506dcefe1a0ed74874f93fa93c8214cbe9ffddf",
58+
)
59+
self.assertEqual(
60+
hash_tapbranch(b"hello").hex(),
61+
"85078decf0533d9abbcaa0589e410b358752403b1c45cf1952f3923e06dbc88f",
62+
)
63+
64+
def test_hash_tapleaf(self):
65+
self.assertEqual(
66+
hash_tapleaf(b"").hex(),
67+
"5212c288a377d1f8164962a5a13429f9ba6a7b84e59776a52c6637df2106facb",
68+
)
69+
self.assertEqual(
70+
hash_tapleaf(b"hello").hex(),
71+
"cc0d501f9c9c9610e44edb939ad98d59a1a98739d0e26dbe932660ae33a8196e",
72+
)
73+
74+
def test_hash_tapsighash(self):
75+
self.assertEqual(
76+
hash_tapsighash(b"").hex(),
77+
"dabc11914abcd8072900042a2681e52f8dba99ce82e224f97b5fdb7cd4b9c803",
78+
)
79+
self.assertEqual(
80+
hash_tapsighash(b"hello").hex(),
81+
"ac12b2c18138a6ea5b05c3d05f88c7cb305fb94fe448aebdbba7b1dee05dd0eb",
82+
)
83+
84+
def test_hash_taptweak(self):
85+
self.assertEqual(
86+
hash_taptweak(b"").hex(),
87+
"8aa4229474ab0100b2d6f0687f031d1fc9d8eef92a042ad97d279bff456b15e4",
88+
)
89+
self.assertEqual(
90+
hash_taptweak(b"hello").hex(),
91+
"38c140830ab88bf8dac2e03f0d765b6677949e5a9aab799378f49dde69c71926",
92+
)
93+
94+
def test_hash_keyagglist(self):
95+
self.assertEqual(
96+
hash_keyagglist(b"").hex(),
97+
"634f77a422b6a39257a76f2c13ae017702bacd4c49b33dad1139cdd56060d360",
98+
)
99+
self.assertEqual(
100+
hash_keyagglist(b"hello").hex(),
101+
"c5bd0bd5c14f3d375ee03b476b8afa7209650974e564992c0ffd2b9572c6f762",
102+
)
103+
104+
def test_hash_musignonce(self):
105+
self.assertEqual(
106+
hash_musignonce(b"").hex(),
107+
"74fc59e69748b3ab4f2d378fa0b0beaa8c2a34dd65a04d7bf0b406b2f8489329",
108+
)
109+
self.assertEqual(
110+
hash_musignonce(b"hello").hex(),
111+
"d5d765e8129eb1959a584408112484ed19f55d095545da60dfa0f19a342a1362",
112+
)
113+
114+
def test_all_return_32_bytes(self):
115+
"""All tagged hash functions should return 32 bytes (SHA256 output)."""
116+
fns = [
117+
hash_aux,
118+
hash_challenge,
119+
hash_keyaggcoef,
120+
hash_keyagglist,
121+
hash_musignonce,
122+
hash_nonce,
123+
hash_tapbranch,
124+
hash_tapleaf,
125+
hash_tapsighash,
126+
hash_taptweak,
127+
]
128+
for fn in fns:
129+
result = fn(b"test")
130+
self.assertEqual(len(result), 32, f"{fn.__name__} should return 32 bytes")

0 commit comments

Comments
 (0)