Skip to content

Commit f3c2fac

Browse files
kariyclaude
andcommitted
docs(vrf): add SNIP-12 inline comments to outside execution signing
Document the type hashes, struct hash computation steps, and link to the SNIP-12 specification. Note the account_sdk dependency conflict that prevents reusing MessageHashRev1 directly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 522013a commit f3c2fac

File tree

1 file changed

+26
-7
lines changed

1 file changed

+26
-7
lines changed

tests/vrf/src/main.rs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
453464
async 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

Comments
 (0)