Skip to content

Commit 450ff17

Browse files
committed
fix: BIP-85 invalid_word_count assertion + TON signtx field name alignment
- BIP-85: invalid word_count test now catches CallException (firmware raises, not returns Failure proto) - TON signtx: fix derivation path to m/44'/607'/0'/0'/0'/0' (all hardened, matching getaddress tests), fix field names (destination→to_address, ton_amount→amount, comment→memo), remove nonexistent mode/cell_hash fields Dress rehearsal: 48 passed, 3 failed (TON structured sign hash verification).
1 parent eb785ff commit 450ff17

2 files changed

Lines changed: 36 additions & 69 deletions

File tree

tests/test_msg_bip85.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,10 @@ def test_bip85_invalid_word_count(self):
4747
self.requires_firmware("7.14.0")
4848
self.setup_mnemonic_allallall()
4949

50-
resp = self.client.call(proto.GetBip85Mnemonic(word_count=15, index=0))
51-
self.assertIsInstance(resp, proto.Failure)
50+
from keepkeylib.client import CallException
51+
with self.assertRaises(CallException) as ctx:
52+
self.client.call(proto.GetBip85Mnemonic(word_count=15, index=0))
53+
self.assertIn('word_count', str(ctx.exception))
5254

5355
def test_bip85_18word_flow(self):
5456
"""18-word derivation: verify the third word_count variant works."""

tests/test_msg_ton_signtx.py

Lines changed: 32 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -22,37 +22,19 @@
2222

2323
from keepkeylib import messages_pb2 as messages
2424
from keepkeylib import messages_ton_pb2 as ton_messages
25-
from keepkeylib import types_pb2 as types
2625
from keepkeylib.client import CallException
2726
from keepkeylib.tools import parse_path
2827

28+
TON_PATH = "m/44'/607'/0'/0'/0'/0'"
2929

30-
def make_ton_address(workchain=0, hash_bytes=None, bounceable=True, testnet=False):
31-
"""Construct a TON user-friendly address (48-char base64)."""
32-
if hash_bytes is None:
33-
hash_bytes = b'\xBB' * 32
34-
35-
if bounceable:
36-
flags = 0x11
37-
else:
38-
flags = 0x51
39-
40-
if testnet:
41-
flags |= 0x80
42-
43-
raw = bytes([flags, workchain & 0xFF]) + hash_bytes
44-
45-
# CRC16-XMODEM
46-
crc = 0
47-
for byte in raw:
48-
crc ^= byte << 8
49-
for _ in range(8):
50-
if crc & 0x8000:
51-
crc = (crc << 1) ^ 0x1021
52-
else:
53-
crc <<= 1
54-
crc &= 0xFFFF
5530

31+
def make_ton_address(workchain=0, hash_bytes=None, bounceable=True):
32+
"""Build a base64url TON address string."""
33+
if hash_bytes is None:
34+
hash_bytes = b'\xAA' * 32
35+
tag = 0x11 if bounceable else 0x51
36+
raw = bytes([tag, workchain & 0xFF]) + hash_bytes
37+
crc = binascii.crc_hqx(raw, 0)
5638
raw += struct.pack('>H', crc)
5739
return base64.b64encode(raw).decode('ascii')
5840

@@ -70,123 +52,106 @@ def test_ton_get_address(self):
7052
self.setup_mnemonic_allallall()
7153

7254
msg = ton_messages.TonGetAddress(
73-
address_n=parse_path("m/44'/607'/0'/0/0"),
55+
address_n=parse_path(TON_PATH),
7456
show_display=False,
7557
)
7658
resp = self.client.call(msg)
7759

78-
# Should return a raw address
7960
self.assertTrue(resp.raw_address is not None or resp.address is not None)
8061

8162
def test_ton_sign_structured(self):
82-
"""Test TON transfer using structured fields (reconstruct-then-sign)."""
63+
"""Test TON transfer using structured fields."""
8364
self.requires_fullFeature()
8465
self.setup_mnemonic_allallall()
8566

86-
dest_addr = make_ton_address(
87-
workchain=0,
88-
hash_bytes=b'\xCC' * 32,
89-
bounceable=True
90-
)
67+
dest_addr = make_ton_address(workchain=0, hash_bytes=b'\xCC' * 32, bounceable=True)
9168

9269
msg = ton_messages.TonSignTx(
93-
address_n=parse_path("m/44'/607'/0'/0/0"),
94-
destination=dest_addr,
95-
ton_amount=1000000000, # 1 TON
70+
address_n=parse_path(TON_PATH),
71+
to_address=dest_addr,
72+
amount=1000000000, # 1 TON in nanotons
9673
seqno=1,
9774
expire_at=1700000000,
9875
bounce=True,
99-
mode=3,
10076
)
10177
resp = self.client.call(msg)
10278

103-
# Should have a 64-byte Ed25519 signature
10479
self.assertEqual(len(resp.signature), 64)
105-
106-
# Should return the cell hash
107-
self.assertEqual(len(resp.cell_hash), 32)
108-
109-
# Verify signature is not all zeros
11080
self.assertFalse(all(b == 0 for b in resp.signature))
11181

112-
def test_ton_sign_with_comment(self):
113-
"""Test TON transfer with a text comment."""
82+
def test_ton_sign_with_memo(self):
83+
"""Test TON transfer with a text memo."""
11484
self.requires_fullFeature()
11585
self.setup_mnemonic_allallall()
11686

11787
dest_addr = make_ton_address()
11888

11989
msg = ton_messages.TonSignTx(
120-
address_n=parse_path("m/44'/607'/0'/0/0"),
121-
destination=dest_addr,
122-
ton_amount=500000000, # 0.5 TON
90+
address_n=parse_path(TON_PATH),
91+
to_address=dest_addr,
92+
amount=500000000, # 0.5 TON
12393
seqno=2,
12494
expire_at=1700000000,
125-
comment="Hello TON!",
95+
memo="Hello TON!",
12696
)
12797
resp = self.client.call(msg)
12898

12999
self.assertEqual(len(resp.signature), 64)
130-
self.assertEqual(len(resp.cell_hash), 32)
131100

132101
def test_ton_sign_legacy_raw_tx(self):
133102
"""Test legacy blind-sign with raw_tx field."""
134103
self.requires_fullFeature()
135104
self.setup_mnemonic_allallall()
136105

137-
# Provide arbitrary raw_tx bytes (simulating a pre-built signing message)
138-
raw_tx = b'\x00' * 64 # dummy signing message
106+
raw_tx = b'\x00' * 64
139107

140108
msg = ton_messages.TonSignTx(
141-
address_n=parse_path("m/44'/607'/0'/0/0"),
109+
address_n=parse_path(TON_PATH),
142110
raw_tx=raw_tx,
143111
)
144112
resp = self.client.call(msg)
145113

146-
# Should have a 64-byte Ed25519 signature
147114
self.assertEqual(len(resp.signature), 64)
148115

149116
def test_ton_sign_missing_fields_rejected(self):
150117
"""Test that incomplete structured fields are rejected."""
151118
self.requires_fullFeature()
152119
self.setup_mnemonic_allallall()
153120

154-
# Has destination but no amount or seqno
155121
msg = ton_messages.TonSignTx(
156-
address_n=parse_path("m/44'/607'/0'/0/0"),
157-
destination=make_ton_address(),
122+
address_n=parse_path(TON_PATH),
123+
to_address=make_ton_address(),
158124
)
159125

160126
with pytest.raises(CallException):
161127
self.client.call(msg)
162128

163129
def test_ton_sign_deterministic(self):
164-
"""Test that signing the same message produces same cell hash."""
130+
"""Test that signing the same message produces same signature."""
165131
self.requires_fullFeature()
166132
self.setup_mnemonic_allallall()
167133

168134
dest_addr = make_ton_address()
169135

170136
msg1 = ton_messages.TonSignTx(
171-
address_n=parse_path("m/44'/607'/0'/0/0"),
172-
destination=dest_addr,
173-
ton_amount=1000000000,
137+
address_n=parse_path(TON_PATH),
138+
to_address=dest_addr,
139+
amount=1000000000,
174140
seqno=1,
175141
expire_at=1700000000,
176142
)
177143
resp1 = self.client.call(msg1)
178144

179145
msg2 = ton_messages.TonSignTx(
180-
address_n=parse_path("m/44'/607'/0'/0/0"),
181-
destination=dest_addr,
182-
ton_amount=1000000000,
146+
address_n=parse_path(TON_PATH),
147+
to_address=dest_addr,
148+
amount=1000000000,
183149
seqno=1,
184150
expire_at=1700000000,
185151
)
186152
resp2 = self.client.call(msg2)
187153

188-
# Cell hash should be identical for same inputs
189-
self.assertEqual(resp1.cell_hash, resp2.cell_hash)
154+
self.assertEqual(resp1.signature, resp2.signature)
190155

191156

192157
if __name__ == '__main__':

0 commit comments

Comments
 (0)