1use fuels::{
2 core::{
3 codec::calldata,
4 traits::Tokenizable,
5 },
6 crypto::Message,
7 types::{
8 Bits256,
9 ChainId,
10 Identity,
11 U256 as FuelU256,
12 },
13};
14
15use crate::{
16 trade_account::{
17 generate_session_signing_payload,
18 generate_signing_payload,
19 },
20 trade_account_deploy::{
21 CallContractArg,
22 CallContractArgs as TradeAccountCallContractArgs,
23 CallParams,
24 Domain,
25 EIP712Domain,
26 Encoding,
27 MultiCallContractArgs,
28 ParallelCallContractArgs,
29 ParallelMultiCallContractArgs,
30 ParallelRevokeArgs,
31 ParallelSessionArgs,
32 ParallelWithdrawArgs,
33 RevokeArgs,
34 SRC16Domain,
35 Secp256k1,
36 SessionArgs,
37 SetProxyTargetArgs,
38 Signature,
39 WithdrawArgs,
40 },
41};
42use ethers_core::{
43 types::{
44 H160,
45 RecoveryMessage,
46 Signature as EthSig,
47 U256,
48 U256 as Ethers256,
49 transaction::eip712::EIP712Domain as EVMDomain,
50 },
51 utils::keccak256,
52};
53
54const EVM_ADDRESS_PADDING_LENGTH: usize = 12;
55
56fn fuel_contract_id_to_evm_address(id: [u8; 32]) -> [u8; 20] {
59 let mut addr = [0u8; 20];
60 addr.copy_from_slice(&id[12..32]);
62 addr
63}
64
65pub trait DomainExt {
66 fn separator(&self) -> [u8; 32];
67 fn encode<T>(&self, struct_type: T) -> [u8; 32]
68 where
69 T: SRC16Encode;
70}
71
72impl DomainExt for Domain {
73 fn separator(&self) -> [u8; 32] {
74 match self {
75 Domain::EIP712Domain(d) => EVMDomain::from(d.clone()).separator(),
76 Domain::SRC16Domain(d) => d.separator(),
77 }
78 }
79
80 fn encode<T>(&self, struct_type: T) -> [u8; 32]
81 where
82 T: SRC16Encode,
83 {
84 let ds = self.separator();
85 let encoding = match self {
86 Domain::SRC16Domain(_) => Encoding::SRC16,
87 Domain::EIP712Domain(_) => Encoding::EIP712,
88 };
89 let sh = struct_type.struct_hash(encoding);
90
91 let mut preimage = Vec::with_capacity(2 + 32 + 32);
92 preimage.extend_from_slice(&[0x19, 0x01]);
93 preimage.extend_from_slice(ds.as_slice());
94 preimage.extend_from_slice(sh.as_slice());
95
96 keccak256(preimage)
97 }
98}
99
100impl SRC16Domain {
101 pub fn separator(&self) -> [u8; 32] {
103 let mut tokens: Vec<u8> = Vec::new();
104
105 tokens.extend_from_slice(src16_domain_type_hash(self).as_ref());
106
107 if self.name.is_some() {
108 tokens.extend_from_slice(keccak256(self.name.clone().unwrap()).as_slice());
109 }
110 if self.version.is_some() {
111 tokens.extend_from_slice(keccak256(self.version.clone().unwrap()).as_slice());
112 }
113
114 if let Some(chain_id) = self.chain_id {
115 tokens.extend_from_slice(fuel_u256_to_bytes(&chain_id).as_slice());
131 }
132
133 if let Some(verifying_contract) = self.verifying_contract {
134 tokens.extend_from_slice(verifying_contract.as_slice());
135 }
136
137 if let Some(salt) = self.salt {
138 tokens.extend_from_slice(salt.0.as_slice());
139 }
140
141 keccak256(tokens)
142 }
143}
144
145fn src16_domain_type_hash(domain: &SRC16Domain) -> [u8; 32] {
146 let mut fields = Vec::new();
147
148 if domain.name.is_some() {
150 fields.push("string name");
151 }
152 if domain.version.is_some() {
153 fields.push("string version");
154 }
155 if domain.chain_id.is_some() {
156 fields.push("u256 chain_id");
157 }
158 if domain.verifying_contract.is_some() {
159 fields.push("contractId verifying_contract");
160 }
161 if domain.salt.is_some() {
162 fields.push("b256 salt");
163 }
164
165 let type_string = format!("SRC16Domain({})", fields.join(","));
166
167 keccak256(type_string.as_bytes())
168}
169
170fn fuel_u256_to_bytes(value: &FuelU256) -> [u8; 32] {
171 let mut bytes = [0u8; 32];
172 value.to_big_endian(&mut bytes);
173 bytes
174}
175
176impl From<EIP712Domain> for EVMDomain {
177 fn from(domain: EIP712Domain) -> Self {
178 let chain_id = domain.chain_id.map(|id| Ethers256(id.0));
179 let verifying_contract = domain
180 .verifying_contract
181 .map(|contract| H160(fuel_contract_id_to_evm_address(contract.0)));
182 let salt = domain.salt.map(|s| s.0);
183
184 EVMDomain {
185 name: domain.name,
186 version: domain.version,
187 chain_id,
188 verifying_contract,
189 salt,
190 }
191 }
192}
193
194pub trait SRC16Encode {
195 #[allow(dead_code)]
196 fn type_hash(encoding: Encoding) -> [u8; 32];
197 fn struct_hash(self, encoding: Encoding) -> [u8; 32];
198}
199
200fn from_compact_or_standard(bytes: &[u8]) -> anyhow::Result<EthSig> {
201 match bytes.len() {
202 65 => Ok(EthSig::try_from(bytes)?),
203 64 => {
204 let r = U256::from_big_endian(&bytes[..32]);
205 let mut s_bytes = [0u8; 32];
206 s_bytes.copy_from_slice(&bytes[32..64]);
207
208 let y_parity = (s_bytes[0] >> 7) & 1;
209 s_bytes[0] &= 0x7F;
212
213 let s = U256::from_big_endian(&s_bytes);
214 let v = 27 + y_parity as u64;
216
217 Ok(EthSig { r, s, v })
218 }
219 n => anyhow::bail!("invalid length {n}, expected 64 or 65"),
220 }
221}
222
223pub trait SignatureCurve {
224 type Address;
225
226 fn verify_owner<T>(
228 &self,
229 nonce: u64,
230 chain_id: ChainId,
231 f_name: String,
232 args: Option<T>,
233 address: &Self::Address,
234 ) -> anyhow::Result<()>
235 where
236 T: Tokenizable;
237
238 fn verify_session<T>(
240 &self,
241 nonce: u64,
242 args: T,
243 address: &Self::Address,
244 ) -> anyhow::Result<()>
245 where
246 T: Tokenizable;
247
248 fn verify_typed<T>(
249 &self,
250 args: T,
251 domain: &Domain,
252 address: &Self::Address,
253 ) -> anyhow::Result<()>
254 where
255 T: SRC16Encode;
256}
257
258pub struct Secp256k1Address(pub fuels::types::Address);
259
260impl SignatureCurve for Secp256k1 {
261 type Address = Secp256k1Address;
262
263 fn verify_owner<T>(
264 &self,
265 nonce: u64,
266 chain_id: ChainId,
267 f_name: String,
268 args: Option<T>,
269 address: &Self::Address,
270 ) -> anyhow::Result<()>
271 where
272 T: Tokenizable,
273 {
274 let address = &address.0;
275
276 if address.starts_with([0u8; EVM_ADDRESS_PADDING_LENGTH].as_ref()) {
279 let evm_address = &address[EVM_ADDRESS_PADDING_LENGTH..];
280 let evm_message = if let Some(args_some) = args {
281 calldata!((nonce, *chain_id, f_name, args_some))?
282 } else {
283 calldata!((nonce, *chain_id, f_name))?
284 };
285 let evm_sig = from_compact_or_standard(&self.bits)?;
286 let recovered_address = evm_sig.recover(&evm_message[..])?;
287
288 if recovered_address.as_bytes() != evm_address {
289 anyhow::bail!("invalid evm address provided");
290 }
291
292 evm_sig.verify(evm_message, recovered_address)?;
293 } else {
294 let fuel_message = generate_signing_payload(nonce, *chain_id, f_name, args);
295 let fuels_sig = fuels::crypto::Signature::from_bytes(self.bits);
296 let recovered_pub_key = fuels_sig.recover(&fuel_message)?;
297
298 if *recovered_pub_key.hash() != address.as_ref() {
299 anyhow::bail!("invalid fuel address provided");
300 }
301
302 fuels_sig.verify(&recovered_pub_key, &fuel_message)?;
303 }
304
305 Ok(())
306 }
307
308 fn verify_session<T>(
309 &self,
310 nonce: u64,
311 args: T,
312 address: &Self::Address,
313 ) -> anyhow::Result<()>
314 where
315 T: Tokenizable,
316 {
317 let address = &address.0;
318 let fuel_message = generate_session_signing_payload(nonce, args);
319
320 let fuels_sig = fuels::crypto::Signature::from_bytes(self.bits);
321 let recovered_pub_key = fuels_sig.recover(&fuel_message)?;
322
323 if *recovered_pub_key.hash() != address.as_ref() {
324 anyhow::bail!("invalid fuel address provided");
325 }
326
327 fuels_sig.verify(&recovered_pub_key, &fuel_message)?;
328
329 Ok(())
330 }
331
332 fn verify_typed<T>(
333 &self,
334 args: T,
335 domain: &Domain,
336 address: &Self::Address,
337 ) -> anyhow::Result<()>
338 where
339 T: SRC16Encode,
340 {
341 let address = &address.0;
342
343 let message: Message = Message::from_bytes(domain.encode(args));
345
346 if address.starts_with([0u8; EVM_ADDRESS_PADDING_LENGTH].as_ref()) {
348 let evm_address = &address[EVM_ADDRESS_PADDING_LENGTH..];
350 let evm_sig = from_compact_or_standard(&self.bits)?;
351 let recovered_address =
352 evm_sig.recover(RecoveryMessage::Hash((*message).into()))?;
353
354 if recovered_address.as_bytes() != evm_address {
355 anyhow::bail!("invalid evm address provided");
356 }
357
358 evm_sig
359 .verify(RecoveryMessage::Hash((*message).into()), recovered_address)?;
360 } else {
361 let fuels_sig = fuels::crypto::Signature::from_bytes(self.bits);
363 let recovered_pub_key = fuels_sig.recover(&message)?;
364
365 if *recovered_pub_key.hash() != address.as_ref() {
366 anyhow::bail!("invalid fuel address provided");
367 }
368
369 fuels_sig.verify(&recovered_pub_key, &message)?;
370 }
371
372 Ok(())
373 }
374}
375
376pub enum Address {
377 Secp256k1(Secp256k1Address),
378 Secp256r1,
379 Ed25519,
380}
381
382impl From<fuels::types::Address> for Address {
383 fn from(addr: fuels::types::Address) -> Self {
384 Address::Secp256k1(Secp256k1Address(addr))
385 }
386}
387
388impl Signature {
389 pub fn verify_owner<T>(
390 &self,
391 nonce: u64,
392 chain_id: ChainId,
393 f_name: String,
394 args: Option<T>,
395 address: &Address,
396 ) -> anyhow::Result<()>
397 where
398 T: Tokenizable,
399 {
400 match (self, address) {
401 (Signature::Secp256k1(sig), Address::Secp256k1(addr)) => {
402 sig.verify_owner(nonce, chain_id, f_name, args, addr)
403 }
404 (Signature::Secp256r1(_), Address::Secp256r1) => {
405 anyhow::bail!(
406 "owner signature verification not supported for p256 signatures"
407 )
408 }
409 (Signature::Ed25519(_), Address::Ed25519) => {
410 anyhow::bail!(
411 "owner signature verification not supported for ed25519 signatures"
412 )
413 }
414 _ => anyhow::bail!("signature and address type mismatch"),
415 }
416 }
417
418 pub fn verify_session<T>(
419 &self,
420 nonce: u64,
421 args: T,
422 address: &Address,
423 ) -> anyhow::Result<()>
424 where
425 T: Tokenizable,
426 {
427 match (self, address) {
428 (Signature::Secp256k1(sig), Address::Secp256k1(addr)) => {
429 sig.verify_session(nonce, args, addr)
430 }
431 (Signature::Secp256r1(_), Address::Secp256r1) => {
432 anyhow::bail!(
433 "session signature verification not supported for p256 signatures"
434 )
435 }
436 (Signature::Ed25519(_), Address::Ed25519) => {
437 anyhow::bail!(
438 "session signature verification not supported for ed25519 signatures"
439 )
440 }
441 _ => anyhow::bail!("signature and address type mismatch"),
442 }
443 }
444
445 pub fn verify_typed<T>(
446 &self,
447 args: T,
448 domain: &Domain,
449 address: &Address,
450 ) -> anyhow::Result<()>
451 where
452 T: SRC16Encode,
453 {
454 match (self, address) {
455 (Signature::Secp256k1(sig), Address::Secp256k1(addr)) => {
456 sig.verify_typed(args, domain, addr)
457 }
458 (Signature::Secp256r1(_), Address::Secp256r1) => {
459 anyhow::bail!(
460 "typed signature verification not supported for p256 signatures"
461 )
462 }
463 (Signature::Ed25519(_), Address::Ed25519) => {
464 anyhow::bail!(
465 "typed signature verification not supported for ed25519 signatures"
466 )
467 }
468 _ => anyhow::bail!("signature and address type mismatch"),
469 }
470 }
471}
472
473pub const SRC_16_SIGNED_SESSION_TYPE_HASH: &str =
477 "0x2eb25d47a049c7084035785d680582656395f354f9484e7e3bbb2993368e46b7";
478
479pub const EIP_712_SIGNED_SESSION_TYPE_HASH: &str =
483 "0xf91528e233badfd402ad99c4d1dd9d109ec218093c9dcf7432699f5bb94721e2";
484
485impl SRC16Encode for SessionArgs {
486 fn type_hash(encoding: Encoding) -> [u8; 32] {
487 match encoding {
488 Encoding::SRC16 => {
489 Bits256::from_hex_str(SRC_16_SIGNED_SESSION_TYPE_HASH)
490 .unwrap()
491 .0
492 }
493 Encoding::EIP712 => {
494 Bits256::from_hex_str(EIP_712_SIGNED_SESSION_TYPE_HASH)
495 .unwrap()
496 .0
497 }
498 }
499 }
500 fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
501 let mut tokens: Vec<u8> = Vec::new();
502 match encoding {
503 Encoding::SRC16 => tokens.extend_from_slice(
504 Bits256::from_hex_str(SRC_16_SIGNED_SESSION_TYPE_HASH)
505 .unwrap()
506 .0
507 .as_slice(),
508 ),
509 Encoding::EIP712 => tokens.extend_from_slice(
510 Bits256::from_hex_str(EIP_712_SIGNED_SESSION_TYPE_HASH)
511 .unwrap()
512 .0
513 .as_slice(),
514 ),
515 }
516
517 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
519 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
520 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
521 tokens.extend_from_slice(self.nonce.to_be_bytes().as_slice());
522 match self.session_id {
524 Identity::Address(bits) => tokens.extend_from_slice(bits.as_slice()),
525 Identity::ContractId(bits) => tokens.extend_from_slice(bits.as_slice()),
526 }
527 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
529 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
530 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
531 tokens.extend_from_slice(self.expiry.unix.to_be_bytes().as_slice());
532
533 let contract_id_bytes: Vec<u8> = self
535 .contract_ids
536 .iter()
537 .flat_map(|id| id.as_ref())
538 .copied()
539 .collect();
540
541 tokens.extend_from_slice(keccak256(contract_id_bytes).as_slice());
542
543 keccak256(tokens)
544 }
545}
546
547pub const SRC_16_SIGNED_WITHDRAW_TYPE_HASH: &str =
551 "0x9732cd3b83bcf964465644c9c8546dff5d9f2e5c16c077dba9a2a2bc2b9f7188";
552
553pub const EIP_712_SIGNED_WITHDRAW_TYPE_HASH: &str =
557 "0x138ab031e51a15ab8c1d21d9a203f6cc5c134d7dbb44a9fd3f6acc3231af1c32";
558
559impl SRC16Encode for &WithdrawArgs {
560 fn type_hash(encoding: Encoding) -> [u8; 32] {
561 match encoding {
562 Encoding::SRC16 => {
563 Bits256::from_hex_str(SRC_16_SIGNED_WITHDRAW_TYPE_HASH)
564 .unwrap()
565 .0
566 }
567 Encoding::EIP712 => {
568 Bits256::from_hex_str(EIP_712_SIGNED_WITHDRAW_TYPE_HASH)
569 .unwrap()
570 .0
571 }
572 }
573 }
574 fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
575 let mut tokens: Vec<u8> = Vec::new();
576 match encoding {
577 Encoding::SRC16 => tokens.extend_from_slice(
578 Bits256::from_hex_str(SRC_16_SIGNED_WITHDRAW_TYPE_HASH)
579 .unwrap()
580 .0
581 .as_slice(),
582 ),
583 Encoding::EIP712 => tokens.extend_from_slice(
584 Bits256::from_hex_str(EIP_712_SIGNED_WITHDRAW_TYPE_HASH)
585 .unwrap()
586 .0
587 .as_slice(),
588 ),
589 }
590
591 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
592 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
593 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
594 tokens.extend_from_slice(self.nonce.to_be_bytes().as_slice());
595 match self.to {
596 Identity::Address(bits) => tokens.extend_from_slice(bits.as_slice()),
597 Identity::ContractId(bits) => tokens.extend_from_slice(bits.as_slice()),
598 }
599 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
600 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
601 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
602 tokens.extend_from_slice(self.amount.to_be_bytes().as_slice());
603 tokens.extend_from_slice(self.asset_id.as_slice());
604
605 keccak256(tokens)
606 }
607}
608
609pub const SRC_16_SIGNED_CALL_PARAMS_TYPE_HASH: &str =
613 "0x05816efb1220d5dd49f0abf9882712729285e7080da3dc2ba051a6ab701cf3b8";
614
615pub const EIP_712_SIGNED_CALL_PARAMS_TYPE_HASH: &str =
619 "0x322b030cfd61eddd3d0acc5c37358539477197bfc2599dc09a9c75ad47f6e5b8";
620
621impl SRC16Encode for CallParams {
622 fn type_hash(encoding: Encoding) -> [u8; 32] {
623 match encoding {
624 Encoding::SRC16 => {
625 Bits256::from_hex_str(SRC_16_SIGNED_CALL_PARAMS_TYPE_HASH)
626 .unwrap()
627 .0
628 }
629 Encoding::EIP712 => {
630 Bits256::from_hex_str(EIP_712_SIGNED_CALL_PARAMS_TYPE_HASH)
631 .unwrap()
632 .0
633 }
634 }
635 }
636 fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
637 let mut tokens: Vec<u8> = Vec::new();
638 match encoding {
639 Encoding::SRC16 => tokens.extend_from_slice(
640 Bits256::from_hex_str(SRC_16_SIGNED_CALL_PARAMS_TYPE_HASH)
641 .unwrap()
642 .0
643 .as_slice(),
644 ),
645 Encoding::EIP712 => tokens.extend_from_slice(
646 Bits256::from_hex_str(EIP_712_SIGNED_CALL_PARAMS_TYPE_HASH)
647 .unwrap()
648 .0
649 .as_slice(),
650 ),
651 }
652
653 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
655 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
656 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
657 tokens.extend_from_slice(self.coins.to_be_bytes().as_slice());
658 tokens.extend_from_slice(self.asset_id.as_slice());
659 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
660 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
661 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
662 tokens.extend_from_slice(self.gas.to_be_bytes().as_slice());
663
664 keccak256(tokens)
665 }
666}
667
668pub const SRC_16_SIGNED_CALL_CONTRACT_ARG_TYPE_HASH: &str =
672 "0xc8ac163d1f87886f48788901b8e5eb4eebb3edad4ae111859b2a051059920048";
673
674pub const EIP_712_SIGNED_CALL_CONTRACT_ARG_TYPE_HASH: &str =
678 "0x325b44fd679189ccd8f708a440a27448cbfd6f47f90205e8ace3f1efc27c92d6";
679
680impl SRC16Encode for CallContractArg {
681 fn type_hash(encoding: Encoding) -> [u8; 32] {
682 match encoding {
683 Encoding::SRC16 => {
684 Bits256::from_hex_str(SRC_16_SIGNED_CALL_CONTRACT_ARG_TYPE_HASH)
685 .unwrap()
686 .0
687 }
688 Encoding::EIP712 => {
689 Bits256::from_hex_str(EIP_712_SIGNED_CALL_CONTRACT_ARG_TYPE_HASH)
690 .unwrap()
691 .0
692 }
693 }
694 }
695 fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
696 let mut tokens: Vec<u8> = Vec::new();
697 match encoding {
698 Encoding::SRC16 => tokens.extend_from_slice(
699 Bits256::from_hex_str(SRC_16_SIGNED_CALL_CONTRACT_ARG_TYPE_HASH)
700 .unwrap()
701 .0
702 .as_slice(),
703 ),
704 Encoding::EIP712 => tokens.extend_from_slice(
705 Bits256::from_hex_str(EIP_712_SIGNED_CALL_CONTRACT_ARG_TYPE_HASH)
706 .unwrap()
707 .0
708 .as_slice(),
709 ),
710 }
711
712 tokens.extend_from_slice(self.contract_id.as_slice());
714
715 tokens.extend_from_slice(keccak256(self.function_selector.0).as_slice());
717
718 tokens.extend_from_slice(self.call_params.struct_hash(encoding).as_slice());
720
721 match self.call_data {
723 Some(data) => tokens.extend_from_slice(keccak256(data.0).as_slice()),
724 None => tokens.extend_from_slice(keccak256(Vec::new()).as_slice()),
725 }
726
727 keccak256(tokens)
728 }
729}
730
731pub const SRC_16_SIGNED_CALL_CONTRACT_TYPE_HASH: &str =
735 "0x3f9265dacb0b84ea31e56285f5757a2200e743f27f0b27c3d58db09c9f62cb46";
736
737pub const EIP_712_SIGNED_CALL_CONTRACT_TYPE_HASH: &str =
741 "0x26ade4c76b3df03eed7d7a2d1387896e8717f8a862414199904b8a4455b25699";
742
743impl SRC16Encode for TradeAccountCallContractArgs {
744 fn type_hash(encoding: Encoding) -> [u8; 32] {
745 match encoding {
746 Encoding::SRC16 => {
747 Bits256::from_hex_str(SRC_16_SIGNED_CALL_CONTRACT_TYPE_HASH)
748 .unwrap()
749 .0
750 }
751 Encoding::EIP712 => {
752 Bits256::from_hex_str(EIP_712_SIGNED_CALL_CONTRACT_TYPE_HASH)
753 .unwrap()
754 .0
755 }
756 }
757 }
758 fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
759 let mut tokens: Vec<u8> = Vec::new();
760 match encoding {
761 Encoding::SRC16 => tokens.extend_from_slice(
762 Bits256::from_hex_str(SRC_16_SIGNED_CALL_CONTRACT_TYPE_HASH)
763 .unwrap()
764 .0
765 .as_slice(),
766 ),
767 Encoding::EIP712 => tokens.extend_from_slice(
768 Bits256::from_hex_str(EIP_712_SIGNED_CALL_CONTRACT_TYPE_HASH)
769 .unwrap()
770 .0
771 .as_slice(),
772 ),
773 }
774
775 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
777 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
778 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
779 tokens.extend_from_slice(self.nonce.to_be_bytes().as_slice());
780
781 tokens
783 .extend_from_slice(self.call_contract_args.struct_hash(encoding).as_slice());
784
785 keccak256(tokens)
786 }
787}
788
789pub const SRC_16_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH: &str =
793 "0x44c30cb1e12dbc44e06a72b087359f9af5caae554d43dada454d5f6619eabf5f";
794
795pub const EIP_712_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH: &str =
799 "0x292b2913cef817f041ea01e3b02c4dd2b7184e6490f21a84ece8443e406640a1";
800
801impl SRC16Encode for MultiCallContractArgs {
802 fn type_hash(encoding: Encoding) -> [u8; 32] {
803 match encoding {
804 Encoding::SRC16 => {
805 Bits256::from_hex_str(SRC_16_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH)
806 .unwrap()
807 .0
808 }
809 Encoding::EIP712 => {
810 Bits256::from_hex_str(EIP_712_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH)
811 .unwrap()
812 .0
813 }
814 }
815 }
816 fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
817 let mut tokens: Vec<u8> = Vec::new();
818 match encoding {
819 Encoding::SRC16 => tokens.extend_from_slice(
820 Bits256::from_hex_str(SRC_16_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH)
821 .unwrap()
822 .0
823 .as_slice(),
824 ),
825 Encoding::EIP712 => tokens.extend_from_slice(
826 Bits256::from_hex_str(EIP_712_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH)
827 .unwrap()
828 .0
829 .as_slice(),
830 ),
831 }
832
833 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
835 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
836 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
837 tokens.extend_from_slice(self.nonce.to_be_bytes().as_slice());
838
839 let mut call_buffer: Vec<u8> = Vec::new();
841 for call in self.call_contract_args {
842 let h = call.struct_hash(encoding.clone());
843 call_buffer.extend_from_slice(h.as_slice());
844 }
845 tokens.extend_from_slice(keccak256(call_buffer.as_slice()).as_slice());
846
847 keccak256(tokens)
848 }
849}
850
851pub const SRC_16_REVOKE_TYPE_HASH: &str =
855 "0x97c9c74c84314e9fcc4ba97ad28e9cde1eda9b79b0229b54219de0e79560ac86";
856
857pub const EIP_712_REVOKE_TYPE_HASH: &str =
861 "0xf863f4b733d291f6bd744df3ba070a394dd83fced6f038a22195c1b34009dabb";
862
863impl SRC16Encode for RevokeArgs {
864 fn type_hash(encoding: Encoding) -> [u8; 32] {
865 match encoding {
866 Encoding::SRC16 => Bits256::from_hex_str(SRC_16_REVOKE_TYPE_HASH).unwrap().0,
867 Encoding::EIP712 => {
868 Bits256::from_hex_str(EIP_712_REVOKE_TYPE_HASH).unwrap().0
869 }
870 }
871 }
872 fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
873 let mut tokens: Vec<u8> = Vec::new();
874 match encoding {
875 Encoding::SRC16 => tokens.extend_from_slice(
876 Bits256::from_hex_str(SRC_16_REVOKE_TYPE_HASH)
877 .unwrap()
878 .0
879 .as_slice(),
880 ),
881 Encoding::EIP712 => tokens.extend_from_slice(
882 Bits256::from_hex_str(EIP_712_REVOKE_TYPE_HASH)
883 .unwrap()
884 .0
885 .as_slice(),
886 ),
887 }
888
889 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
890 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
891 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
892 tokens.extend_from_slice(self.nonce.to_be_bytes().as_slice());
893
894 keccak256(tokens)
895 }
896}
897
898pub const SRC_16_PROXY_TARGET_TYPE_HASH: &str =
902 "0x523db1f70cbdfc0982627ed4e52dc429384c32e94e16ddc5fa5ab921035d8d6d";
903
904pub const EIP_712_PROXY_TARGET_TYPE_HASH: &str =
908 "0xf12c7a18e931f36d11f09bd52f37183c4993a3a509ac0c2c6cec459cee7a004d";
909
910impl SRC16Encode for SetProxyTargetArgs {
911 fn type_hash(encoding: Encoding) -> [u8; 32] {
912 match encoding {
913 Encoding::SRC16 => {
914 Bits256::from_hex_str(SRC_16_PROXY_TARGET_TYPE_HASH)
915 .unwrap()
916 .0
917 }
918 Encoding::EIP712 => {
919 Bits256::from_hex_str(EIP_712_PROXY_TARGET_TYPE_HASH)
920 .unwrap()
921 .0
922 }
923 }
924 }
925 fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
926 let mut tokens: Vec<u8> = Vec::new();
927 match encoding {
928 Encoding::SRC16 => tokens.extend_from_slice(
929 Bits256::from_hex_str(SRC_16_PROXY_TARGET_TYPE_HASH)
930 .unwrap()
931 .0
932 .as_slice(),
933 ),
934 Encoding::EIP712 => tokens.extend_from_slice(
935 Bits256::from_hex_str(EIP_712_PROXY_TARGET_TYPE_HASH)
936 .unwrap()
937 .0
938 .as_slice(),
939 ),
940 }
941
942 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
943 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
944 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
945 tokens.extend_from_slice(self.nonce.to_be_bytes().as_slice());
946
947 keccak256(tokens)
948 }
949}
950
951pub const SRC_16_PARALLEL_SIGNED_SESSION_TYPE_HASH: &str =
952 "0x7bcb65ebf5b9c6a7eb6a5d92462eeb2b3eba824e9c8f22c2b01533c2e3364019";
953
954pub const EIP_712_PARALLEL_SIGNED_SESSION_TYPE_HASH: &str =
955 "0x67a8adc215ea9fc6e3a50e038c7c123758629a96e6f33c3a5b6bd8534842d9a7";
956
957impl SRC16Encode for &ParallelSessionArgs {
958 fn type_hash(encoding: Encoding) -> [u8; 32] {
959 match encoding {
960 Encoding::SRC16 => {
961 Bits256::from_hex_str(SRC_16_PARALLEL_SIGNED_SESSION_TYPE_HASH)
962 .unwrap()
963 .0
964 }
965 Encoding::EIP712 => {
966 Bits256::from_hex_str(EIP_712_PARALLEL_SIGNED_SESSION_TYPE_HASH)
967 .unwrap()
968 .0
969 }
970 }
971 }
972
973 fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
974 let mut tokens: Vec<u8> = Vec::new();
975 match encoding {
976 Encoding::SRC16 => tokens.extend_from_slice(
977 Bits256::from_hex_str(SRC_16_PARALLEL_SIGNED_SESSION_TYPE_HASH)
978 .unwrap()
979 .0
980 .as_slice(),
981 ),
982 Encoding::EIP712 => tokens.extend_from_slice(
983 Bits256::from_hex_str(EIP_712_PARALLEL_SIGNED_SESSION_TYPE_HASH)
984 .unwrap()
985 .0
986 .as_slice(),
987 ),
988 }
989
990 tokens.extend_from_slice(fuel_u256_to_bytes(&self.nonce).as_slice());
991 match self.session_id {
992 Identity::Address(bits) => tokens.extend_from_slice(bits.as_slice()),
993 Identity::ContractId(bits) => tokens.extend_from_slice(bits.as_slice()),
994 }
995 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
996 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
997 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
998 tokens.extend_from_slice(self.expiry.unix.to_be_bytes().as_slice());
999
1000 let contract_id_bytes: Vec<u8> = self
1001 .contract_ids
1002 .iter()
1003 .flat_map(|id| id.as_ref())
1004 .copied()
1005 .collect();
1006 tokens.extend_from_slice(keccak256(contract_id_bytes).as_slice());
1007
1008 keccak256(tokens)
1009 }
1010}
1011
1012pub const SRC_16_PARALLEL_SIGNED_WITHDRAW_TYPE_HASH: &str =
1013 "0x95a9c4c23dd3607b14ecdc67fcc42946f61a4f1d153dc7dfbd77ca6c2af1c6f6";
1014
1015pub const EIP_712_PARALLEL_SIGNED_WITHDRAW_TYPE_HASH: &str =
1016 "0x7d2e70c357f073fd00c187b1871bf74c440ec8303b435f001d884d6ffd77ca1c";
1017
1018impl SRC16Encode for &ParallelWithdrawArgs {
1019 fn type_hash(encoding: Encoding) -> [u8; 32] {
1020 match encoding {
1021 Encoding::SRC16 => {
1022 Bits256::from_hex_str(SRC_16_PARALLEL_SIGNED_WITHDRAW_TYPE_HASH)
1023 .unwrap()
1024 .0
1025 }
1026 Encoding::EIP712 => {
1027 Bits256::from_hex_str(EIP_712_PARALLEL_SIGNED_WITHDRAW_TYPE_HASH)
1028 .unwrap()
1029 .0
1030 }
1031 }
1032 }
1033
1034 fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
1035 let mut tokens: Vec<u8> = Vec::new();
1036 match encoding {
1037 Encoding::SRC16 => tokens.extend_from_slice(
1038 Bits256::from_hex_str(SRC_16_PARALLEL_SIGNED_WITHDRAW_TYPE_HASH)
1039 .unwrap()
1040 .0
1041 .as_slice(),
1042 ),
1043 Encoding::EIP712 => tokens.extend_from_slice(
1044 Bits256::from_hex_str(EIP_712_PARALLEL_SIGNED_WITHDRAW_TYPE_HASH)
1045 .unwrap()
1046 .0
1047 .as_slice(),
1048 ),
1049 }
1050
1051 tokens.extend_from_slice(fuel_u256_to_bytes(&self.nonce).as_slice());
1052 match self.to {
1053 Identity::Address(bits) => tokens.extend_from_slice(bits.as_slice()),
1054 Identity::ContractId(bits) => tokens.extend_from_slice(bits.as_slice()),
1055 }
1056 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
1057 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
1058 tokens.extend_from_slice(0u64.to_be_bytes().as_slice());
1059 tokens.extend_from_slice(self.amount.to_be_bytes().as_slice());
1060 tokens.extend_from_slice(self.asset_id.as_slice());
1061
1062 keccak256(tokens)
1063 }
1064}
1065
1066pub const SRC_16_PARALLEL_SIGNED_CALL_CONTRACT_TYPE_HASH: &str =
1067 "0xfed97554285faeaf02128aa961f1b12bc144175e0bc3095deee2999f9c0563c1";
1068
1069pub const EIP_712_PARALLEL_SIGNED_CALL_CONTRACT_TYPE_HASH: &str =
1070 "0x936febfd2859fb63ad8d03cb75f762221befa5f66b3ed7a2716039b602fdc944";
1071
1072impl SRC16Encode for ParallelCallContractArgs {
1073 fn type_hash(encoding: Encoding) -> [u8; 32] {
1074 match encoding {
1075 Encoding::SRC16 => {
1076 Bits256::from_hex_str(SRC_16_PARALLEL_SIGNED_CALL_CONTRACT_TYPE_HASH)
1077 .unwrap()
1078 .0
1079 }
1080 Encoding::EIP712 => {
1081 Bits256::from_hex_str(EIP_712_PARALLEL_SIGNED_CALL_CONTRACT_TYPE_HASH)
1082 .unwrap()
1083 .0
1084 }
1085 }
1086 }
1087
1088 fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
1089 let mut tokens: Vec<u8> = Vec::new();
1090 match encoding {
1091 Encoding::SRC16 => tokens.extend_from_slice(
1092 Bits256::from_hex_str(SRC_16_PARALLEL_SIGNED_CALL_CONTRACT_TYPE_HASH)
1093 .unwrap()
1094 .0
1095 .as_slice(),
1096 ),
1097 Encoding::EIP712 => tokens.extend_from_slice(
1098 Bits256::from_hex_str(EIP_712_PARALLEL_SIGNED_CALL_CONTRACT_TYPE_HASH)
1099 .unwrap()
1100 .0
1101 .as_slice(),
1102 ),
1103 }
1104
1105 tokens.extend_from_slice(fuel_u256_to_bytes(&self.nonce).as_slice());
1106 tokens
1107 .extend_from_slice(self.call_contract_args.struct_hash(encoding).as_slice());
1108
1109 keccak256(tokens)
1110 }
1111}
1112
1113pub const SRC_16_PARALLEL_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH: &str =
1114 "0xb913eb232c21ef9250e65abc3c1cb9f75af53ed044455ea484d4f3fa3b4b0ee5";
1115
1116pub const EIP_712_PARALLEL_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH: &str =
1117 "0x3c5f4ecf0e01c024666fd99c1a6b05d93d7ca2ca31879f4d66d008356c8dcbf2";
1118
1119impl SRC16Encode for ParallelMultiCallContractArgs {
1120 fn type_hash(encoding: Encoding) -> [u8; 32] {
1121 match encoding {
1122 Encoding::SRC16 => {
1123 Bits256::from_hex_str(
1124 SRC_16_PARALLEL_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH,
1125 )
1126 .unwrap()
1127 .0
1128 }
1129 Encoding::EIP712 => {
1130 Bits256::from_hex_str(
1131 EIP_712_PARALLEL_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH,
1132 )
1133 .unwrap()
1134 .0
1135 }
1136 }
1137 }
1138
1139 fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
1140 let mut tokens: Vec<u8> = Vec::new();
1141 match encoding {
1142 Encoding::SRC16 => tokens.extend_from_slice(
1143 Bits256::from_hex_str(
1144 SRC_16_PARALLEL_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH,
1145 )
1146 .unwrap()
1147 .0
1148 .as_slice(),
1149 ),
1150 Encoding::EIP712 => tokens.extend_from_slice(
1151 Bits256::from_hex_str(
1152 EIP_712_PARALLEL_SIGNED_MULTI_CALL_CONTRACT_TYPE_HASH,
1153 )
1154 .unwrap()
1155 .0
1156 .as_slice(),
1157 ),
1158 }
1159
1160 tokens.extend_from_slice(fuel_u256_to_bytes(&self.nonce).as_slice());
1161
1162 let mut call_buffer: Vec<u8> = Vec::new();
1163 for call in self.call_contract_args {
1164 call_buffer.extend_from_slice(call.struct_hash(encoding.clone()).as_slice());
1165 }
1166 tokens.extend_from_slice(keccak256(call_buffer).as_slice());
1167
1168 keccak256(tokens)
1169 }
1170}
1171
1172pub const SRC_16_PARALLEL_REVOKE_TYPE_HASH: &str =
1173 "0xac7c930989abf28d71694ae7f327a130cdc1ef80040d94c6873cf0686024382d";
1174
1175pub const EIP_712_PARALLEL_REVOKE_TYPE_HASH: &str =
1176 "0x51c1376821126b4be9647fdbbf51877dd5a72c6a76e58fb957f37ac985962ea8";
1177
1178impl SRC16Encode for ParallelRevokeArgs {
1179 fn type_hash(encoding: Encoding) -> [u8; 32] {
1180 match encoding {
1181 Encoding::SRC16 => {
1182 Bits256::from_hex_str(SRC_16_PARALLEL_REVOKE_TYPE_HASH)
1183 .unwrap()
1184 .0
1185 }
1186 Encoding::EIP712 => {
1187 Bits256::from_hex_str(EIP_712_PARALLEL_REVOKE_TYPE_HASH)
1188 .unwrap()
1189 .0
1190 }
1191 }
1192 }
1193
1194 fn struct_hash(self, encoding: Encoding) -> [u8; 32] {
1195 let mut tokens: Vec<u8> = Vec::new();
1196 match encoding {
1197 Encoding::SRC16 => tokens.extend_from_slice(
1198 Bits256::from_hex_str(SRC_16_PARALLEL_REVOKE_TYPE_HASH)
1199 .unwrap()
1200 .0
1201 .as_slice(),
1202 ),
1203 Encoding::EIP712 => tokens.extend_from_slice(
1204 Bits256::from_hex_str(EIP_712_PARALLEL_REVOKE_TYPE_HASH)
1205 .unwrap()
1206 .0
1207 .as_slice(),
1208 ),
1209 }
1210
1211 tokens.extend_from_slice(fuel_u256_to_bytes(&self.nonce).as_slice());
1212
1213 keccak256(tokens)
1214 }
1215}
1216
1217#[cfg(test)]
1218mod tests {
1219 use super::*;
1220 use fuels::types::ContractId;
1221
1222 #[test]
1255 fn src16_domain_separator_encodes_chain_id_msu_first_big_endian() {
1256 let chain_id: u64 = 9889;
1257 let name = "TradeAccount";
1258 let version = "2";
1259 let mut vc_bytes = [0u8; 32];
1260 for (i, chunk) in
1261 "9423658cc5c65c586208bedd70f1cc11af5120858df6c6ba128b6ac4e1e6a9d6"
1262 .as_bytes()
1263 .chunks(2)
1264 .enumerate()
1265 {
1266 vc_bytes[i] =
1267 u8::from_str_radix(std::str::from_utf8(chunk).unwrap(), 16).unwrap();
1268 }
1269 let verifying_contract = ContractId::from(vc_bytes);
1270
1271 let domain = SRC16Domain {
1272 name: Some(name.to_string()),
1273 version: Some(version.to_string()),
1274 chain_id: Some(FuelU256::from(chain_id)),
1275 verifying_contract: Some(verifying_contract),
1276 salt: None,
1277 };
1278 let actual = domain.separator();
1279
1280 let type_hash_str = "SRC16Domain(string name,string version,u256 chain_id,contractId verifying_contract)";
1284 let type_hash = keccak256(type_hash_str.as_bytes());
1285
1286 let mut expected_preimage = Vec::new();
1287 expected_preimage.extend_from_slice(&type_hash);
1288 expected_preimage.extend_from_slice(&keccak256(name.as_bytes()));
1289 expected_preimage.extend_from_slice(&keccak256(version.as_bytes()));
1290 let mut chain_id_be = [0u8; 32];
1293 chain_id_be[24..].copy_from_slice(&chain_id.to_be_bytes());
1294 expected_preimage.extend_from_slice(&chain_id_be);
1295 expected_preimage.extend_from_slice(verifying_contract.as_slice());
1296
1297 let expected = keccak256(&expected_preimage);
1298
1299 assert_eq!(
1300 actual,
1301 expected,
1302 "SRC16Domain::separator() must place chain_id at byte offset 24 \
1303 (big-endian) to match the Sway contract's VM-memory mcp read; \
1304 got {} expected {}",
1305 hex_of(&actual),
1306 hex_of(&expected),
1307 );
1308 }
1309
1310 #[test]
1316 fn src16_domain_separator_handles_zero_chain_id_unchanged() {
1317 let domain = SRC16Domain {
1318 name: Some("TradeAccount".to_string()),
1319 version: Some("2".to_string()),
1320 chain_id: Some(FuelU256::from(0u64)),
1321 verifying_contract: Some(ContractId::from([0u8; 32])),
1322 salt: None,
1323 };
1324 let _ = domain.separator();
1328 }
1329
1330 fn hex_of(bytes: &[u8]) -> String {
1331 bytes.iter().map(|b| format!("{:02x}", b)).collect()
1332 }
1333}