From ca155c8ab72a31e8ff73d8d65dd738b272871815 Mon Sep 17 00:00:00 2001 From: evalir Date: Thu, 21 Aug 2025 17:20:47 +0200 Subject: [PATCH 1/8] feat(types): implement `order_hash` for `SignedOrder` wip --- crates/types/src/signing/order.rs | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/crates/types/src/signing/order.rs b/crates/types/src/signing/order.rs index 07a8b060..c43a4543 100644 --- a/crates/types/src/signing/order.rs +++ b/crates/types/src/signing/order.rs @@ -1,7 +1,10 @@ use crate::signing::{permit_signing_info, SignedPermitError, SigningError}; use alloy::{ - network::TransactionBuilder, primitives::Address, rpc::types::TransactionRequest, - signers::Signer, sol_types::SolCall, + network::TransactionBuilder, + primitives::{Address, B256}, + rpc::types::TransactionRequest, + signers::Signer, + sol_types::{SolCall, SolValue}, }; use chrono::Utc; use serde::{Deserialize, Serialize}; @@ -68,6 +71,30 @@ impl SignedOrder { // construct an initiate tx request TransactionRequest::default().with_input(initiate_data).with_to(order_contract) } + + /// Get the hash of the order. + /// + /// # Composition + /// + /// The order hash is composed of the following: + /// - The permit2 batch permit, ABI encoded. + /// - The permit2 batch outputs, ABI encoded. + /// - The permit2 batch signature, normalized. + /// + /// The components are then hashed together. + pub fn order_hash(&self) -> B256 { + let mut buf = vec![]; + + buf.extend_from_slice(self.permit.abi_encode().as_slice()); + buf.extend_from_slice(self.outputs.abi_encode().as_slice()); + + // Normalize the signature. + let signature = + alloy::primitives::Signature::from_raw(&self.permit.signature).unwrap().normalized_s(); + buf.extend_from_slice(&signature.as_bytes()); + + alloy::primitives::keccak256(buf) + } } /// An UnsignedOrder is a helper type used to easily transform an Order into a SignedOrder with correct permit2 semantics. From 55c8edec5cf4946a3788275d568a51e3c3c64669 Mon Sep 17 00:00:00 2001 From: evalir Date: Thu, 21 Aug 2025 17:23:03 +0200 Subject: [PATCH 2/8] chore: do not encode the entire permit, only the actual permit batch and owner --- crates/types/src/signing/order.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/types/src/signing/order.rs b/crates/types/src/signing/order.rs index c43a4543..76a02b22 100644 --- a/crates/types/src/signing/order.rs +++ b/crates/types/src/signing/order.rs @@ -85,7 +85,8 @@ impl SignedOrder { pub fn order_hash(&self) -> B256 { let mut buf = vec![]; - buf.extend_from_slice(self.permit.abi_encode().as_slice()); + buf.extend_from_slice(self.permit.permit.abi_encode().as_slice()); + buf.extend_from_slice(self.permit.owner.abi_encode().as_slice()); buf.extend_from_slice(self.outputs.abi_encode().as_slice()); // Normalize the signature. From 87728909646781047b6957c633894ea27e0aa07e Mon Sep 17 00:00:00 2001 From: evalir Date: Thu, 21 Aug 2025 17:24:10 +0200 Subject: [PATCH 3/8] chore: mod docs --- crates/types/src/signing/order.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/types/src/signing/order.rs b/crates/types/src/signing/order.rs index 76a02b22..759dffaa 100644 --- a/crates/types/src/signing/order.rs +++ b/crates/types/src/signing/order.rs @@ -77,8 +77,9 @@ impl SignedOrder { /// # Composition /// /// The order hash is composed of the following: - /// - The permit2 batch permit, ABI encoded. - /// - The permit2 batch outputs, ABI encoded. + /// - The permit2 batch permit inputs, ABI encoded. + /// - The permit2 batch owner, ABI encoded. + /// - The order outputs, ABI encoded. /// - The permit2 batch signature, normalized. /// /// The components are then hashed together. From 6c8208ef672135355f5c01cb922ea418a8abe9b2 Mon Sep 17 00:00:00 2001 From: evalir Date: Thu, 21 Aug 2025 20:28:25 +0200 Subject: [PATCH 4/8] chore: keccak intermediate steps --- crates/types/src/signing/order.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/types/src/signing/order.rs b/crates/types/src/signing/order.rs index 759dffaa..a58336b3 100644 --- a/crates/types/src/signing/order.rs +++ b/crates/types/src/signing/order.rs @@ -1,7 +1,7 @@ use crate::signing::{permit_signing_info, SignedPermitError, SigningError}; use alloy::{ network::TransactionBuilder, - primitives::{Address, B256}, + primitives::{keccak256, Address, B256}, rpc::types::TransactionRequest, signers::Signer, sol_types::{SolCall, SolValue}, @@ -77,25 +77,25 @@ impl SignedOrder { /// # Composition /// /// The order hash is composed of the following: - /// - The permit2 batch permit inputs, ABI encoded. - /// - The permit2 batch owner, ABI encoded. - /// - The order outputs, ABI encoded. - /// - The permit2 batch signature, normalized. + /// - The permit2 batch permit inputs, ABI encoded and hashed. + /// - The permit2 batch owner, ABI encoded and hashed. + /// - The order outputs, ABI encoded and hashed. + /// - The permit2 batch signature, normalized and hashed. /// /// The components are then hashed together. pub fn order_hash(&self) -> B256 { let mut buf = vec![]; - buf.extend_from_slice(self.permit.permit.abi_encode().as_slice()); - buf.extend_from_slice(self.permit.owner.abi_encode().as_slice()); - buf.extend_from_slice(self.outputs.abi_encode().as_slice()); + buf.extend_from_slice(keccak256(self.permit.permit.abi_encode()).as_slice()); + buf.extend_from_slice(keccak256(self.permit.owner.abi_encode()).as_slice()); + buf.extend_from_slice(keccak256(self.outputs.abi_encode()).as_slice()); // Normalize the signature. let signature = alloy::primitives::Signature::from_raw(&self.permit.signature).unwrap().normalized_s(); - buf.extend_from_slice(&signature.as_bytes()); + buf.extend_from_slice(keccak256(signature.as_bytes()).as_slice()); - alloy::primitives::keccak256(buf) + keccak256(buf) } } From 01d2418a03663d44f736f47ecd3b5822c27c955b Mon Sep 17 00:00:00 2001 From: evalir Date: Thu, 21 Aug 2025 20:45:48 +0200 Subject: [PATCH 5/8] chore: solve nits --- crates/types/src/signing/order.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/types/src/signing/order.rs b/crates/types/src/signing/order.rs index a58336b3..41bfea18 100644 --- a/crates/types/src/signing/order.rs +++ b/crates/types/src/signing/order.rs @@ -84,7 +84,15 @@ impl SignedOrder { /// /// The components are then hashed together. pub fn order_hash(&self) -> B256 { - let mut buf = vec![]; + keccak256(self.order_hash_pre_image()) + } + + /// Get the pre-image for the order hash. + /// + /// This is the raw bytes that are hashed to produce the order hash. + fn order_hash_pre_image(&self) -> Vec { + // 4 * 32 bytes = 128 bytes + let mut buf = Vec::with_capacity(128); buf.extend_from_slice(keccak256(self.permit.permit.abi_encode()).as_slice()); buf.extend_from_slice(keccak256(self.permit.owner.abi_encode()).as_slice()); @@ -95,7 +103,7 @@ impl SignedOrder { alloy::primitives::Signature::from_raw(&self.permit.signature).unwrap().normalized_s(); buf.extend_from_slice(keccak256(signature.as_bytes()).as_slice()); - keccak256(buf) + buf } } From 437b1c11008618734f3471037e63a179643e33a0 Mon Sep 17 00:00:00 2001 From: evalir Date: Thu, 21 Aug 2025 20:57:08 +0200 Subject: [PATCH 6/8] chore: add basic test --- crates/types/src/signing/order.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/crates/types/src/signing/order.rs b/crates/types/src/signing/order.rs index 41bfea18..7c04ae7f 100644 --- a/crates/types/src/signing/order.rs +++ b/crates/types/src/signing/order.rs @@ -90,7 +90,8 @@ impl SignedOrder { /// Get the pre-image for the order hash. /// /// This is the raw bytes that are hashed to produce the order hash. - fn order_hash_pre_image(&self) -> Vec { + #[doc(hidden)] + pub fn order_hash_pre_image(&self) -> Vec { // 4 * 32 bytes = 128 bytes let mut buf = Vec::with_capacity(128); @@ -184,3 +185,25 @@ impl<'a> UnsignedOrder<'a> { }) } } + +#[cfg(test)] +mod tests { + use alloy::primitives::b256; + + use super::*; + + #[test] + fn test_order_hash() { + // https://explorer.pecorino.signet.sh/tx/0xdc842ebcadf89f91c936ec4c4e6b16d2a7b9179f4d7aaefb664179d546c807ec + let order = r#"{"permit":{"permitted":[{"token":"0x0000000000000000007369676e65742d77657468","amount":"0x3b9aca00"}],"nonce":"0x63ce4972df028","deadline":"0x68a76d42"},"owner":"0x492e9c316f073fe4de9d665221568cdad1a7e95b","signature":"0xab7a30e9d211f1f75d9c0759e41d60b886eb0c7c80a471e292b244b78ac84ffa059b1bd281f278336e22d977d0cccaeaa81e0adf6714e579c03fe03e49d5b8611b","outputs":[{"token":"0xd03d085b78067a18155d3b29d64914df3d19a53c","amount":"0x3b9aca00","recipient":"0x492e9c316f073fe4de9d665221568cdad1a7e95b","chainId":3151908}]}"#; + let order: SignedOrder = serde_json::from_str(order).unwrap(); + let hash = order.order_hash(); + let pre_image = order.order_hash_pre_image(); + + assert_eq!(hash, keccak256(pre_image)); + assert_eq!( + hash, + b256!("0x7e721875bb2bb9ea09a528992b13388e0b0566381e0f407daf8d797326728002") + ); + } +} From 3c0113b9becd8d2060206bfd52e09d8e8a5fee10 Mon Sep 17 00:00:00 2001 From: evalir Date: Thu, 21 Aug 2025 21:02:54 +0200 Subject: [PATCH 7/8] chore: load order as artifact instead --- crates/types/src/signing/order.rs | 4 ++-- tests/artifacts/order_hash.json | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 tests/artifacts/order_hash.json diff --git a/crates/types/src/signing/order.rs b/crates/types/src/signing/order.rs index 7c04ae7f..d72a3f3d 100644 --- a/crates/types/src/signing/order.rs +++ b/crates/types/src/signing/order.rs @@ -195,7 +195,7 @@ mod tests { #[test] fn test_order_hash() { // https://explorer.pecorino.signet.sh/tx/0xdc842ebcadf89f91c936ec4c4e6b16d2a7b9179f4d7aaefb664179d546c807ec - let order = r#"{"permit":{"permitted":[{"token":"0x0000000000000000007369676e65742d77657468","amount":"0x3b9aca00"}],"nonce":"0x63ce4972df028","deadline":"0x68a76d42"},"owner":"0x492e9c316f073fe4de9d665221568cdad1a7e95b","signature":"0xab7a30e9d211f1f75d9c0759e41d60b886eb0c7c80a471e292b244b78ac84ffa059b1bd281f278336e22d977d0cccaeaa81e0adf6714e579c03fe03e49d5b8611b","outputs":[{"token":"0xd03d085b78067a18155d3b29d64914df3d19a53c","amount":"0x3b9aca00","recipient":"0x492e9c316f073fe4de9d665221568cdad1a7e95b","chainId":3151908}]}"#; + let order = include_str!("../../../../tests/artifacts/order_hash.json"); let order: SignedOrder = serde_json::from_str(order).unwrap(); let hash = order.order_hash(); let pre_image = order.order_hash_pre_image(); @@ -203,7 +203,7 @@ mod tests { assert_eq!(hash, keccak256(pre_image)); assert_eq!( hash, - b256!("0x7e721875bb2bb9ea09a528992b13388e0b0566381e0f407daf8d797326728002") + b256!("0x4c74fe5a339b88fa909a43828a31466deba6b33cc6b6f522d1e8c5755a9fddcd") ); } } diff --git a/tests/artifacts/order_hash.json b/tests/artifacts/order_hash.json new file mode 100644 index 00000000..5a288f7e --- /dev/null +++ b/tests/artifacts/order_hash.json @@ -0,0 +1,22 @@ +{ + "permit": { + "permitted": [ + { + "token": "0x0000000000000000007369676e65742d77657468", + "amount": "0x3b9aca00" + } + ], + "nonce": "0x63ce4972df028", + "deadline": "0x68a76d42" + }, + "owner": "0x492e9c316f073fe4de9d665221568cdad1a7e95b", + "signature": "0xab7a30e9d211f1f75d9c0759e41d60b886eb0c7c80a471e292b244b78ac84ffa059b1bd281f278336e22d977d0cccaeaa81e0adf6714e579c03fe03e49d5b8611b", + "outputs": [ + { + "token": "0xd03d085b78067a18155d3b29d64914df3d19a53c", + "amount": "0x3b9aca00", + "recipient": "0x492e9c316f073fe4de9d665221568cdad1a7e95b", + "chainId": 3151908 + } + ] +} From b78ec67e06fec059a92d9b0ddf466bb5e0edef51 Mon Sep 17 00:00:00 2001 From: evalir Date: Fri, 22 Aug 2025 12:49:30 +0200 Subject: [PATCH 8/8] chore: create very basic signed order manually instead for reproducibility --- crates/types/src/signing/order.rs | 29 ++++++++++++++++++++++++----- tests/artifacts/order_hash.json | 22 ---------------------- 2 files changed, 24 insertions(+), 27 deletions(-) delete mode 100644 tests/artifacts/order_hash.json diff --git a/crates/types/src/signing/order.rs b/crates/types/src/signing/order.rs index d72a3f3d..46fc2978 100644 --- a/crates/types/src/signing/order.rs +++ b/crates/types/src/signing/order.rs @@ -188,22 +188,41 @@ impl<'a> UnsignedOrder<'a> { #[cfg(test)] mod tests { - use alloy::primitives::b256; + use alloy::primitives::{b256, Signature, U256}; + use signet_zenith::HostOrders::{PermitBatchTransferFrom, TokenPermissions}; use super::*; + fn basic_order() -> SignedOrder { + SignedOrder::new( + Permit2Batch { + permit: PermitBatchTransferFrom { + permitted: vec![TokenPermissions { token: Address::ZERO, amount: U256::ZERO }], + nonce: U256::ZERO, + deadline: U256::ZERO, + }, + owner: Address::ZERO, + signature: Signature::test_signature().as_bytes().into(), + }, + vec![Output { + token: Address::ZERO, + amount: U256::ZERO, + recipient: Address::ZERO, + chainId: 0, + }], + ) + } + #[test] fn test_order_hash() { - // https://explorer.pecorino.signet.sh/tx/0xdc842ebcadf89f91c936ec4c4e6b16d2a7b9179f4d7aaefb664179d546c807ec - let order = include_str!("../../../../tests/artifacts/order_hash.json"); - let order: SignedOrder = serde_json::from_str(order).unwrap(); + let order = basic_order(); let hash = order.order_hash(); let pre_image = order.order_hash_pre_image(); assert_eq!(hash, keccak256(pre_image)); assert_eq!( hash, - b256!("0x4c74fe5a339b88fa909a43828a31466deba6b33cc6b6f522d1e8c5755a9fddcd") + b256!("0xba359dd4f891bed0a2cf87c306e59fb6ee099e02b5b0fa86584cdcc44bf6c272") ); } } diff --git a/tests/artifacts/order_hash.json b/tests/artifacts/order_hash.json deleted file mode 100644 index 5a288f7e..00000000 --- a/tests/artifacts/order_hash.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "permit": { - "permitted": [ - { - "token": "0x0000000000000000007369676e65742d77657468", - "amount": "0x3b9aca00" - } - ], - "nonce": "0x63ce4972df028", - "deadline": "0x68a76d42" - }, - "owner": "0x492e9c316f073fe4de9d665221568cdad1a7e95b", - "signature": "0xab7a30e9d211f1f75d9c0759e41d60b886eb0c7c80a471e292b244b78ac84ffa059b1bd281f278336e22d977d0cccaeaa81e0adf6714e579c03fe03e49d5b8611b", - "outputs": [ - { - "token": "0xd03d085b78067a18155d3b29d64914df3d19a53c", - "amount": "0x3b9aca00", - "recipient": "0x492e9c316f073fe4de9d665221568cdad1a7e95b", - "chainId": 3151908 - } - ] -}