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(
145 initiator: &[u8; 20],
146 pool_a: &[u8; 20],
147 pool_b: &[u8; 20],
148 htlc_hash: &[u8; 32],
149 commit_a: &[u8; 32],
150 salt: &[u8; 32],
151) -> [u8; 32] {
152 let encoded = encode(&[
153 Token::Address(ethabi::Address::from(*initiator)),
154 Token::Address(ethabi::Address::from(*pool_a)),
155 Token::Address(ethabi::Address::from(*pool_b)),
156 Token::FixedBytes(htlc_hash.to_vec()),
157 Token::FixedBytes(commit_a.to_vec()),
158 Token::FixedBytes(salt.to_vec()),
159 ]);
160 Keccak256::digest(&encoded).into()
161}
162
163pub fn encode_swap_initiate_calldata(
165 pool_a: &[u8; 20],
166 pool_b: &[u8; 20],
167 htlc_hash: &[u8; 32],
168 commit_a: &[u8; 32],
169 deadline: u64,
170 salt: &[u8; 32],
171) -> Vec<u8> {
172 let tokens = vec![
173 Token::Address(ethabi::Address::from(*pool_a)),
174 Token::Address(ethabi::Address::from(*pool_b)),
175 Token::FixedBytes(htlc_hash.to_vec()),
176 Token::FixedBytes(commit_a.to_vec()),
177 Token::Uint(Uint::from(deadline)),
178 Token::FixedBytes(salt.to_vec()),
179 ];
180 let body = encode(&tokens);
181 with_selector(
182 selector(b"initiateSwap(address,address,bytes32,bytes32,uint64,bytes32)"),
183 body,
184 )
185}
186
187pub fn encode_swap_join_calldata(
191 swap_id: &[u8; 32],
192 commit_b: &[u8; 32],
193 rk_bx: &[u8; 32],
194 rk_by: &[u8; 32],
195 joiner_sig: &[[u8; 32]; 3],
196) -> Vec<u8> {
197 let joiner_sig_token = Token::FixedArray(
198 joiner_sig
199 .iter()
200 .map(|b| Token::Uint(Uint::from_big_endian(b)))
201 .collect(),
202 );
203 let tokens = vec![
204 Token::FixedBytes(swap_id.to_vec()),
205 Token::FixedBytes(commit_b.to_vec()),
206 Token::Uint(Uint::from_big_endian(rk_bx)),
207 Token::Uint(Uint::from_big_endian(rk_by)),
208 joiner_sig_token,
209 ];
210 let body = encode(&tokens);
211 with_selector(
212 selector(b"joinSwap(bytes32,bytes32,uint256,uint256,uint256[3])"),
213 body,
214 )
215}
216
217pub fn encode_swap_settle_calldata(
220 swap_id: &[u8; 32],
221 secret: &[u8; 32],
222 call_a: &PrivacyCallArgs,
223 call_b: &PrivacyCallArgs,
224) -> Vec<u8> {
225 let tokens = vec![
226 Token::FixedBytes(swap_id.to_vec()),
227 Token::FixedBytes(secret.to_vec()),
228 privacy_call_token(call_a),
229 privacy_call_token(call_b),
230 ];
231 let body = encode(&tokens);
232 with_selector(
233 selector(b"settle(bytes32,bytes32,(bytes,uint256[3]),(bytes,uint256[3]))"),
234 body,
235 )
236}
237
238pub fn perc20_transfer_selector() -> [u8; 4] { selector(b"transfer((bytes,uint256[3]))") }
241pub fn perc20_transfer_executor_selector() -> [u8; 4] { selector(b"transfer(address,(bytes,uint256[3]))") }
242pub fn wrapped_shield_selector() -> [u8; 4] { selector(b"shield(uint256,(bytes,uint256[3]))") }
243pub fn wrapped_unshield_selector() -> [u8; 4] { selector(b"unshield(uint256,address,(bytes,uint256[3]))") }
244pub fn swap_initiate_selector() -> [u8; 4] { selector(b"initiateSwap(address,address,bytes32,bytes32,uint64,bytes32)") }
245pub fn swap_join_selector() -> [u8; 4] { selector(b"joinSwap(bytes32,bytes32,uint256,uint256,uint256[3])") }
246pub fn swap_settle_selector() -> [u8; 4] { selector(b"settle(bytes32,bytes32,(bytes,uint256[3]),(bytes,uint256[3]))") }
247
248#[allow(dead_code)]
251fn _assert_error_in_scope(_e: EthEncodeError) {}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256
257 fn dummy_action() -> BundleActionArgs {
258 BundleActionArgs {
259 cmx: [1u8; 32],
260 enc_ciphertext: vec![0u8; 580],
261 out_ciphertext: vec![0u8; 80],
262 epk: [2u8; 32],
263 nf_old: [3u8; 32],
264 anchor: [4u8; 32],
265 proof: vec![0xabu8; 256],
266 pub_fields: [[5u8; 32]; 8],
267 spend_auth_sig: [[6u8; 32]; 3],
268 }
269 }
270
271 fn dummy_call() -> PrivacyCallArgs {
272 PrivacyCallArgs { actions: vec![dummy_action()], binding_sig: [[7u8; 32]; 3] }
273 }
274
275 #[test]
276 fn selectors_match_onchain() {
277 assert_eq!(perc20_transfer_selector(), [0xed, 0xa1, 0xa0, 0xac]);
278 assert_eq!(perc20_transfer_executor_selector(), [0xc7, 0xb9, 0x21, 0xd3]);
279 assert_eq!(wrapped_shield_selector(), [0x04, 0x11, 0xcb, 0xab]);
280 assert_eq!(wrapped_unshield_selector(), [0x53, 0x64, 0x4c, 0x61]);
281 assert_eq!(swap_initiate_selector(), [0x1e, 0x17, 0x9f, 0x2a]);
282 assert_eq!(swap_join_selector(), [0x25, 0x6e, 0x19, 0x50]);
283 assert_eq!(swap_settle_selector(), [0xc7, 0xec, 0xe1, 0x5f]);
284 }
285
286 #[test]
287 fn calldata_prefixes_correct_selector() {
288 let call = dummy_call();
289 assert_eq!(&encode_perc20_transfer_calldata(&call)[..4], &perc20_transfer_selector());
290 assert_eq!(
291 &encode_perc20_transfer_executor_calldata(&[0xEFu8; 20], &call)[..4],
292 &perc20_transfer_executor_selector()
293 );
294 assert_eq!(&encode_wrapped_shield_calldata(1000, &call)[..4], &wrapped_shield_selector());
295 assert_eq!(
296 &encode_wrapped_unshield_calldata(1000, &[0xDEu8; 20], &call)[..4],
297 &wrapped_unshield_selector()
298 );
299 }
300
301 #[test]
302 fn shield_and_transfer_share_privacy_call_tail() {
303 let call = dummy_call();
305 let transfer = encode_perc20_transfer_calldata(&call);
306 let shield = encode_wrapped_shield_calldata(1000, &call);
307 let t_tail = &transfer[4 + 32..]; let s_tail = &shield[4 + 64..]; assert_eq!(t_tail, s_tail, "PrivacyCall encoding must be reused verbatim");
313 }
314
315 #[test]
316 fn commit_is_deterministic_keccak() {
317 let call = dummy_call();
318 let c1 = privacy_call_commit(&call);
319 let c2 = privacy_call_commit(&call);
320 assert_eq!(c1, c2);
321 let mut other = call.clone();
323 other.binding_sig[2] = [0x9u8; 32];
324 assert_ne!(privacy_call_commit(&other), c1);
325 }
326
327 #[test]
328 fn swap_id_matches_abi_encode_layout() {
329 let base = compute_swap_id(&[1u8; 20], &[2u8; 20], &[3u8; 20], &[4u8; 32], &[5u8; 32], &[6u8; 32]);
331 assert_eq!(
332 base,
333 compute_swap_id(&[1u8; 20], &[2u8; 20], &[3u8; 20], &[4u8; 32], &[5u8; 32], &[6u8; 32])
334 );
335 assert_ne!(
336 base,
337 compute_swap_id(&[1u8; 20], &[2u8; 20], &[3u8; 20], &[4u8; 32], &[5u8; 32], &[7u8; 32])
338 );
339 }
340}