@@ -303,5 +303,83 @@ def test_hive_sign_account_update(self):
303303 r .assert_end ()
304304
305305
306+ def _transfer_kwargs (self , ** overrides ):
307+ """Baseline valid HiveSignTx args; override per negative case."""
308+ kw = dict (
309+ address_n = hive_path (ROLE_ACTIVE ),
310+ chain_id = HIVE_CHAIN_ID ,
311+ ref_block_num = 12345 ,
312+ ref_block_prefix = 67890 ,
313+ expiration = 1700000000 ,
314+ sender = "kktester" ,
315+ recipient = "kkrecipient" ,
316+ amount = 1000 ,
317+ decimals = 3 ,
318+ asset_symbol = "HIVE" ,
319+ memo = "kktest" ,
320+ )
321+ kw .update (overrides )
322+ return kw
323+
324+ def _assert_sign_tx_fails (self , message_fragment , ** overrides ):
325+ from keepkeylib .client import CallException
326+ with self .assertRaises (CallException ) as ctx :
327+ hive .sign_tx (self .client , ** self ._transfer_kwargs (** overrides ))
328+ self .assertIn (message_fragment , str (ctx .exception ))
329+
330+ def test_hive_sign_transfer_rejects_foreign_path (self ):
331+ """A path outside SLIP-0048 (e.g. BIP-44 BTC) must be rejected before
332+ signing — a compromised host cannot obtain a Hive signature with a
333+ key from another coin's derivation tree."""
334+ self .requires_firmware ("7.15.0" )
335+ self .requires_message ("HiveSignTx" )
336+ self .setup_mnemonic_nopin_nopassphrase ()
337+ self ._assert_sign_tx_fails (
338+ "Invalid Hive SLIP-0048 path" ,
339+ address_n = parse_path ("m/44'/0'/0'/0/0" ),
340+ )
341+
342+ def test_hive_sign_transfer_rejects_wrong_network (self ):
343+ """Wrong SLIP-0048 network index (registry 3054' instead of the
344+ de-facto 13') must be rejected — keys must be Ledger-compatible."""
345+ self .requires_firmware ("7.15.0" )
346+ self .requires_message ("HiveSignTx" )
347+ self .setup_mnemonic_nopin_nopassphrase ()
348+ h = 0x80000000
349+ self ._assert_sign_tx_fails (
350+ "Invalid Hive SLIP-0048 path" ,
351+ address_n = [h + 48 , h + 3054 , h + ROLE_ACTIVE , h , h ],
352+ )
353+
354+ def test_hive_sign_transfer_rejects_wrong_role (self ):
355+ """Role index outside {owner, active, memo, posting} must be rejected."""
356+ self .requires_firmware ("7.15.0" )
357+ self .requires_message ("HiveSignTx" )
358+ self .setup_mnemonic_nopin_nopassphrase ()
359+ h = 0x80000000
360+ self ._assert_sign_tx_fails (
361+ "Invalid Hive SLIP-0048 path" ,
362+ address_n = [h + 48 , h + 13 , h + 2 , h , h ], # role 2' is unassigned
363+ )
364+
365+ def test_hive_sign_transfer_rejects_long_memo (self ):
366+ """Memo over the 440-byte serialization limit must fail with a
367+ specific error, not a generic signing failure."""
368+ self .requires_firmware ("7.15.0" )
369+ self .requires_message ("HiveSignTx" )
370+ self .setup_mnemonic_nopin_nopassphrase ()
371+ self ._assert_sign_tx_fails ("memo too long" , memo = "x" * 441 )
372+
373+ def test_hive_sign_transfer_max_memo_ok (self ):
374+ """A memo of exactly 440 bytes still signs (boundary check)."""
375+ self .requires_firmware ("7.15.0" )
376+ self .requires_message ("HiveSignTx" )
377+ self .setup_mnemonic_nopin_nopassphrase ()
378+ active = hive .get_public_key (self .client , hive_path (ROLE_ACTIVE ), show_display = False )
379+ resp = hive .sign_tx (self .client , ** self ._transfer_kwargs (memo = "x" * 440 ))
380+ self .assertEqual (len (resp .signature ), 65 )
381+ self .assertEqual (recover_compressed (resp .serialized_tx , resp .signature ), active .raw_public_key )
382+
383+
306384if __name__ == "__main__" :
307385 unittest .main ()
0 commit comments