aztec_account/entrypoint/
encoding.rs1use aztec_core::abi::FunctionType;
7use aztec_core::constants::domain_separator;
8use aztec_core::hash::{abi_values_to_fields, poseidon2_hash_with_separator};
9use aztec_core::tx::{FunctionCall, HashedValues};
10use aztec_core::types::Fr;
11use aztec_core::Error;
12
13pub const APP_MAX_CALLS: usize = 5;
15
16#[derive(Clone, Debug)]
18struct EncodedCall {
19 args_hash: Fr,
20 function_selector: Fr,
21 target_address: Fr,
22 is_public: bool,
23 hide_msg_sender: bool,
24 is_static: bool,
25}
26
27#[derive(Clone, Copy, Debug)]
29pub struct EncodedCallView {
30 pub args_hash: Fr,
32 pub function_selector: Fr,
34 pub target_address: Fr,
36 pub is_public: bool,
38 pub hide_msg_sender: bool,
40 pub is_static: bool,
42}
43
44impl EncodedCall {
45 fn to_fields(&self) -> Vec<Fr> {
47 vec![
48 self.args_hash,
49 self.function_selector,
50 self.target_address,
51 Fr::from(self.is_public),
52 Fr::from(self.hide_msg_sender),
53 Fr::from(self.is_static),
54 ]
55 }
56}
57
58pub struct EncodedAppEntrypointCalls {
60 encoded_calls: Vec<EncodedCall>,
61 tx_nonce: Fr,
62 hashed_args_list: Vec<HashedValues>,
63}
64
65impl EncodedAppEntrypointCalls {
66 pub fn create(calls: &[FunctionCall], tx_nonce: Option<Fr>) -> Result<Self, Error> {
70 if calls.len() > APP_MAX_CALLS {
71 return Err(Error::InvalidData(format!(
72 "Too many calls: {} > {}",
73 calls.len(),
74 APP_MAX_CALLS
75 )));
76 }
77
78 let tx_nonce = tx_nonce.unwrap_or_else(Fr::random);
79 let mut encoded_calls = Vec::with_capacity(APP_MAX_CALLS);
80 let mut hashed_args_list = Vec::with_capacity(APP_MAX_CALLS);
81 let padded_calls = calls
82 .iter()
83 .cloned()
84 .chain(std::iter::repeat_with(FunctionCall::empty).take(APP_MAX_CALLS - calls.len()));
85
86 for call in padded_calls {
87 let is_public = call.function_type == FunctionType::Public;
88 let arg_fields = abi_values_to_fields(&call.args);
89
90 let (args_hash, hashed_values) = if is_public {
91 let mut calldata = vec![call.selector.to_field()];
92 calldata.extend_from_slice(&arg_fields);
93 let hv = HashedValues::from_calldata(calldata);
94 let h = hv.hash();
95 (h, hv)
96 } else {
97 let hv = HashedValues::from_args(arg_fields);
98 let h = hv.hash();
99 (h, hv)
100 };
101
102 hashed_args_list.push(hashed_values);
103
104 encoded_calls.push(EncodedCall {
105 args_hash,
106 function_selector: call.selector.to_field(),
107 target_address: call.to.0,
108 is_public,
109 hide_msg_sender: call.hide_msg_sender,
110 is_static: call.is_static,
111 });
112 }
113
114 Ok(Self {
115 encoded_calls,
116 tx_nonce,
117 hashed_args_list,
118 })
119 }
120
121 pub fn to_fields(&self) -> Vec<Fr> {
125 let mut fields = Vec::new();
126 for call in &self.encoded_calls {
127 fields.extend(call.to_fields());
128 }
129 fields.push(self.tx_nonce);
130 fields
131 }
132
133 pub fn hash(&self) -> Fr {
135 let fields = self.to_fields();
136 poseidon2_hash_with_separator(&fields, domain_separator::SIGNATURE_PAYLOAD)
137 }
138
139 pub fn hashed_args(&self) -> &[HashedValues] {
141 &self.hashed_args_list
142 }
143
144 pub fn encoded_calls(&self) -> Vec<EncodedCallView> {
146 self.encoded_calls
147 .iter()
148 .map(|call| EncodedCallView {
149 args_hash: call.args_hash,
150 function_selector: call.function_selector,
151 target_address: call.target_address,
152 is_public: call.is_public,
153 hide_msg_sender: call.hide_msg_sender,
154 is_static: call.is_static,
155 })
156 .collect()
157 }
158
159 pub fn tx_nonce(&self) -> Fr {
161 self.tx_nonce
162 }
163}
164
165#[cfg(test)]
166#[allow(clippy::unwrap_used, clippy::expect_used)]
167mod tests {
168 use super::*;
169 use aztec_core::abi::{AbiValue, FunctionSelector, FunctionType};
170 use aztec_core::types::AztecAddress;
171
172 fn make_private_call(addr: u64, selector_hex: &str) -> FunctionCall {
173 FunctionCall {
174 to: AztecAddress::from(addr),
175 selector: FunctionSelector::from_hex(selector_hex).expect("valid"),
176 args: vec![AbiValue::Field(Fr::from(42u64))],
177 function_type: FunctionType::Private,
178 is_static: false,
179 hide_msg_sender: false,
180 }
181 }
182
183 fn make_public_call(addr: u64, selector_hex: &str) -> FunctionCall {
184 FunctionCall {
185 to: AztecAddress::from(addr),
186 selector: FunctionSelector::from_hex(selector_hex).expect("valid"),
187 args: vec![AbiValue::Field(Fr::from(99u64))],
188 function_type: FunctionType::Public,
189 is_static: false,
190 hide_msg_sender: false,
191 }
192 }
193
194 #[test]
195 fn encode_single_private_call() {
196 let call = make_private_call(1, "0x11223344");
197 let encoded =
198 EncodedAppEntrypointCalls::create(&[call], Some(Fr::from(1u64))).expect("encode");
199
200 let fields = encoded.to_fields();
201 assert_eq!(fields.len(), 31);
203 assert_ne!(fields[0], Fr::zero());
205 assert_eq!(*fields.last().unwrap(), Fr::from(1u64));
207 }
208
209 #[test]
210 fn encode_pads_to_max_calls() {
211 let call = make_private_call(1, "0x11223344");
212 let encoded =
213 EncodedAppEntrypointCalls::create(&[call], Some(Fr::from(1u64))).expect("encode");
214
215 assert_eq!(encoded.encoded_calls.len(), APP_MAX_CALLS);
216 for i in 1..APP_MAX_CALLS {
218 assert_eq!(encoded.encoded_calls[i].target_address, Fr::zero());
219 }
220 }
221
222 #[test]
223 fn encode_multiple_calls() {
224 let calls = vec![
225 make_private_call(1, "0x11111111"),
226 make_public_call(2, "0x22222222"),
227 ];
228 let encoded =
229 EncodedAppEntrypointCalls::create(&calls, Some(Fr::from(1u64))).expect("encode");
230
231 assert_eq!(encoded.hashed_args().len(), APP_MAX_CALLS);
232 assert!(encoded.encoded_calls[1].is_public);
234 }
235
236 #[test]
237 fn encode_rejects_too_many_calls() {
238 let calls: Vec<_> = (0..6)
239 .map(|i| make_private_call(i + 1, "0x11223344"))
240 .collect();
241 let result = EncodedAppEntrypointCalls::create(&calls, None);
242 assert!(result.is_err());
243 }
244
245 #[test]
246 fn hash_is_deterministic() {
247 let call = make_private_call(1, "0x11223344");
248 let nonce = Fr::from(42u64);
249
250 let h1 = EncodedAppEntrypointCalls::create(&[call.clone()], Some(nonce))
251 .expect("encode")
252 .hash();
253 let h2 = EncodedAppEntrypointCalls::create(&[call], Some(nonce))
254 .expect("encode")
255 .hash();
256
257 assert_eq!(h1, h2);
258 }
259
260 #[test]
261 fn different_nonce_different_hash() {
262 let call = make_private_call(1, "0x11223344");
263
264 let h1 = EncodedAppEntrypointCalls::create(&[call.clone()], Some(Fr::from(1u64)))
265 .expect("encode")
266 .hash();
267 let h2 = EncodedAppEntrypointCalls::create(&[call], Some(Fr::from(2u64)))
268 .expect("encode")
269 .hash();
270
271 assert_ne!(h1, h2);
272 }
273
274 #[test]
275 fn encode_mix_of_public_and_private() {
276 let calls = vec![
277 make_private_call(1, "0x11111111"),
278 make_public_call(2, "0x22222222"),
279 make_private_call(3, "0x33333333"),
280 ];
281 let encoded =
282 EncodedAppEntrypointCalls::create(&calls, Some(Fr::from(1u64))).expect("encode");
283
284 assert!(!encoded.encoded_calls[0].is_public);
285 assert!(encoded.encoded_calls[1].is_public);
286 assert!(!encoded.encoded_calls[2].is_public);
287 assert_eq!(encoded.hashed_args().len(), APP_MAX_CALLS);
288 }
289}