Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions tests/fixtures/base-thorchain-real-deposits.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[
{
"txid": "0xd567902e8e057283c10c41cc5f95cc236d60eaea39834295a53851dee231d808",
"chain_id": 8453,
"to": "0x00dc6100103bc402d490aee3f9a5560cbd91f1d4",
"value": 41276400000000000,
"nonce": 49,
"gas_price": 50000000,
"gas_limit": 200000,
"data": "0x44bc937b0000000000000000000000004d39d5c55cfeb48f44d680fcac20493635dafdee00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000092a4aac79b600000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000006acd804100000000000000000000000000000000000000000000000000000000000000643d3a54524f4e2e555344542d5452374e48514a454b5158475443493851385a5934504c384f54535a474a4c4a36543a544d6f554a3245715645536763614435654a647670686b6b6e384b337563586454703a393332313030343337393a6b6565703a333000000000000000000000000000000000000000000000000000000000",
"memo": "=:TRON.USDT-TR7NHQJEKQXGTCI8Q8ZY4PL8OTSZGJLJ6T:TMoUJ2EqVESgcaD5eJdvphkkn8K3ucXdTp:9321004379:keep:30",
"memo_length": 100,
"calldata_length": 324,
"vault": "0x4d39d5c55cfeb48f44d680fcac20493635dafdee",
"expiry": 1791852609
},
{
"txid": "0x3492f35c2b19554d761dd342b8f2bc5d07f7dfac3a6118893c82d86aacd0e856",
"chain_id": 8453,
"to": "0x00dc6100103bc402d490aee3f9a5560cbd91f1d4",
"value": 8255225600000000,
"nonce": 50,
"gas_price": 50000000,
"gas_limit": 200000,
"data": "0x44bc937b0000000000000000000000004d39d5c55cfeb48f44d680fcac20493635dafdee0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001d54157d6dc00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000006acd810400000000000000000000000000000000000000000000000000000000000000403d3a54524f4e2e5452583a544d6f554a3245715645536763614435654a647670686b6b6e384b337563586454703a353734303833303833333a6b6565703a3330",
"memo": "=:TRON.TRX:TMoUJ2EqVESgcaD5eJdvphkkn8K3ucXdTp:5740830833:keep:30",
"memo_length": 64,
"calldata_length": 260,
"vault": "0x4d39d5c55cfeb48f44d680fcac20493635dafdee",
"expiry": 1791852804
}
]
50 changes: 50 additions & 0 deletions tests/test_msg_ethereum_thorchain_deposit.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,5 +287,55 @@ def test_deposit_new_routers_wrong_chain_blocked(self):
data=data,
)

def test_base_real_deposits_without_advanced_mode(self):
"""Replay the two Base swaps with their exact calldata and recover
the emulator's signer; no AdvancedMode or metadata is supplied."""
self.requires_fullFeature()
self.requires_firmware("7.15.0")
self.requires_release_capability('thor-deposit-review')
self.setup_mnemonic_allallall()

import json
from pathlib import Path
from keepkeylib.signed_metadata import eth_sighash_legacy, keccak256
from keepkeylib.client import CallException
from ecdsa import VerifyingKey, SECP256k1, util

self.client.init_device()
advanced = [p for p in self.client.features.policies
if p.policy_name == "AdvancedMode"]
self.assertTrue(advanced, "AdvancedMode policy must be reported")
self.assertFalse(advanced[0].enabled)
vectors = json.loads((Path(__file__).parent / "fixtures" /
"base-thorchain-real-deposits.json").read_text())
n = parse_path("m/44'/60'/0'/0/0")
me = self.client.ethereum_get_address(n)
for tx in vectors:
with self.subTest(txid=tx["txid"]):
data = bytes.fromhex(tx["data"][2:])
to = bytes.fromhex(tx["to"][2:])
sig_v, sig_r, sig_s = self.client.ethereum_sign_tx(
n=n, nonce=tx["nonce"], gas_price=tx["gas_price"],
gas_limit=tx["gas_limit"], to=to, value=tx["value"],
chain_id=tx["chain_id"], data=data,
)
self.assertIn(sig_v, [16941, 16942])
digest = eth_sighash_legacy(
tx["nonce"], tx["gas_price"], tx["gas_limit"], to,
tx["value"], data, tx["chain_id"])
keys = VerifyingKey.from_public_key_recovery_with_digest(
sig_r + sig_s, digest, SECP256k1, hashfunc=None,
sigdecode=util.sigdecode_string)
self.assertEqual(keccak256(keys[sig_v - 16941].to_string())[-20:], me)
# Avalanche shares the router identity; it is a valid pin.
for dest, chain in ((b"\x11" * 20, 8453), (to, 1), (to, 56),
(to, 42161)):
with self.assertRaises(CallException):
self.client.ethereum_sign_tx(
n=n, nonce=tx["nonce"], gas_price=tx["gas_price"],
gas_limit=tx["gas_limit"], to=dest, value=tx["value"],
chain_id=chain, data=data,
)

if __name__ == "__main__":
unittest.main()
15 changes: 12 additions & 3 deletions tests/test_msg_zcash_display_address.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import common

from keepkeylib import messages_zcash_pb2 as zcash_proto
from keepkeylib import types_pb2 as proto_types
from keepkeylib.client import CallException
from keepkeylib.tools import parse_path

# Hardened offset
Expand Down Expand Up @@ -62,15 +64,22 @@ def test_zcash_display_address_bad_path_rejected(self):
is rejected with a SyntaxError (no silent wrong-account derivation)."""
self.setup_mnemonic_allallall()

import pytest
from keepkeylib.client import CallException
# Without account=, the path itself selects the approved account.
valid = self.client.zcash_display_address(
address_n=[H + 32, H + 133, H + 0])
explicit = self.client.zcash_display_address(account=0)
self.assertIsInstance(valid, zcash_proto.ZcashAddress)
self.assertEqual(valid.address, explicit.address)

with pytest.raises(CallException):
with self.assertRaises(CallException) as caught:
self.client.call(
zcash_proto.ZcashDisplayAddress(
address_n=[H + 44, H + 133, H + 0], # wrong purpose (44')
)
)
self.assertEqual(caught.exception.args[0], proto_types.Failure_SyntaxError)
self.assertEqual(caught.exception.args[1],
"Require account field or ZIP-32 path m/32'/133'/account'")


if __name__ == '__main__':
Expand Down
78 changes: 61 additions & 17 deletions tests/test_msg_zcash_seed_fingerprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@
import common

from keepkeylib import messages_zcash_pb2 as zcash_proto
from keepkeylib import types_pb2 as proto_types
from keepkeylib.client import CallException
from keepkeylib.zcash import calculate_seed_fingerprint

from test_msg_zcash_sign_pczt_device import (
PADDING_BUNDLE, bundle_digest, fabricated_action, sign_kwargs,
)

# Hardened offset
H = 0x80000000

Expand Down Expand Up @@ -117,25 +122,64 @@ def test_device_fingerprint_matches_python_helper(self):

# ── ZcashSignPCZT: through client.zcash_sign_pczt(...) ──────────────

def test_sign_pczt_helper_rejects_wrong_fingerprint(self):
"""Helper passes expected_seed_fingerprint through; wrong fp rejected
before any signing crypto runs."""
def _signing_request(self):
"""A complete wallet spend accepted by the current firmware."""
actions = [fabricated_action(v) for v in PADDING_BUNDLE]
actions[1]['is_spend'] = False
kwargs = sign_kwargs(actions, fee=10000, orchard_value_balance=10000,
total_amount=90000)
kwargs['orchard_digest'] = bundle_digest(
actions, False, 5, value_balance=10000)
return kwargs

def test_sign_pczt_helper_accepts_matching_fingerprint(self):
"""The same complete request signs with this seed's fingerprint."""
self.setup_mnemonic_allallall()
fvk = self.client.zcash_get_orchard_fvk(
address_n=[H + 32, H + 133, H], account=0)
result = self.client.zcash_sign_pczt(
**self._signing_request(),
expected_seed_fingerprint=fvk.seed_fingerprint,
)
self.assertIsInstance(result, zcash_proto.ZcashSignedPCZT)
self.assertEqual(len(result.signatures), 1)
self.assertEqual(len(result.signatures[0]), 64)

wrong_fp = b"\x01" * 32

with pytest.raises(CallException):
self.client.zcash_sign_pczt(
address_n=[H + 32, H + 133, H + 0],
actions=[{"is_spend": False}],
# Explicit dummy action passes the helper's contract preflight;
# the bad fingerprint is still rejected by the initial device call.
account=0,
total_amount=100000,
fee=10000,
branch_id=0x37519621,
expected_seed_fingerprint=wrong_fp,
)
def test_sign_pczt_helper_rejects_wrong_fingerprint(self):
"""A changed fingerprint alone causes the precise initial refusal."""
self.setup_mnemonic_allallall()
fvk = self.client.zcash_get_orchard_fvk(
address_n=[H + 32, H + 133, H], account=0)
wrong_fp = bytearray(fvk.seed_fingerprint)
wrong_fp[0] ^= 0x01

responses = []
original = self.client.call_raw

def capture(request):
response = original(request)
responses.append(response)
return response

self.client.call_raw = capture
try:
with pytest.raises(CallException) as caught:
self.client.zcash_sign_pczt(
**self._signing_request(),
expected_seed_fingerprint=bytes(wrong_fp),
)
finally:
self.client.call_raw = original

self.assertEqual(caught.value.args[0], proto_types.Failure_Other)
self.assertEqual(caught.value.args[1],
'Seed fingerprint mismatch — wrong device')
self.assertFalse(any(isinstance(response, (
zcash_proto.ZcashPCZTActionAck,
zcash_proto.ZcashSignedPCZT,
zcash_proto.ZcashTransparentSigned,
)) for response in responses),
'a mismatched seed must not start action streaming or release signatures')


if __name__ == '__main__':
Expand Down
Loading