rust_x402/crypto/
eip712.rs1use crate::{Result, X402Error};
4use ethereum_types::{Address, H256, U256};
5use serde_json::json;
6use std::str::FromStr;
7
8#[derive(Debug, Clone)]
10pub struct Domain {
11 pub name: String,
12 pub version: String,
13 pub chain_id: u64,
14 pub verifying_contract: Address,
15}
16
17#[derive(Debug, Clone)]
19pub struct TypedData {
20 pub domain: Domain,
21 pub primary_type: String,
22 pub types: serde_json::Value,
23 pub message: serde_json::Value,
24}
25
26pub fn create_transfer_with_authorization_hash(
28 domain: &Domain,
29 from: Address,
30 to: Address,
31 value: U256,
32 valid_after: U256,
33 valid_before: U256,
34 nonce: H256,
35) -> Result<H256> {
36 let types = json!({
37 "EIP712Domain": [
38 {"name": "name", "type": "string"},
39 {"name": "version", "type": "string"},
40 {"name": "chainId", "type": "uint256"},
41 {"name": "verifyingContract", "type": "address"}
42 ],
43 "TransferWithAuthorization": [
44 {"name": "from", "type": "address"},
45 {"name": "to", "type": "address"},
46 {"name": "value", "type": "uint256"},
47 {"name": "validAfter", "type": "uint256"},
48 {"name": "validBefore", "type": "uint256"},
49 {"name": "nonce", "type": "bytes32"}
50 ]
51 });
52
53 let message = json!({
54 "from": format!("{:?}", from),
55 "to": format!("{:?}", to),
56 "value": format!("0x{:x}", value),
57 "validAfter": format!("0x{:x}", valid_after),
58 "validBefore": format!("0x{:x}", valid_before),
59 "nonce": format!("{:?}", nonce)
60 });
61
62 let typed_data = TypedData {
63 domain: domain.clone(),
64 primary_type: "TransferWithAuthorization".to_string(),
65 types,
66 message,
67 };
68
69 hash_typed_data(&typed_data)
70}
71
72pub fn hash_typed_data(typed_data: &TypedData) -> Result<H256> {
74 let domain_separator = hash_domain(&typed_data.domain)?;
77 let struct_hash = hash_struct(
78 &typed_data.primary_type,
79 &typed_data.types,
80 &typed_data.message,
81 )?;
82
83 let mut data = Vec::new();
85 data.extend_from_slice(&[0x19, 0x01]); data.extend_from_slice(domain_separator.as_bytes());
87 data.extend_from_slice(struct_hash.as_bytes());
88
89 Ok(H256::from_slice(&keccak256(&data)))
90}
91
92fn hash_domain(domain: &Domain) -> Result<H256> {
94 let domain_type_hash = keccak256(
95 b"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)",
96 );
97
98 let name_hash = keccak256(domain.name.as_bytes());
99 let version_hash = keccak256(domain.version.as_bytes());
100 let chain_id_hash = keccak256(&domain.chain_id.to_be_bytes());
101 let verifying_contract_hash = keccak256(domain.verifying_contract.as_bytes());
102
103 let mut data = Vec::new();
104 data.extend_from_slice(&domain_type_hash);
105 data.extend_from_slice(&name_hash);
106 data.extend_from_slice(&version_hash);
107 data.extend_from_slice(&chain_id_hash);
108 data.extend_from_slice(&verifying_contract_hash);
109
110 Ok(H256::from_slice(&keccak256(&data)))
111}
112
113fn hash_struct(
115 primary_type: &str,
116 _types: &serde_json::Value,
117 message: &serde_json::Value,
118) -> Result<H256> {
119 let type_hash = keccak256(
123 format!("{}(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)", primary_type)
124 .as_bytes()
125 );
126
127 let encoded_message = encode_message_fields(message)?;
129 let message_hash = keccak256(&encoded_message);
130
131 let mut data = Vec::new();
133 data.extend_from_slice(&type_hash);
134 data.extend_from_slice(&message_hash);
135
136 Ok(H256::from_slice(&keccak256(&data)))
137}
138
139fn encode_message_fields(message: &serde_json::Value) -> Result<Vec<u8>> {
141 let mut encoded = Vec::new();
143
144 if let Some(from) = message.get("from") {
146 if let Some(addr_str) = from.as_str() {
147 let addr = Address::from_str(addr_str)
148 .map_err(|_| X402Error::invalid_authorization("Invalid from address"))?;
149 let mut padded = [0u8; 32];
150 padded[12..32].copy_from_slice(addr.as_bytes());
151 encoded.extend_from_slice(&padded);
152 }
153 }
154
155 if let Some(to) = message.get("to") {
157 if let Some(addr_str) = to.as_str() {
158 let addr = Address::from_str(addr_str)
159 .map_err(|_| X402Error::invalid_authorization("Invalid to address"))?;
160 let mut padded = [0u8; 32];
161 padded[12..32].copy_from_slice(addr.as_bytes());
162 encoded.extend_from_slice(&padded);
163 }
164 }
165
166 if let Some(value) = message.get("value") {
168 if let Some(value_str) = value.as_str() {
169 let value_hex = value_str.trim_start_matches("0x");
170 let value_bytes = hex::decode(value_hex)
171 .map_err(|_| X402Error::invalid_authorization("Invalid value format"))?;
172 let mut padded = [0u8; 32];
173 let start = 32 - value_bytes.len();
174 padded[start..].copy_from_slice(&value_bytes);
175 encoded.extend_from_slice(&padded);
176 }
177 }
178
179 if let Some(valid_after) = message.get("validAfter") {
181 if let Some(valid_after_str) = valid_after.as_str() {
182 let valid_after_hex = valid_after_str.trim_start_matches("0x");
183 let valid_after_bytes = hex::decode(valid_after_hex)
184 .map_err(|_| X402Error::invalid_authorization("Invalid validAfter format"))?;
185 let mut padded = [0u8; 32];
186 let start = 32 - valid_after_bytes.len();
187 padded[start..].copy_from_slice(&valid_after_bytes);
188 encoded.extend_from_slice(&padded);
189 }
190 }
191
192 if let Some(valid_before) = message.get("validBefore") {
194 if let Some(valid_before_str) = valid_before.as_str() {
195 let valid_before_hex = valid_before_str.trim_start_matches("0x");
196 let valid_before_bytes = hex::decode(valid_before_hex)
197 .map_err(|_| X402Error::invalid_authorization("Invalid validBefore format"))?;
198 let mut padded = [0u8; 32];
199 let start = 32 - valid_before_bytes.len();
200 padded[start..].copy_from_slice(&valid_before_bytes);
201 encoded.extend_from_slice(&padded);
202 }
203 }
204
205 if let Some(nonce) = message.get("nonce") {
207 if let Some(nonce_str) = nonce.as_str() {
208 let nonce_hex = nonce_str.trim_start_matches("0x");
209 let nonce_bytes = hex::decode(nonce_hex)
210 .map_err(|_| X402Error::invalid_authorization("Invalid nonce format"))?;
211 if nonce_bytes.len() != 32 {
212 return Err(X402Error::invalid_authorization("Nonce must be 32 bytes"));
213 }
214 encoded.extend_from_slice(&nonce_bytes);
215 }
216 }
217
218 Ok(encoded)
219}
220
221pub fn keccak256(data: &[u8]) -> [u8; 32] {
223 use sha3::{Digest, Keccak256};
224 Keccak256::digest(data).into()
225}
226
227pub fn sha3_256(data: &[u8]) -> [u8; 32] {
233 use sha3::{Digest, Sha3_256};
234 Sha3_256::digest(data).into()
235}