1use async_trait::async_trait;
8
9use aztec_core::abi::{
10 AbiParameter, AbiType, AbiValue, ContractArtifact, FunctionArtifact, FunctionType,
11};
12use aztec_core::fee::GasSettings;
13use aztec_core::hash::{compute_auth_wit_message_hash, ChainInfo};
14use aztec_core::tx::{AuthWitness, ExecutionPayload};
15use aztec_core::types::{AztecAddress, CompleteAddress, Fr, Point};
16use aztec_core::Error;
17
18use crate::entrypoint::account_entrypoint::AccountFeePaymentMethodOptions;
19use aztec_crypto::keys::{derive_public_key_from_secret_key, derive_signing_key};
20use aztec_crypto::schnorr::schnorr_sign;
21
22use crate::account::{
23 Account, AccountContract, AuthorizationProvider, EntrypointOptions, TxExecutionRequest,
24};
25use crate::entrypoint::{DefaultAccountEntrypoint, DefaultAccountEntrypointOptions};
26use crate::wallet::{ChainInfo as WalletChainInfo, MessageHashOrIntent};
27
28fn resolve_message_hash(intent: &MessageHashOrIntent, chain_info: &WalletChainInfo) -> Fr {
29 match intent {
30 MessageHashOrIntent::Hash { hash } => *hash,
31 MessageHashOrIntent::Intent { .. } | MessageHashOrIntent::InnerHash { .. } => {
32 let core_chain_info = ChainInfo {
33 chain_id: chain_info.chain_id,
34 version: chain_info.version,
35 };
36 compute_auth_wit_message_hash(intent, &core_chain_info)
37 }
38 }
39}
40
41pub struct SchnorrAuthorizationProvider {
47 signing_key: aztec_core::types::GrumpkinScalar,
48}
49
50#[async_trait]
51impl AuthorizationProvider for SchnorrAuthorizationProvider {
52 async fn create_auth_wit(
53 &self,
54 intent: MessageHashOrIntent,
55 chain_info: &WalletChainInfo,
56 ) -> Result<AuthWitness, Error> {
57 let message_hash = resolve_message_hash(&intent, chain_info);
58 let signature = schnorr_sign(&self.signing_key, &message_hash);
59
60 Ok(AuthWitness {
61 request_hash: message_hash,
62 fields: signature.to_fields(),
63 })
64 }
65}
66
67pub struct SchnorrAccount {
73 address: CompleteAddress,
74 signing_key: aztec_core::types::GrumpkinScalar,
75 entrypoint: DefaultAccountEntrypoint,
76}
77
78#[async_trait]
79impl AuthorizationProvider for SchnorrAccount {
80 async fn create_auth_wit(
81 &self,
82 intent: MessageHashOrIntent,
83 chain_info: &WalletChainInfo,
84 ) -> Result<AuthWitness, Error> {
85 let message_hash = resolve_message_hash(&intent, chain_info);
86 let signature = schnorr_sign(&self.signing_key, &message_hash);
87
88 Ok(AuthWitness {
89 request_hash: message_hash,
90 fields: signature.to_fields(),
91 })
92 }
93}
94
95#[async_trait]
96impl Account for SchnorrAccount {
97 fn complete_address(&self) -> &CompleteAddress {
98 &self.address
99 }
100
101 fn address(&self) -> AztecAddress {
102 self.address.address
103 }
104
105 async fn create_tx_execution_request(
106 &self,
107 exec: ExecutionPayload,
108 gas_settings: GasSettings,
109 chain_info: &WalletChainInfo,
110 options: EntrypointOptions,
111 ) -> Result<TxExecutionRequest, Error> {
112 let core_chain_info = ChainInfo {
113 chain_id: chain_info.chain_id,
114 version: chain_info.version,
115 };
116 let mut ep_opts = DefaultAccountEntrypointOptions::default();
117 if let Some(method) = options.fee_payment_method {
118 ep_opts.fee_payment_method_options = match method {
119 1 => AccountFeePaymentMethodOptions::PreexistingFeeJuice,
120 2 => AccountFeePaymentMethodOptions::FeeJuiceWithClaim,
121 _ => AccountFeePaymentMethodOptions::External,
122 };
123 }
124 self.entrypoint
125 .create_tx_execution_request(exec, gas_settings, &core_chain_info, &ep_opts)
126 .await
127 }
128
129 async fn wrap_execution_payload(
130 &self,
131 exec: ExecutionPayload,
132 _options: EntrypointOptions,
133 ) -> Result<ExecutionPayload, Error> {
134 let chain_info = ChainInfo {
135 chain_id: Fr::from(0u64),
136 version: Fr::from(0u64),
137 };
138 self.entrypoint
139 .wrap_execution_payload(
140 exec,
141 &chain_info,
142 &DefaultAccountEntrypointOptions::default(),
143 )
144 .await
145 }
146}
147
148pub struct SchnorrAccountContract {
177 secret_key: Fr,
178 signing_key: aztec_core::types::GrumpkinScalar,
179 signing_public_key: Point,
180}
181
182impl SchnorrAccountContract {
183 pub fn new(secret_key: Fr) -> Self {
188 let signing_key = derive_signing_key(&secret_key);
189 let signing_public_key = derive_public_key_from_secret_key(&signing_key);
190 Self {
191 secret_key,
192 signing_key,
193 signing_public_key,
194 }
195 }
196
197 pub fn new_with_signing_key(
206 secret_key: Fr,
207 signing_key: aztec_core::types::GrumpkinScalar,
208 ) -> Self {
209 let signing_public_key = derive_public_key_from_secret_key(&signing_key);
210 Self {
211 secret_key,
212 signing_key,
213 signing_public_key,
214 }
215 }
216
217 pub fn signing_public_key(&self) -> &Point {
219 &self.signing_public_key
220 }
221
222 pub fn secret_key(&self) -> Fr {
224 self.secret_key
225 }
226
227 fn constructor_artifact() -> FunctionArtifact {
228 FunctionArtifact {
232 name: "constructor".to_owned(),
233 function_type: FunctionType::Private,
234 is_initializer: true,
235 is_static: false,
236 parameters: vec![
237 AbiParameter {
238 name: "signing_pub_key_x".to_owned(),
239 typ: AbiType::Field,
240 visibility: None,
241 },
242 AbiParameter {
243 name: "signing_pub_key_y".to_owned(),
244 typ: AbiType::Field,
245 visibility: None,
246 },
247 ],
248 return_types: vec![],
249 selector: None,
250 bytecode: None,
251 verification_key_hash: None,
252 verification_key: None,
253 custom_attributes: None,
254 is_unconstrained: None,
255 debug_symbols: None,
256 error_types: None,
257 is_only_self: None,
258 }
259 }
260}
261
262use crate::account::InitializationSpec;
263
264#[async_trait]
265impl AccountContract for SchnorrAccountContract {
266 async fn contract_artifact(&self) -> Result<ContractArtifact, Error> {
267 let entrypoint_abi = DefaultAccountEntrypoint::entrypoint_abi();
268
269 Ok(ContractArtifact {
270 name: "SchnorrAccount".to_owned(),
271 functions: vec![Self::constructor_artifact(), entrypoint_abi],
272 outputs: None,
273 file_map: None,
274 context_inputs_sizes: None,
275 })
276 }
277
278 async fn initialization_function_and_args(&self) -> Result<Option<InitializationSpec>, Error> {
279 Ok(Some(InitializationSpec {
280 constructor_name: "constructor".to_owned(),
281 constructor_args: vec![
282 AbiValue::Field(self.signing_public_key.x),
283 AbiValue::Field(self.signing_public_key.y),
284 ],
285 }))
286 }
287
288 fn account(&self, address: CompleteAddress) -> Box<dyn Account> {
289 let auth = self.auth_witness_provider(address.clone());
290 let entrypoint = DefaultAccountEntrypoint::new(address.address, auth);
291 Box::new(SchnorrAccount {
292 address,
293 signing_key: self.signing_key,
294 entrypoint,
295 })
296 }
297
298 fn auth_witness_provider(&self, _address: CompleteAddress) -> Box<dyn AuthorizationProvider> {
299 Box::new(SchnorrAuthorizationProvider {
300 signing_key: self.signing_key,
301 })
302 }
303}
304
305#[cfg(test)]
306#[allow(clippy::unwrap_used, clippy::expect_used)]
307mod tests {
308 use super::*;
309 use crate::account::AccountManager;
310 use crate::wallet::MockWallet;
311 use aztec_core::abi::{FunctionSelector, FunctionType};
312 use aztec_core::tx::FunctionCall;
313 use aztec_core::types::AztecAddress;
314
315 fn sample_chain_info() -> WalletChainInfo {
316 WalletChainInfo {
317 chain_id: Fr::from(31337u64),
318 version: Fr::from(1u64),
319 }
320 }
321
322 #[test]
323 fn new_derives_keys() {
324 let contract = SchnorrAccountContract::new(Fr::from(12345u64));
325 let pk = contract.signing_public_key();
326 assert!(!pk.is_zero());
327 assert!(!pk.is_infinite);
328 }
329
330 #[tokio::test]
331 async fn contract_artifact_has_constructor_and_entrypoint() {
332 let contract = SchnorrAccountContract::new(Fr::from(12345u64));
333 let artifact = contract.contract_artifact().await.expect("artifact");
334 assert_eq!(artifact.name, "SchnorrAccount");
335 assert_eq!(artifact.functions.len(), 2);
336 assert_eq!(artifact.functions[0].name, "constructor");
337 assert!(artifact.functions[0].is_initializer);
338 assert_eq!(artifact.functions[1].name, "entrypoint");
339 }
340
341 #[tokio::test]
342 async fn initialization_spec_contains_public_key() {
343 let contract = SchnorrAccountContract::new(Fr::from(12345u64));
344 let spec = contract
345 .initialization_function_and_args()
346 .await
347 .expect("init spec")
348 .expect("should have spec");
349 assert_eq!(spec.constructor_name, "constructor");
350 assert_eq!(spec.constructor_args.len(), 2);
351 }
352
353 #[tokio::test]
354 async fn auth_provider_creates_real_signature() {
355 let contract = SchnorrAccountContract::new(Fr::from(12345u64));
356 let addr = CompleteAddress::default();
357 let provider = contract.auth_witness_provider(addr);
358 let chain_info = sample_chain_info();
359
360 let wit = provider
361 .create_auth_wit(
362 MessageHashOrIntent::Hash {
363 hash: Fr::from(42u64),
364 },
365 &chain_info,
366 )
367 .await
368 .expect("create auth wit");
369
370 assert_eq!(wit.fields.len(), 64);
372 assert_eq!(wit.request_hash, Fr::from(42u64));
374 }
375
376 #[tokio::test]
377 async fn signature_is_verifiable() {
378 let secret = Fr::from(8923u64);
379 let contract = SchnorrAccountContract::new(secret);
380 let addr = CompleteAddress::default();
381 let provider = contract.auth_witness_provider(addr);
382 let chain_info = sample_chain_info();
383
384 let message = Fr::from(999u64);
385 let wit = provider
386 .create_auth_wit(MessageHashOrIntent::Hash { hash: message }, &chain_info)
387 .await
388 .expect("create auth wit");
389
390 let sig_bytes: Vec<u8> = wit.fields.iter().map(|f| f.to_usize() as u8).collect();
392 let mut sig_arr = [0u8; 64];
393 sig_arr.copy_from_slice(&sig_bytes);
394 let sig = aztec_crypto::schnorr::SchnorrSignature::from_bytes(&sig_arr);
395
396 let pk = contract.signing_public_key();
398 assert!(aztec_crypto::schnorr::schnorr_verify(pk, &message, &sig));
399 }
400
401 #[tokio::test]
402 async fn intent_variant_is_hashed_before_signing() {
403 let secret = Fr::from(4242u64);
404 let contract = SchnorrAccountContract::new(secret);
405 let provider = contract.auth_witness_provider(CompleteAddress::default());
406 let chain_info = sample_chain_info();
407 let intent = MessageHashOrIntent::Intent {
408 caller: AztecAddress::from(1u64),
409 call: FunctionCall {
410 to: AztecAddress::from(2u64),
411 selector: FunctionSelector::from_hex("0x11223344").expect("valid selector"),
412 args: vec![AbiValue::Field(Fr::from(7u64))],
413 function_type: FunctionType::Private,
414 is_static: false,
415 hide_msg_sender: false,
416 },
417 };
418
419 let wit = provider
420 .create_auth_wit(intent.clone(), &chain_info)
421 .await
422 .expect("create auth wit");
423
424 let expected = compute_auth_wit_message_hash(
425 &intent,
426 &ChainInfo {
427 chain_id: chain_info.chain_id,
428 version: chain_info.version,
429 },
430 );
431 assert_eq!(wit.request_hash, expected);
432 }
433
434 #[tokio::test]
435 async fn account_manager_integration() {
436 let wallet = MockWallet::new(sample_chain_info());
437 let secret = Fr::from(12345u64);
438 let contract = SchnorrAccountContract::new(secret);
439
440 let manager = AccountManager::create(wallet, secret, Box::new(contract), None::<Fr>)
441 .await
442 .expect("create manager");
443
444 assert_ne!(manager.address(), AztecAddress::zero());
445 assert!(manager.has_initializer());
446
447 let account = manager.account().await.expect("get account");
448 assert_eq!(account.address(), manager.address());
449 }
450
451 #[tokio::test]
452 async fn deploy_method_builds_payload() {
453 let wallet = MockWallet::new(sample_chain_info());
454 let secret = Fr::from(12345u64);
455 let contract = SchnorrAccountContract::new(secret);
456
457 let manager = AccountManager::create(wallet, secret, Box::new(contract), None::<Fr>)
458 .await
459 .expect("create manager");
460
461 let deploy = manager.deploy_method().await.expect("deploy method");
462 let opts = crate::account::DeployAccountOptions {
463 skip_registration: true,
464 ..Default::default()
465 };
466 let payload = deploy.request(&opts).await.expect("deploy payload");
467 assert!(!payload.calls.is_empty());
468 }
469}