@@ -449,7 +449,18 @@ async fn whitelist_on_forwarder(
449449 println ! ( "Whitelisted VRF account {address_to_whitelist} on forwarder" ) ;
450450}
451451
452- /// Signs an OutsideExecutionV2 using SNIP-12 (same hash computation as the OZ SRC9 contract).
452+ /// Signs an OutsideExecutionV2 per SNIP-12 rev1 (off-chain typed structured data hashing).
453+ ///
454+ /// Implements the same message hash as OpenZeppelin's SRC9Component, which the on-chain
455+ /// account uses to verify the signature. The hash is:
456+ ///
457+ /// H("StarkNet Message", domain_hash, signer_address, outside_execution_hash)
458+ ///
459+ /// where each component is a Poseidon hash following the SNIP-12 specification:
460+ /// <https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md>
461+ ///
462+ /// NOTE: ideally we'd use `account_sdk::hash::MessageHashRev1` from controller-rs, but
463+ /// it currently pins an incompatible `starknet-types-core` version.
453464async fn sign_outside_execution_v2 (
454465 outside_execution : & OutsideExecutionV2 ,
455466 chain_id : Felt ,
@@ -459,26 +470,34 @@ async fn sign_outside_execution_v2(
459470 use starknet:: signers:: Signer ;
460471 use starknet_crypto:: { poseidon_hash_many, PoseidonHasher } ;
461472
473+ // sn_keccak("\"StarknetDomain\"(\"name\":\"shortstring\",\"version\":\"shortstring\",
474+ // \"chainId\":\"shortstring\",\"revision\":\"shortstring\")")
462475 const STARKNET_DOMAIN_TYPE_HASH : Felt = Felt :: from_hex_unchecked (
463476 "0x1ff2f602e42168014d405a94f75e8a93d640751d71d16311266e140d8b0a210" ,
464477 ) ;
478+ // sn_keccak("\"OutsideExecution\"(\"Caller\":\"ContractAddress\",\"Nonce\":\"felt\",
479+ // \"Execute After\":\"u128\",\"Execute Before\":\"u128\",\"Calls\":\"Call*\")
480+ // \"Call\"(\"To\":\"ContractAddress\",\"Selector\":\"selector\",
481+ // \"Calldata\":\"felt*\")")
465482 const OUTSIDE_EXECUTION_TYPE_HASH : Felt = Felt :: from_hex_unchecked (
466483 "0x312b56c05a7965066ddbda31c016d8d05afc305071c0ca3cdc2192c3c2f1f0f" ,
467484 ) ;
485+ // sn_keccak("\"Call\"(\"To\":\"ContractAddress\",\"Selector\":\"selector\",
486+ // \"Calldata\":\"felt*\")")
468487 const CALL_TYPE_HASH : Felt = Felt :: from_hex_unchecked (
469488 "0x3635c7f2a7ba93844c0d064e18e487f35ab90f7c39d00f186a781fc3f0c2ca9" ,
470489 ) ;
471490
472- // Domain hash
491+ // 1. StarknetDomain struct hash: H(type_hash, name, version, chain_id, revision)
473492 let domain_hash = poseidon_hash_many ( & [
474493 STARKNET_DOMAIN_TYPE_HASH ,
475494 Felt :: from_bytes_be_slice ( b"Account.execute_from_outside" ) ,
476- Felt :: TWO ,
495+ Felt :: TWO , // version
477496 chain_id,
478- Felt :: ONE ,
497+ Felt :: ONE , // revision
479498 ] ) ;
480499
481- // Hash each call
500+ // 2. Call struct hashes: H(type_hash, to, selector, H(calldata))
482501 let mut hashed_calls = Vec :: new ( ) ;
483502 for call in & outside_execution. calls {
484503 let mut h = PoseidonHasher :: new ( ) ;
@@ -489,7 +508,7 @@ async fn sign_outside_execution_v2(
489508 hashed_calls. push ( h. finalize ( ) ) ;
490509 }
491510
492- // Outside execution hash
511+ // 3. OutsideExecution struct hash: H(type_hash, caller, nonce, after, before, H(calls))
493512 let mut h = PoseidonHasher :: new ( ) ;
494513 h. update ( OUTSIDE_EXECUTION_TYPE_HASH ) ;
495514 h. update ( outside_execution. caller . into ( ) ) ;
@@ -499,7 +518,7 @@ async fn sign_outside_execution_v2(
499518 h. update ( poseidon_hash_many ( & hashed_calls) ) ;
500519 let outside_execution_hash = h. finalize ( ) ;
501520
502- // Final message hash
521+ // 4. Final SNIP-12 message hash: H("StarkNet Message", domain, signer, message)
503522 let mut h = PoseidonHasher :: new ( ) ;
504523 h. update ( Felt :: from_bytes_be_slice ( b"StarkNet Message" ) ) ;
505524 h. update ( domain_hash) ;
0 commit comments