Skip to content

Commit 7929ca4

Browse files
Fix signing of typed data
1 parent c54e822 commit 7929ca4

2 files changed

Lines changed: 47 additions & 29 deletions

File tree

virtuals_acp/contract_clients/contract_client.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -241,19 +241,20 @@ def get_asset_manager_address(self) -> str:
241241
raise ACPError("Not Supported")
242242

243243
def sign_typed_data(self, typed_data: dict[str, Any]) -> str:
244-
domain = typed_data["domain"]
245-
types = typed_data["types"]
246-
primary_type = typed_data["primaryType"]
247-
message = typed_data["message"]
244+
signable = encode_typed_data(full_message=typed_data)
245+
signed = self.account.sign_message(signable)
246+
raw_signature = signed.signature.hex()
247+
return f"0x{self._pack_1271_eoa_signature(raw_signature)}"
248248

249-
# encode_typed_data expects (domain_data, types, primary_type, message_data)
250-
# It handles EIP-712 hashing internally
251-
signable = encode_typed_data(
252-
domain,
253-
types,
254-
primary_type,
255-
message,
256-
)
249+
def _pack_1271_eoa_signature(self, validation_signature: str) -> str:
250+
if validation_signature.startswith("0x"):
251+
validation_signature = validation_signature[2:]
257252

258-
signed = self.account.sign_message(signable)
259-
return signed.signature.hex()
253+
prefix = b"\x00"
254+
entity_id_bytes = self.entity_id.to_bytes(4, "big")
255+
separator = b"\xff"
256+
eoa_type = b"\x00"
257+
sig_bytes = bytes.fromhex(validation_signature)
258+
259+
packed = prefix + entity_id_bytes + separator + eoa_type + sig_bytes
260+
return packed.hex()

virtuals_acp/contract_clients/contract_client_v2.py

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55

66
from eth_account import Account
77
from eth_account.messages import encode_typed_data
8+
from eth_utils.crypto import keccak
89
from web3 import Web3
910

1011
from virtuals_acp.abis.job_manager import JOB_MANAGER_ABI
1112
from virtuals_acp.abis.memo_manager import MEMO_MANAGER_ABI
1213
from virtuals_acp.alchemy import AlchemyAccountKit
1314
from virtuals_acp.configs.configs import ACPContractConfig, BASE_MAINNET_CONFIG_V2
15+
from virtuals_acp.constants import SINGLE_SIGNER_VALIDATION_MODULE_ADDRESS
1416
from virtuals_acp.contract_clients.base_contract_client import BaseAcpContractClient
1517
from virtuals_acp.exceptions import ACPError
1618
from virtuals_acp.models import AcpJobX402PaymentDetails, OffChainJob, OperationPayload, X402PayableRequest, X402PayableRequirements, X402Payment
@@ -202,19 +204,34 @@ def get_asset_manager_address(self) -> str:
202204
return self.memo_manager_contract.functions.assetManager().call()
203205

204206
def sign_typed_data(self, typed_data: dict[str, Any]) -> str:
205-
domain = typed_data["domain"]
206-
types = typed_data["types"]
207-
primary_type = typed_data["primaryType"]
208-
message = typed_data["message"]
209-
210-
# encode_typed_data expects (domain_data, types, primary_type, message_data)
211-
# It handles EIP-712 hashing internally
212-
signable = encode_typed_data(
213-
domain,
214-
types,
215-
primary_type,
216-
message,
217-
)
218-
207+
encoded = encode_typed_data(full_message=typed_data)
208+
typed_data_hash = keccak(b"\x19\x01" + encoded.header + encoded.body)
209+
210+
replay_safe_typed_data = {
211+
"domain": {
212+
"chainId": self.config.chain_id,
213+
"verifyingContract": SINGLE_SIGNER_VALIDATION_MODULE_ADDRESS,
214+
"salt": "0x" + "00" * 12 + self.agent_wallet_address[2:],
215+
},
216+
"types": {"ReplaySafeHash": [{"name": "hash", "type": "bytes32"}]},
217+
"message": {"hash": "0x" + typed_data_hash.hex()},
218+
"primaryType": "ReplaySafeHash",
219+
}
220+
221+
signable = encode_typed_data(full_message=replay_safe_typed_data)
219222
signed = self.account.sign_message(signable)
220-
return signed.signature.hex()
223+
raw_signature = signed.signature.hex()
224+
return f"0x{self._pack_1271_eoa_signature(raw_signature)}"
225+
226+
def _pack_1271_eoa_signature(self, validation_signature: str) -> str:
227+
if validation_signature.startswith("0x"):
228+
validation_signature = validation_signature[2:]
229+
230+
prefix = b"\x00"
231+
entity_id_bytes = self.entity_id.to_bytes(4, "big")
232+
separator = b"\xff"
233+
eoa_type = b"\x00"
234+
sig_bytes = bytes.fromhex(validation_signature)
235+
236+
packed = prefix + entity_id_bytes + separator + eoa_type + sig_bytes
237+
return packed.hex()

0 commit comments

Comments
 (0)