diff --git a/device-protocol b/device-protocol index 6d0ae670..f2246ceb 160000 --- a/device-protocol +++ b/device-protocol @@ -1 +1 @@ -Subproject commit 6d0ae670e287a75338244fe82c4bef33a920a2ee +Subproject commit f2246cebea8f96fcd7ec2883588a784a60b430ae diff --git a/keepkeylib/client.py b/keepkeylib/client.py index 3d747be5..1860301f 100644 --- a/keepkeylib/client.py +++ b/keepkeylib/client.py @@ -1860,6 +1860,7 @@ def zcash_sign_pczt(self, address_n, actions, account=None, total_amount=0, fee=0, branch_id=0x37519621, header_digest=None, transparent_digest=None, sapling_digest=None, orchard_digest=None, + shielded_pool=None, ironwood_digest=None, orchard_flags=None, orchard_value_balance=None, orchard_anchor=None, tx_version=None, version_group_id=None, lock_time=None, @@ -1867,10 +1868,10 @@ def zcash_sign_pczt(self, address_n, actions, account=None, transparent_inputs=None, expected_seed_fingerprint=None, return_transparent_signatures=False): - """Sign a Zcash Orchard shielded transaction via PCZT protocol. + """Sign a Zcash Orchard-family shielded transaction via PCZT protocol. - Streams transparent outputs, then transparent inputs, then Orchard - actions in the exact order requested by firmware 7.15. Orchard + Streams transparent outputs, then transparent inputs, then shielded + actions in the exact order requested by firmware 7.15. Shielded signatures are compact: the response contains one signature for each action whose explicit ``is_spend`` value is true, in action order. @@ -1885,6 +1886,8 @@ def zcash_sign_pczt(self, address_n, actions, account=None, transparent_digest: 32-byte transparent digest sapling_digest: 32-byte sapling digest orchard_digest: 32-byte orchard digest + shielded_pool: ZcashShieldedPool value (Orchard by default) + ironwood_digest: 32-byte Ironwood digest for transaction v6 orchard_flags: bundle flags byte (enables digest verification) orchard_value_balance: signed i64 value balance orchard_anchor: 32-byte anchor @@ -1938,6 +1941,10 @@ def zcash_sign_pczt(self, address_n, actions, account=None, kwargs['sapling_digest'] = sapling_digest if orchard_digest is not None: kwargs['orchard_digest'] = orchard_digest + if shielded_pool is not None: + kwargs['shielded_pool'] = shielded_pool + if ironwood_digest is not None: + kwargs['ironwood_digest'] = ironwood_digest if orchard_flags is not None: kwargs['orchard_flags'] = orchard_flags if orchard_value_balance is not None: diff --git a/keepkeylib/messages_zcash_pb2.py b/keepkeylib/messages_zcash_pb2.py index 771e2ee0..953b2849 100644 --- a/keepkeylib/messages_zcash_pb2.py +++ b/keepkeylib/messages_zcash_pb2.py @@ -3,6 +3,7 @@ import sys _b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf.internal import enum_type_wrapper from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message from google.protobuf import reflection as _reflection @@ -19,9 +20,34 @@ name='messages-zcash.proto', package='', syntax='proto2', - serialized_pb=_b('\n\x14messages-zcash.proto\"\xf8\x03\n\rZcashSignPCZT\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x0f\n\x07\x61\x63\x63ount\x18\x02 \x01(\r\x12\x11\n\tpczt_data\x18\x03 \x01(\x0c\x12\x11\n\tn_actions\x18\x04 \x01(\r\x12\x14\n\x0ctotal_amount\x18\x05 \x01(\x04\x12\x0b\n\x03\x66\x65\x65\x18\x06 \x01(\x04\x12\x11\n\tbranch_id\x18\x07 \x01(\r\x12\x15\n\rheader_digest\x18\x08 \x01(\x0c\x12\x1a\n\x12transparent_digest\x18\t \x01(\x0c\x12\x16\n\x0esapling_digest\x18\n \x01(\x0c\x12\x16\n\x0eorchard_digest\x18\x0b \x01(\x0c\x12\x15\n\rorchard_flags\x18\x0c \x01(\r\x12\x1d\n\x15orchard_value_balance\x18\r \x01(\x03\x12\x16\n\x0eorchard_anchor\x18\x0e \x01(\x0c\x12\x12\n\ntx_version\x18\x0f \x01(\r\x12\x18\n\x10version_group_id\x18\x10 \x01(\r\x12\x11\n\tlock_time\x18\x11 \x01(\r\x12\x15\n\rexpiry_height\x18\x12 \x01(\r\x12\x1d\n\x15n_transparent_outputs\x18\x1d \x01(\r\x12\x1c\n\x14n_transparent_inputs\x18\x1e \x01(\r\x12!\n\x19\x65xpected_seed_fingerprint\x18\x1f \x01(\x0c\"\xa3\x02\n\x0fZcashPCZTAction\x12\r\n\x05index\x18\x01 \x01(\r\x12\r\n\x05\x61lpha\x18\x02 \x01(\x0c\x12\x0f\n\x07sighash\x18\x03 \x01(\x0c\x12\x0e\n\x06\x63v_net\x18\x04 \x01(\x0c\x12\r\n\x05value\x18\x05 \x01(\x04\x12\x10\n\x08is_spend\x18\x06 \x01(\x08\x12\x11\n\tnullifier\x18\x07 \x01(\x0c\x12\x0b\n\x03\x63mx\x18\x08 \x01(\x0c\x12\x0b\n\x03\x65pk\x18\t \x01(\x0c\x12\x13\n\x0b\x65nc_compact\x18\n \x01(\x0c\x12\x10\n\x08\x65nc_memo\x18\x0b \x01(\x0c\x12\x16\n\x0e\x65nc_noncompact\x18\x0c \x01(\x0c\x12\n\n\x02rk\x18\r \x01(\x0c\x12\x16\n\x0eout_ciphertext\x18\x0e \x01(\x0c\x12\x11\n\trecipient\x18\x0f \x01(\x0c\x12\r\n\x05rseed\x18\x10 \x01(\x0c\"(\n\x12ZcashPCZTActionAck\x12\x12\n\nnext_index\x18\x01 \x01(\r\"3\n\x0fZcashSignedPCZT\x12\x12\n\nsignatures\x18\x01 \x03(\x0c\x12\x0c\n\x04txid\x18\x02 \x01(\x0c\"N\n\x12ZcashGetOrchardFVK\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x0f\n\x07\x61\x63\x63ount\x18\x02 \x01(\r\x12\x14\n\x0cshow_display\x18\x03 \x01(\x08\"Q\n\x0fZcashOrchardFVK\x12\n\n\x02\x61k\x18\x01 \x01(\x0c\x12\n\n\x02nk\x18\x02 \x01(\x0c\x12\x0c\n\x04rivk\x18\x03 \x01(\x0c\x12\x18\n\x10seed_fingerprint\x18\x04 \x01(\x0c\"N\n\x16ZcashTransparentOutput\x12\r\n\x05index\x18\x01 \x02(\r\x12\x0e\n\x06\x61mount\x18\x02 \x01(\x04\x12\x15\n\rscript_pubkey\x18\x03 \x01(\x0c\"\xb0\x01\n\x15ZcashTransparentInput\x12\r\n\x05index\x18\x01 \x02(\r\x12\x0f\n\x07sighash\x18\x02 \x01(\x0c\x12\x11\n\taddress_n\x18\x03 \x03(\r\x12\x0e\n\x06\x61mount\x18\x04 \x01(\x04\x12\x14\n\x0cprevout_txid\x18\x05 \x01(\x0c\x12\x15\n\rprevout_index\x18\x06 \x01(\r\x12\x10\n\x08sequence\x18\x07 \x01(\r\x12\x15\n\rscript_pubkey\x18\x08 \x01(\x0c\"J\n\x13ZcashTransparentAck\x12\x19\n\x11next_output_index\x18\x01 \x01(\r\x12\x18\n\x10next_input_index\x18\x02 \x01(\r\",\n\x16ZcashTransparentSigned\x12\x12\n\nsignatures\x18\x01 \x03(\x0c\"\x8b\x01\n\x13ZcashDisplayAddress\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x0f\n\x07\x61\x63\x63ount\x18\x02 \x01(\r\x12!\n\x19\x65xpected_seed_fingerprint\x18\x07 \x01(\x0cJ\x04\x08\x03\x10\x04J\x04\x08\x04\x10\x05J\x04\x08\x05\x10\x06J\x04\x08\x06\x10\x07R\x07\x61\x64\x64ressR\x02\x61kR\x02nkR\x04rivk\"9\n\x0cZcashAddress\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\x18\n\x10seed_fingerprint\x18\x02 \x01(\x0c\x42\x31\n\x1a\x63om.keepkey.deviceprotocolB\x13KeepKeyMessageZcash') + serialized_pb=_b('\n\x14messages-zcash.proto\"\xd9\x04\n\rZcashSignPCZT\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x0f\n\x07\x61\x63\x63ount\x18\x02 \x01(\r\x12\x11\n\tpczt_data\x18\x03 \x01(\x0c\x12\x11\n\tn_actions\x18\x04 \x01(\r\x12\x14\n\x0ctotal_amount\x18\x05 \x01(\x04\x12\x0b\n\x03\x66\x65\x65\x18\x06 \x01(\x04\x12\x11\n\tbranch_id\x18\x07 \x01(\r\x12\x15\n\rheader_digest\x18\x08 \x01(\x0c\x12\x1a\n\x12transparent_digest\x18\t \x01(\x0c\x12\x16\n\x0esapling_digest\x18\n \x01(\x0c\x12\x16\n\x0eorchard_digest\x18\x0b \x01(\x0c\x12\x15\n\rorchard_flags\x18\x0c \x01(\r\x12\x1d\n\x15orchard_value_balance\x18\r \x01(\x03\x12\x16\n\x0eorchard_anchor\x18\x0e \x01(\x0c\x12\x12\n\ntx_version\x18\x0f \x01(\r\x12\x18\n\x10version_group_id\x18\x10 \x01(\r\x12\x11\n\tlock_time\x18\x11 \x01(\r\x12\x15\n\rexpiry_height\x18\x12 \x01(\r\x12\x46\n\rshielded_pool\x18\x13 \x01(\x0e\x32\x12.ZcashShieldedPool:\x1bZCASH_SHIELDED_POOL_ORCHARD\x12\x17\n\x0fironwood_digest\x18\x14 \x01(\x0c\x12\x1d\n\x15n_transparent_outputs\x18\x1d \x01(\r\x12\x1c\n\x14n_transparent_inputs\x18\x1e \x01(\r\x12!\n\x19\x65xpected_seed_fingerprint\x18\x1f \x01(\x0c\"\xa3\x02\n\x0fZcashPCZTAction\x12\r\n\x05index\x18\x01 \x01(\r\x12\r\n\x05\x61lpha\x18\x02 \x01(\x0c\x12\x0f\n\x07sighash\x18\x03 \x01(\x0c\x12\x0e\n\x06\x63v_net\x18\x04 \x01(\x0c\x12\r\n\x05value\x18\x05 \x01(\x04\x12\x10\n\x08is_spend\x18\x06 \x01(\x08\x12\x11\n\tnullifier\x18\x07 \x01(\x0c\x12\x0b\n\x03\x63mx\x18\x08 \x01(\x0c\x12\x0b\n\x03\x65pk\x18\t \x01(\x0c\x12\x13\n\x0b\x65nc_compact\x18\n \x01(\x0c\x12\x10\n\x08\x65nc_memo\x18\x0b \x01(\x0c\x12\x16\n\x0e\x65nc_noncompact\x18\x0c \x01(\x0c\x12\n\n\x02rk\x18\r \x01(\x0c\x12\x16\n\x0eout_ciphertext\x18\x0e \x01(\x0c\x12\x11\n\trecipient\x18\x0f \x01(\x0c\x12\r\n\x05rseed\x18\x10 \x01(\x0c\"(\n\x12ZcashPCZTActionAck\x12\x12\n\nnext_index\x18\x01 \x01(\r\"3\n\x0fZcashSignedPCZT\x12\x12\n\nsignatures\x18\x01 \x03(\x0c\x12\x0c\n\x04txid\x18\x02 \x01(\x0c\"N\n\x12ZcashGetOrchardFVK\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x0f\n\x07\x61\x63\x63ount\x18\x02 \x01(\r\x12\x14\n\x0cshow_display\x18\x03 \x01(\x08\"Q\n\x0fZcashOrchardFVK\x12\n\n\x02\x61k\x18\x01 \x01(\x0c\x12\n\n\x02nk\x18\x02 \x01(\x0c\x12\x0c\n\x04rivk\x18\x03 \x01(\x0c\x12\x18\n\x10seed_fingerprint\x18\x04 \x01(\x0c\"N\n\x16ZcashTransparentOutput\x12\r\n\x05index\x18\x01 \x02(\r\x12\x0e\n\x06\x61mount\x18\x02 \x01(\x04\x12\x15\n\rscript_pubkey\x18\x03 \x01(\x0c\"\xb0\x01\n\x15ZcashTransparentInput\x12\r\n\x05index\x18\x01 \x02(\r\x12\x0f\n\x07sighash\x18\x02 \x01(\x0c\x12\x11\n\taddress_n\x18\x03 \x03(\r\x12\x0e\n\x06\x61mount\x18\x04 \x01(\x04\x12\x14\n\x0cprevout_txid\x18\x05 \x01(\x0c\x12\x15\n\rprevout_index\x18\x06 \x01(\r\x12\x10\n\x08sequence\x18\x07 \x01(\r\x12\x15\n\rscript_pubkey\x18\x08 \x01(\x0c\"J\n\x13ZcashTransparentAck\x12\x19\n\x11next_output_index\x18\x01 \x01(\r\x12\x18\n\x10next_input_index\x18\x02 \x01(\r\",\n\x16ZcashTransparentSigned\x12\x12\n\nsignatures\x18\x01 \x03(\x0c\"\x8b\x01\n\x13ZcashDisplayAddress\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x0f\n\x07\x61\x63\x63ount\x18\x02 \x01(\r\x12!\n\x19\x65xpected_seed_fingerprint\x18\x07 \x01(\x0cJ\x04\x08\x03\x10\x04J\x04\x08\x04\x10\x05J\x04\x08\x05\x10\x06J\x04\x08\x06\x10\x07R\x07\x61\x64\x64ressR\x02\x61kR\x02nkR\x04rivk\"9\n\x0cZcashAddress\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\x18\n\x10seed_fingerprint\x18\x02 \x01(\x0c*V\n\x11ZcashShieldedPool\x12\x1f\n\x1bZCASH_SHIELDED_POOL_ORCHARD\x10\x00\x12 \n\x1cZCASH_SHIELDED_POOL_IRONWOOD\x10\x01\x42\x31\n\x1a\x63om.keepkey.deviceprotocolB\x13KeepKeyMessageZcash') ) +_ZCASHSHIELDEDPOOL = _descriptor.EnumDescriptor( + name='ZcashShieldedPool', + full_name='ZcashShieldedPool', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='ZCASH_SHIELDED_POOL_ORCHARD', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='ZCASH_SHIELDED_POOL_IRONWOOD', index=1, number=1, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=1762, + serialized_end=1848, +) +_sym_db.RegisterEnumDescriptor(_ZCASHSHIELDEDPOOL) + +ZcashShieldedPool = enum_type_wrapper.EnumTypeWrapper(_ZCASHSHIELDEDPOOL) +ZCASH_SHIELDED_POOL_ORCHARD = 0 +ZCASH_SHIELDED_POOL_IRONWOOD = 1 @@ -159,21 +185,35 @@ is_extension=False, extension_scope=None, options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='n_transparent_outputs', full_name='ZcashSignPCZT.n_transparent_outputs', index=18, + name='shielded_pool', full_name='ZcashSignPCZT.shielded_pool', index=18, + number=19, type=14, cpp_type=8, label=1, + has_default_value=True, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='ironwood_digest', full_name='ZcashSignPCZT.ironwood_digest', index=19, + number=20, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='n_transparent_outputs', full_name='ZcashSignPCZT.n_transparent_outputs', index=20, number=29, type=13, cpp_type=3, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='n_transparent_inputs', full_name='ZcashSignPCZT.n_transparent_inputs', index=19, + name='n_transparent_inputs', full_name='ZcashSignPCZT.n_transparent_inputs', index=21, number=30, type=13, cpp_type=3, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='expected_seed_fingerprint', full_name='ZcashSignPCZT.expected_seed_fingerprint', index=20, + name='expected_seed_fingerprint', full_name='ZcashSignPCZT.expected_seed_fingerprint', index=22, number=31, type=12, cpp_type=9, label=1, has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, @@ -192,7 +232,7 @@ oneofs=[ ], serialized_start=25, - serialized_end=529, + serialized_end=626, ) @@ -327,8 +367,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=532, - serialized_end=823, + serialized_start=629, + serialized_end=920, ) @@ -358,8 +398,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=825, - serialized_end=865, + serialized_start=922, + serialized_end=962, ) @@ -396,8 +436,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=867, - serialized_end=918, + serialized_start=964, + serialized_end=1015, ) @@ -441,8 +481,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=920, - serialized_end=998, + serialized_start=1017, + serialized_end=1095, ) @@ -493,8 +533,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1000, - serialized_end=1081, + serialized_start=1097, + serialized_end=1178, ) @@ -538,8 +578,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1083, - serialized_end=1161, + serialized_start=1180, + serialized_end=1258, ) @@ -618,8 +658,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1164, - serialized_end=1340, + serialized_start=1261, + serialized_end=1437, ) @@ -656,8 +696,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1342, - serialized_end=1416, + serialized_start=1439, + serialized_end=1513, ) @@ -687,8 +727,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1418, - serialized_end=1462, + serialized_start=1515, + serialized_end=1559, ) @@ -732,8 +772,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1465, - serialized_end=1604, + serialized_start=1562, + serialized_end=1701, ) @@ -770,10 +810,11 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1606, - serialized_end=1663, + serialized_start=1703, + serialized_end=1760, ) +_ZCASHSIGNPCZT.fields_by_name['shielded_pool'].enum_type = _ZCASHSHIELDEDPOOL DESCRIPTOR.message_types_by_name['ZcashSignPCZT'] = _ZCASHSIGNPCZT DESCRIPTOR.message_types_by_name['ZcashPCZTAction'] = _ZCASHPCZTACTION DESCRIPTOR.message_types_by_name['ZcashPCZTActionAck'] = _ZCASHPCZTACTIONACK @@ -786,6 +827,7 @@ DESCRIPTOR.message_types_by_name['ZcashTransparentSigned'] = _ZCASHTRANSPARENTSIGNED DESCRIPTOR.message_types_by_name['ZcashDisplayAddress'] = _ZCASHDISPLAYADDRESS DESCRIPTOR.message_types_by_name['ZcashAddress'] = _ZCASHADDRESS +DESCRIPTOR.enum_types_by_name['ZcashShieldedPool'] = _ZCASHSHIELDEDPOOL _sym_db.RegisterFileDescriptor(DESCRIPTOR) ZcashSignPCZT = _reflection.GeneratedProtocolMessageType('ZcashSignPCZT', (_message.Message,), dict( diff --git a/tests/test_msg_zcash_sign_pczt.py b/tests/test_msg_zcash_sign_pczt.py index 58cf2860..69ecae0c 100644 --- a/tests/test_msg_zcash_sign_pczt.py +++ b/tests/test_msg_zcash_sign_pczt.py @@ -73,7 +73,42 @@ def sign_kwargs(actions): } +def ironwood_sign_kwargs(actions): + kwargs = sign_kwargs(actions) + kwargs.update({ + 'branch_id': 0x37A5165B, + 'orchard_digest': b'\x14' * 32, + 'shielded_pool': zcash_proto.ZCASH_SHIELDED_POOL_IRONWOOD, + 'ironwood_digest': b'\x15' * 32, + 'orchard_value_balance': 0, + 'tx_version': 6, + 'version_group_id': 0xD884B698, + }) + return kwargs + + class TestZcashSignPCZTClient(unittest.TestCase): + def test_ironwood_v6_metadata_is_forwarded_exactly(self): + actions = [action(0, False)] + client = ScriptedClient([ + zcash_proto.ZcashPCZTActionAck(next_index=0), + zcash_proto.ZcashSignedPCZT(signatures=[]), + ]) + + signed = client.zcash_sign_pczt(**ironwood_sign_kwargs(actions)) + + self.assertEqual(list(signed.signatures), []) + request = client.sent[0] + self.assertEqual(request.branch_id, 0x37A5165B) + self.assertEqual(request.tx_version, 6) + self.assertEqual(request.version_group_id, 0xD884B698) + self.assertEqual( + request.shielded_pool, + zcash_proto.ZCASH_SHIELDED_POOL_IRONWOOD, + ) + self.assertEqual(request.orchard_digest, b'\x14' * 32) + self.assertEqual(request.ironwood_digest, b'\x15' * 32) + def test_all_dummy_shield_streams_outputs_inputs_and_no_orchard_sigs(self): actions = [action(0, False), action(1, False)] responses = [ @@ -125,6 +160,8 @@ def test_all_dummy_shield_streams_outputs_inputs_and_no_orchard_sigs(self): self.assertEqual(request.tx_version, 5) self.assertEqual(request.version_group_id, 0x26A7270A) self.assertFalse(request.HasField('sapling_digest')) + self.assertFalse(request.HasField('shielded_pool')) + self.assertFalse(request.HasField('ironwood_digest')) self.assertFalse(client.sent[3].is_spend) self.assertFalse(client.sent[4].is_spend) self.assertFalse(client.sent[2].HasField('sighash'))