1use super::{BundleActionArgs, EthEncodeError};
11use ethabi::{encode, Token, Uint};
12use sha3::{Digest, Keccak256};
13
14#[derive(Debug, Clone)]
16pub struct PrivacyCallArgs {
17 pub actions: Vec<BundleActionArgs>,
18 pub binding_sig: [[u8; 32]; 3],
20}
21
22fn selector(signature: &[u8]) -> [u8; 4] {
23 Keccak256::digest(signature)[..4]
24 .try_into()
25 .expect("selector is 4 bytes")
26}
27
28fn bundle_actions_token(actions: &[BundleActionArgs]) -> Token {
30 Token::Array(
31 actions
32 .iter()
33 .map(|a| {
34 let pub_fields_token = Token::FixedArray(
35 a.pub_fields
36 .iter()
37 .map(|b| Token::Uint(Uint::from_big_endian(b)))
38 .collect(),
39 );
40 let spend_auth_sig_token = Token::FixedArray(
41 a.spend_auth_sig
42 .iter()
43 .map(|b| Token::Uint(Uint::from_big_endian(b)))
44 .collect(),
45 );
46 Token::Tuple(vec![
47 Token::FixedBytes(a.cmx.to_vec()),
48 Token::Bytes(a.enc_ciphertext.clone()),
49 Token::Bytes(a.out_ciphertext.clone()),
50 Token::FixedBytes(a.epk.to_vec()),
51 Token::FixedBytes(a.nf_old.to_vec()),
52 Token::FixedBytes(a.anchor.to_vec()),
53 Token::Bytes(a.proof.clone()),
54 pub_fields_token,
55 spend_auth_sig_token,
56 ])
57 })
58 .collect(),
59 )
60}
61
62fn privacy_call_token(call: &PrivacyCallArgs) -> Token {
64 let actions_bytes = encode(&[bundle_actions_token(&call.actions)]);
65 let binding_sig_token = Token::FixedArray(
66 call.binding_sig
67 .iter()
68 .map(|b| Token::Uint(Uint::from_big_endian(b)))
69 .collect(),
70 );
71 Token::Tuple(vec![Token::Bytes(actions_bytes), binding_sig_token])
72}
73
74pub fn privacy_call_commit(call: &PrivacyCallArgs) -> [u8; 32] {
77 let encoded = encode(&[privacy_call_token(call)]);
78 Keccak256::digest(&encoded).into()
79}
80
81fn with_selector(sel: [u8; 4], body: Vec<u8>) -> Vec<u8> {
82 let mut out = Vec::with_capacity(4 + body.len());
83 out.extend_from_slice(&sel);
84 out.extend_from_slice(&body);
85 out
86}
87
88pub fn encode_perc20_transfer_calldata(call: &PrivacyCallArgs) -> Vec<u8> {
92 let body = encode(&[privacy_call_token(call)]);
93 with_selector(selector(b"transfer((bytes,uint256[3]))"), body)
94}
95
96pub fn encode_perc20_transfer_executor_calldata(
100 executor: &[u8; 20],
101 call: &PrivacyCallArgs,
102) -> Vec<u8> {
103 let tokens = vec![
104 Token::Address(ethabi::Address::from(*executor)),
105 privacy_call_token(call),
106 ];
107 let body = encode(&tokens);
108 with_selector(selector(b"transfer(address,(bytes,uint256[3]))"), body)
109}
110
111pub fn encode_wrapped_shield_calldata(amount_units: u64, call: &PrivacyCallArgs) -> Vec<u8> {
117 let tokens = vec![Token::Uint(Uint::from(amount_units)), privacy_call_token(call)];
118 let body = encode(&tokens);
119 with_selector(selector(b"shield(uint256,(bytes,uint256[3]))"), body)
120}
121
122pub fn encode_wrapped_unshield_calldata(
126 amount_units: u64,
127 recipient: &[u8; 20],
128 call: &PrivacyCallArgs,
129) -> Vec<u8> {
130 let tokens = vec![
131 Token::Uint(Uint::from(amount_units)),
132 Token::Address(ethabi::Address::from(*recipient)),
133 privacy_call_token(call),
134 ];
135 let body = encode(&tokens);
136 with_selector(selector(b"unshield(uint256,address,(bytes,uint256[3]))"), body)
137}
138
139pub fn compute_swap_id(
148 initiator: &[u8; 20],
149 pool_a: &[u8; 20],
150 pool_b: &[u8; 20],
151 htlc_hash: &[u8; 32],
152 commit_a: &[u8; 32],
153 rk_bx: &[u8; 32],
154 rk_by: &[u8; 32],
155 salt: &[u8; 32],
156) -> [u8; 32] {
157 let encoded = encode(&[
158 Token::Address(ethabi::Address::from(*initiator)),
159 Token::Address(ethabi::Address::from(*pool_a)),
160 Token::Address(ethabi::Address::from(*pool_b)),
161 Token::FixedBytes(htlc_hash.to_vec()),
162 Token::FixedBytes(commit_a.to_vec()),
163 Token::Uint(Uint::from_big_endian(rk_bx)),
164 Token::Uint(Uint::from_big_endian(rk_by)),
165 Token::FixedBytes(salt.to_vec()),
166 ]);
167 Keccak256::digest(&encoded).into()
168}
169
170pub fn encode_swap_initiate_calldata(
174 pool_a: &[u8; 20],
175 pool_b: &[u8; 20],
176 htlc_hash: &[u8; 32],
177 commit_a: &[u8; 32],
178 rk_bx: &[u8; 32],
179 rk_by: &[u8; 32],
180 deadline: u64,
181 salt: &[u8; 32],
182) -> Vec<u8> {
183 let tokens = vec![
184 Token::Address(ethabi::Address::from(*pool_a)),
185 Token::Address(ethabi::Address::from(*pool_b)),
186 Token::FixedBytes(htlc_hash.to_vec()),
187 Token::FixedBytes(commit_a.to_vec()),
188 Token::Uint(Uint::from_big_endian(rk_bx)),
189 Token::Uint(Uint::from_big_endian(rk_by)),
190 Token::Uint(Uint::from(deadline)),
191 Token::FixedBytes(salt.to_vec()),
192 ];
193 let body = encode(&tokens);
194 with_selector(
195 selector(b"initiateSwap(address,address,bytes32,bytes32,uint256,uint256,uint64,bytes32)"),
196 body,
197 )
198}
199
200pub fn encode_swap_join_calldata(
205 swap_id: &[u8; 32],
206 commit_b: &[u8; 32],
207 joiner_sig: &[[u8; 32]; 3],
208) -> Vec<u8> {
209 let joiner_sig_token = Token::FixedArray(
210 joiner_sig
211 .iter()
212 .map(|b| Token::Uint(Uint::from_big_endian(b)))
213 .collect(),
214 );
215 let tokens = vec![
216 Token::FixedBytes(swap_id.to_vec()),
217 Token::FixedBytes(commit_b.to_vec()),
218 joiner_sig_token,
219 ];
220 let body = encode(&tokens);
221 with_selector(
222 selector(b"joinSwap(bytes32,bytes32,uint256[3])"),
223 body,
224 )
225}
226
227pub fn encode_swap_settle_calldata(
230 swap_id: &[u8; 32],
231 secret: &[u8; 32],
232 call_a: &PrivacyCallArgs,
233 call_b: &PrivacyCallArgs,
234) -> Vec<u8> {
235 let tokens = vec![
236 Token::FixedBytes(swap_id.to_vec()),
237 Token::FixedBytes(secret.to_vec()),
238 privacy_call_token(call_a),
239 privacy_call_token(call_b),
240 ];
241 let body = encode(&tokens);
242 with_selector(
243 selector(b"settle(bytes32,bytes32,(bytes,uint256[3]),(bytes,uint256[3]))"),
244 body,
245 )
246}
247
248pub fn perc20_transfer_selector() -> [u8; 4] { selector(b"transfer((bytes,uint256[3]))") }
251pub fn perc20_transfer_executor_selector() -> [u8; 4] { selector(b"transfer(address,(bytes,uint256[3]))") }
252pub fn wrapped_shield_selector() -> [u8; 4] { selector(b"shield(uint256,(bytes,uint256[3]))") }
253pub fn wrapped_unshield_selector() -> [u8; 4] { selector(b"unshield(uint256,address,(bytes,uint256[3]))") }
254pub fn swap_initiate_selector() -> [u8; 4] { selector(b"initiateSwap(address,address,bytes32,bytes32,uint256,uint256,uint64,bytes32)") }
255pub fn swap_join_selector() -> [u8; 4] { selector(b"joinSwap(bytes32,bytes32,uint256[3])") }
256pub fn swap_settle_selector() -> [u8; 4] { selector(b"settle(bytes32,bytes32,(bytes,uint256[3]),(bytes,uint256[3]))") }
257
258#[allow(dead_code)]
261fn _assert_error_in_scope(_e: EthEncodeError) {}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 fn dummy_action() -> BundleActionArgs {
268 BundleActionArgs {
269 cmx: [1u8; 32],
270 enc_ciphertext: vec![0u8; 580],
271 out_ciphertext: vec![0u8; 80],
272 epk: [2u8; 32],
273 nf_old: [3u8; 32],
274 anchor: [4u8; 32],
275 proof: vec![0xabu8; 256],
276 pub_fields: [[5u8; 32]; 8],
277 spend_auth_sig: [[6u8; 32]; 3],
278 }
279 }
280
281 fn dummy_call() -> PrivacyCallArgs {
282 PrivacyCallArgs { actions: vec![dummy_action()], binding_sig: [[7u8; 32]; 3] }
283 }
284
285 #[test]
286 fn selectors_match_onchain() {
287 assert_eq!(perc20_transfer_selector(), [0xed, 0xa1, 0xa0, 0xac]);
288 assert_eq!(perc20_transfer_executor_selector(), [0xc7, 0xb9, 0x21, 0xd3]);
289 assert_eq!(wrapped_shield_selector(), [0x04, 0x11, 0xcb, 0xab]);
290 assert_eq!(wrapped_unshield_selector(), [0x53, 0x64, 0x4c, 0x61]);
291 assert_eq!(swap_initiate_selector(), [0x6d, 0xb7, 0x97, 0x4d]);
292 assert_eq!(swap_join_selector(), [0x8b, 0xbe, 0x82, 0x1a]);
293 assert_eq!(swap_settle_selector(), [0xc7, 0xec, 0xe1, 0x5f]);
294 }
295
296 #[test]
297 fn calldata_prefixes_correct_selector() {
298 let call = dummy_call();
299 assert_eq!(&encode_perc20_transfer_calldata(&call)[..4], &perc20_transfer_selector());
300 assert_eq!(
301 &encode_perc20_transfer_executor_calldata(&[0xEFu8; 20], &call)[..4],
302 &perc20_transfer_executor_selector()
303 );
304 assert_eq!(&encode_wrapped_shield_calldata(1000, &call)[..4], &wrapped_shield_selector());
305 assert_eq!(
306 &encode_wrapped_unshield_calldata(1000, &[0xDEu8; 20], &call)[..4],
307 &wrapped_unshield_selector()
308 );
309 }
310
311 #[test]
312 fn shield_and_transfer_share_privacy_call_tail() {
313 let call = dummy_call();
315 let transfer = encode_perc20_transfer_calldata(&call);
316 let shield = encode_wrapped_shield_calldata(1000, &call);
317 let t_tail = &transfer[4 + 32..]; let s_tail = &shield[4 + 64..]; assert_eq!(t_tail, s_tail, "PrivacyCall encoding must be reused verbatim");
323 }
324
325 #[test]
326 fn commit_is_deterministic_keccak() {
327 let call = dummy_call();
328 let c1 = privacy_call_commit(&call);
329 let c2 = privacy_call_commit(&call);
330 assert_eq!(c1, c2);
331 let mut other = call.clone();
333 other.binding_sig[2] = [0x9u8; 32];
334 assert_ne!(privacy_call_commit(&other), c1);
335 }
336
337 #[test]
338 fn swap_id_matches_abi_encode_layout() {
339 let base = compute_swap_id(&[1u8; 20], &[2u8; 20], &[3u8; 20], &[4u8; 32], &[5u8; 32], &[8u8; 32], &[9u8; 32], &[6u8; 32]);
341 assert_eq!(
342 base,
343 compute_swap_id(&[1u8; 20], &[2u8; 20], &[3u8; 20], &[4u8; 32], &[5u8; 32], &[8u8; 32], &[9u8; 32], &[6u8; 32])
344 );
345 assert_ne!(
346 base,
347 compute_swap_id(&[1u8; 20], &[2u8; 20], &[3u8; 20], &[4u8; 32], &[5u8; 32], &[8u8; 32], &[9u8; 32], &[7u8; 32])
348 );
349 assert_ne!(
351 base,
352 compute_swap_id(&[1u8; 20], &[2u8; 20], &[3u8; 20], &[4u8; 32], &[5u8; 32], &[0xAu8; 32], &[9u8; 32], &[6u8; 32])
353 );
354 }
355}