5252
5353class TestEip712StreamHelpers (unittest .TestCase ):
5454
55+ def test_review_identifiers_are_exact_and_unambiguous (self ):
56+ doc = {
57+ 'types' : {
58+ 'Permit' : [
59+ {'name' : 'value' , 'type' : 'uint256' },
60+ {'name' : 'value' , 'type' : 'uint256' },
61+ ],
62+ },
63+ }
64+ with self .assertRaises (es .Eip712Error ) as duplicate :
65+ es .struct_members (doc , 'Permit' )
66+ self .assertIn ('Duplicate' , str (duplicate .exception ))
67+
68+ doc ['types' ]['Permit' ][1 ]['name' ] = 'identifier_that_would_be_truncated'
69+ with self .assertRaises (es .Eip712Error ) as overlong :
70+ es .struct_members (doc , 'Permit' )
71+ self .assertIn ('canonical EIP-712 identifier' , str (overlong .exception ))
72+
73+ doc ['types' ]['Permit' ][1 ]['name' ] = 'amount%08x'
74+ with self .assertRaises (es .Eip712Error ) as malformed :
75+ es .struct_members (doc , 'Permit' )
76+ self .assertIn ('canonical EIP-712 identifier' , str (malformed .exception ))
77+
5578 def test_multidimensional_arrays_are_walked_outermost_first (self ):
5679 doc = {
5780 'types' : {
@@ -139,11 +162,14 @@ def _walk(self, doc, max_steps=400):
139162
140163 def setUp (self ):
141164 super (TestMsgEip712Streaming , self ).setUp ()
142- self .requires_firmware ("7.15 .0" )
165+ self .requires_firmware ("7.16 .0" )
143166 self .requires_fullFeature ()
144167 self .requires_structured_eip712 ()
145168 self .setup_mnemonic_nopin_nopassphrase ()
146- self .client .apply_policy ('AdvancedMode' , 1 )
169+ # The device-driven stream validates, displays and hashes the same
170+ # bytes. It is not the blind precomputed-hash endpoint and must work
171+ # with Advanced Mode disabled.
172+ self .client .apply_policy ('AdvancedMode' , 0 )
147173 # The report entries describe typed-data fields, not the policy prompt.
148174 self .client .reset_screenshots ()
149175
@@ -186,6 +212,61 @@ def test_array_of_structs_walks(self):
186212 self .assertIsInstance (resp , eth .EthereumTypedDataSignature )
187213 self .assertEqual (len (resp .signature ), 65 )
188214
215+ def test_permit2_batch_walks_realistic_nested_array (self ):
216+ """The production Permit2 Batch shape, including trailing root fields.
217+
218+ The smaller Basket fixture proves the array primitive, but does not
219+ exercise a multi-field child struct followed by more members on the
220+ parent. That is the shape Uniswap and swap providers actually send.
221+ """
222+ doc = {
223+ "types" : {
224+ "EIP712Domain" : [
225+ {"name" : "name" , "type" : "string" },
226+ {"name" : "chainId" , "type" : "uint256" },
227+ {"name" : "verifyingContract" , "type" : "address" },
228+ ],
229+ "PermitDetails" : [
230+ {"name" : "token" , "type" : "address" },
231+ {"name" : "amount" , "type" : "uint160" },
232+ {"name" : "expiration" , "type" : "uint48" },
233+ {"name" : "nonce" , "type" : "uint48" },
234+ ],
235+ "PermitBatch" : [
236+ {"name" : "details" , "type" : "PermitDetails[]" },
237+ {"name" : "spender" , "type" : "address" },
238+ {"name" : "sigDeadline" , "type" : "uint256" },
239+ ],
240+ },
241+ "primaryType" : "PermitBatch" ,
242+ "domain" : {
243+ "name" : "Permit2" ,
244+ "chainId" : 1 ,
245+ "verifyingContract" : "0x000000000022D473030F116dDEE9F6B43aC78BA3" ,
246+ },
247+ "message" : {
248+ "details" : [
249+ {
250+ "token" : "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" ,
251+ "amount" : "250000000" ,
252+ "expiration" : "1893456000" ,
253+ "nonce" : "1" ,
254+ },
255+ {
256+ "token" : "0x6B175474E89094C44Da98b954EedeAC495271d0F" ,
257+ "amount" : "500000000000000000000" ,
258+ "expiration" : "1893456000" ,
259+ "nonce" : "2" ,
260+ },
261+ ],
262+ "spender" : "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD" ,
263+ "sigDeadline" : "1893456000" ,
264+ },
265+ }
266+ resp = self ._walk (doc )
267+ self .assertIsInstance (resp , eth .EthereumTypedDataSignature )
268+ self .assertEqual (len (resp .signature ), 65 )
269+
189270 def test_fixed_array_length_must_match_the_declared_size (self ):
190271 """A declared dimension is part of the type string and so of typeHash.
191272
@@ -207,17 +288,13 @@ def test_fixed_array_length_must_match_the_declared_size(self):
207288 self ._walk (doc )
208289 self .assertIn ('declares 2 elements' , str (ctx .exception ))
209290
210- def test_advanced_mode_gates_the_endpoint (self ):
211- """New parser surface reachable from a website stays behind the gate
212- until there is hardware evidence for it."""
291+ def test_advanced_mode_is_not_required_for_structured_review (self ):
292+ """Exact device-driven review is available with blind signing off."""
213293 self .client .apply_policy ('AdvancedMode' , 0 )
214- msg = eth .EthereumSignTypedData ()
215- for n in PATH :
216- msg .address_n .append (n )
217- msg .primary_type = 'Mail'
218- resp = self .client .call_raw (msg )
219- self .assertIsInstance (resp , proto .Failure )
220- self .assertIn ('AdvancedMode' , resp .message )
294+ resp = self ._walk (SPEC_MAIL )
295+ self .assertIsInstance (resp , eth .EthereumTypedDataSignature )
296+ self .assertEqual (resp .domain_separator_hash .hex (), SPEC_DOMAIN_SEPARATOR )
297+ self .assertEqual (resp .message_hash .hex (), SPEC_MESSAGE_HASH )
221298
222299
223300if __name__ == '__main__' :
0 commit comments