Skip to content

Commit a0f59ba

Browse files
BitHighlanderclaude
andcommitted
fix: Zcash transparent input loop + report duplicate key collision
1. zcash_sign_pczt(): Add Phase 3 transparent input signing loop. After Orchard action-ack loop, device may send ZcashTransparentSig requests for shielding transactions. New transparent_inputs parameter feeds inputs back. Without this, any transparent-to-shielded tx would throw "Unexpected response type". 2. generate-zoo-report.py: Key JUnit results by classname.name instead of bare test name. Prevents last-wins collision when multiple test modules define test_ping, test_sign, etc. Lookup falls back to bare name for backward compatibility. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f687cac commit a0f59ba

2 files changed

Lines changed: 35 additions & 9 deletions

File tree

keepkeylib/client.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1625,11 +1625,13 @@ def zcash_sign_pczt(self, address_n, actions, account=None,
16251625
header_digest=None, transparent_digest=None,
16261626
sapling_digest=None, orchard_digest=None,
16271627
orchard_flags=None, orchard_value_balance=None,
1628-
orchard_anchor=None):
1628+
orchard_anchor=None, transparent_inputs=None):
16291629
"""Sign a Zcash Orchard shielded transaction via PCZT protocol.
16301630
1631-
Sends ZcashSignPCZT, then loops on ZcashPCZTActionAck feeding
1632-
actions one at a time, until the device returns ZcashSignedPCZT.
1631+
Phase 2: Sends ZcashSignPCZT, then loops on ZcashPCZTActionAck
1632+
feeding Orchard actions one at a time.
1633+
Phase 3: If transparent_inputs provided, handles ZcashTransparentSig
1634+
loop for transparent-to-shielded (shielding) transactions.
16331635
16341636
Args:
16351637
address_n: ZIP-32 derivation path [32', 133', account']
@@ -1682,7 +1684,7 @@ def zcash_sign_pczt(self, address_n, actions, account=None,
16821684

16831685
resp = self.call(zcash_proto.ZcashSignPCZT(**kwargs))
16841686

1685-
# Ack loop: device asks for actions one at a time
1687+
# Phase 2: Orchard action-ack loop — device asks for actions one at a time
16861688
while isinstance(resp, zcash_proto.ZcashPCZTActionAck):
16871689
idx = resp.next_index
16881690
if idx >= n_actions:
@@ -1692,6 +1694,21 @@ def zcash_sign_pczt(self, address_n, actions, account=None,
16921694
action = actions[idx]
16931695
resp = self.call(zcash_proto.ZcashPCZTAction(index=idx, **action))
16941696

1697+
# Phase 3: Transparent input signing — device sends back signatures
1698+
# and may request transparent inputs for shielding transactions
1699+
transparent_sigs = []
1700+
while isinstance(resp, zcash_proto.ZcashTransparentSig):
1701+
transparent_sigs.append(resp)
1702+
if not transparent_inputs:
1703+
raise Exception(
1704+
"Device sent ZcashTransparentSig but no transparent_inputs provided")
1705+
if resp.input_index >= len(transparent_inputs):
1706+
raise Exception(
1707+
"Device requested transparent input %d but only %d provided"
1708+
% (resp.input_index, len(transparent_inputs)))
1709+
inp = transparent_inputs[resp.input_index]
1710+
resp = self.call(zcash_proto.ZcashTransparentInput(**inp))
1711+
16951712
if isinstance(resp, proto.Failure):
16961713
raise Exception("Zcash signing failed: %s" % resp.message)
16971714

scripts/generate-zoo-report.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,18 +111,20 @@ def parse_junit(junit_path):
111111
tree = ET.parse(junit_path)
112112
results = {}
113113
for tc in tree.iter('testcase'):
114+
classname = tc.get('classname', '')
114115
name = tc.get('name', '')
116+
key = '%s.%s' % (classname, name) if classname else name
115117
failure = tc.find('failure')
116118
error = tc.find('error')
117119
skip = tc.find('skipped')
118120
if failure is not None:
119-
results[name] = 'FAIL'
121+
results[key] = 'FAIL'
120122
elif error is not None:
121-
results[name] = 'ERROR'
123+
results[key] = 'ERROR'
122124
elif skip is not None:
123-
results[name] = 'SKIP'
125+
results[key] = 'SKIP'
124126
else:
125-
results[name] = 'PASS'
127+
results[key] = 'PASS'
126128
return results
127129

128130

@@ -276,7 +278,14 @@ def generate_html(screenshots, junit_results, output_path):
276278
for test_name, pngs in sorted(tests.items()):
277279
test_counter[letter] += 1
278280
idx = f"{letter}{test_counter[letter]}"
279-
result = junit_results.get(test_name, 'UNKNOWN')
281+
# Try classname.name first, fall back to bare name
282+
result = 'UNKNOWN'
283+
for key, val in junit_results.items():
284+
if key.endswith('.' + test_name):
285+
result = val
286+
break
287+
else:
288+
result = junit_results.get(test_name, 'UNKNOWN')
280289
status_class = 'pass' if result == 'PASS' else 'fail' if result in ('FAIL', 'ERROR') else ''
281290
badge_class = 'pass' if result == 'PASS' else 'fail' if result in ('FAIL', 'ERROR') else 'skip'
282291

0 commit comments

Comments
 (0)