Skip to content

Commit fa29d0e

Browse files
committed
refactor(authwit): split into utils/private/public
1 parent c312b64 commit fa29d0e

24 files changed

Lines changed: 459 additions & 518 deletions

File tree

docs/docs-developers/docs/aztec-nr/framework-description/authentication_witnesses.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ The `aztec` library includes authwit functionality. Import the necessary compone
2121

2222
```rust
2323
use aztec::{
24-
authwit::auth::{compute_authwit_message_hash_from_call, set_authorized},
24+
authwit::{utils::compute_authwit_message_hash_from_call, public::set_authorized},
2525
macros::functions::authorize_once,
2626
};
2727
```

docs/examples/contracts/example_uniswap/src/main.nr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ use aztec::macros::aztec;
99
#[aztec]
1010
pub contract ExampleUniswap {
1111
use aztec::{
12-
authwit::auth::{
13-
assert_current_call_valid_authwit_public, compute_authwit_message_hash_from_call,
14-
set_authorized,
12+
authwit::{
13+
common::compute_authwit_message_hash_from_call,
14+
public::{assert_current_call_valid_authwit_public, set_authorized},
1515
},
1616
macros::{functions::{external, initializer, only_self}, storage::storage},
1717
protocol::{

noir-projects/aztec-nr/aztec/src/authwit/account.nr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use crate::context::PrivateContext;
22

33
use crate::protocol::{constants::DOM_SEP__TX_NULLIFIER, hash::poseidon2_hash_with_separator, traits::Hash};
44

5-
use crate::authwit::auth::{compute_authwit_message_hash, IS_VALID_SELECTOR};
65
use crate::authwit::entrypoint::app::AppPayload;
6+
use crate::authwit::utils::{compute_authwit_message_hash, IS_VALID_SELECTOR};
77

88
pub struct AccountActions<Context> {
99
context: Context,

noir-projects/aztec-nr/aztec/src/authwit/auth.nr

Lines changed: 0 additions & 423 deletions
This file was deleted.

noir-projects/aztec-nr/aztec/src/authwit/mod.nr

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,7 @@ pub mod account;
44
pub mod authorization_interface;
55
mod authorization_selector;
66
pub use authorization_selector::AuthorizationSelector;
7-
pub mod auth;
7+
pub mod utils;
8+
pub mod private;
9+
pub mod public;
810
pub mod entrypoint;
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
use crate::{
2+
authwit::utils::{
3+
CallAuthorization, compute_authwit_nullifier, compute_inner_authwit_hash, emit_authorization_as_offchain_effect,
4+
IS_VALID_SELECTOR,
5+
},
6+
context::PrivateContext,
7+
};
8+
use crate::protocol::{abis::function_selector::FunctionSelector, address::AztecAddress, traits::Serialize};
9+
10+
/// Private-flow authwit helpers.
11+
///
12+
/// Say that a user `Alice` wants to deposit some tokens into a DeFi protocol (say a DEX). `Alice` would make a
13+
/// `deposit` transaction, that she is executing using her account contract. The account would call the `DeFi`
14+
/// contract to execute `deposit`, which would try to pull funds from the `Token` contract. Since the `DeFi` contract
15+
/// is trying to pull funds from an account that is not its own, it needs to convince the `Token` contract that it is
16+
/// allowed to do so.
17+
///
18+
/// This is where the authentication witness comes in. The `Token` contract computes a `message_hash` from the
19+
/// `transfer` call, and then asks `Alice Account` contract to verify that the `DeFi` contract is allowed to execute
20+
/// that call.
21+
///
22+
/// `Alice Account` contract can then ask `Alice` if she wants to allow the `DeFi` contract to pull funds from her
23+
/// account. If she does, she will sign the `message_hash` and return the signature to the `Alice Account` which will
24+
/// validate it and return success to the `Token` contract which will then allow the `DeFi` contract to pull funds
25+
/// from `Alice`.
26+
///
27+
/// To ensure that the same "approval" cannot be used multiple times, we also compute a `nullifier` for the
28+
/// authentication witness, and emit it from the `Token` contract (consumer).
29+
///
30+
/// Note that we can do this flow as we are in private where we can do oracle calls out from contracts.
31+
///
32+
/// Person Contract Contract Contract
33+
/// Alice Alice Account Token DeFi
34+
/// | | | |
35+
/// | Defi.deposit(Token, 1000) | |
36+
/// |----------------->| | |
37+
/// | | deposit(Token, 1000) |
38+
/// | |---------------------------------------->|
39+
/// | | | |
40+
/// | | | transfer(Alice, Defi, 1000)
41+
/// | | |<---------------------|
42+
/// | | | |
43+
/// | | Check if Defi may call transfer(Alice, Defi, 1000)
44+
/// | |<-----------------| |
45+
/// | | | |
46+
/// | Please give me AuthWit for DeFi | |
47+
/// | calling transfer(Alice, Defi, 1000) | |
48+
/// |<-----------------| | |
49+
/// | | | |
50+
/// | | | |
51+
/// | AuthWit for transfer(Alice, Defi, 1000) |
52+
/// |----------------->| | |
53+
/// | | AuthWit validity | |
54+
/// | |----------------->| |
55+
/// | | | |
56+
/// | | throw if invalid AuthWit |
57+
/// | | | |
58+
/// | | emit AuthWit nullifier |
59+
/// | | | |
60+
/// | | transfer(Alice, Defi, 1000) |
61+
/// | | | |
62+
/// | | | |
63+
/// | | | success |
64+
/// | | |--------------------->|
65+
/// | | | |
66+
/// | | | |
67+
/// | | | deposit(Token, 1000)
68+
/// | | | |
69+
70+
/// Assert that `on_behalf_of` has authorized the current call with a valid authentication witness
71+
///
72+
/// Compute the `inner_hash` using the `msg_sender`, `selector` and `args_hash` and then make a call out to the
73+
/// `on_behalf_of` contract to verify that the `inner_hash` is valid.
74+
///
75+
/// Additionally, this function emits the identifying information of the call as an offchain effect so PXE can rely the
76+
/// information to the user/wallet in a readable way. To that effect, it is generic over N, where N is the number of
77+
/// arguments the authorized functions takes. This is used to load the arguments from the execution cache. This
78+
/// function is intended to be called via a macro, which will use the turbofish operator to specify the number of
79+
/// arguments.
80+
///
81+
/// @param on_behalf_of The address that has allegedly authorized the current call
82+
pub fn assert_current_call_valid_authwit<let N: u32>(context: &mut PrivateContext, on_behalf_of: AztecAddress) {
83+
let args_hash: Field = context.get_args_hash();
84+
85+
let authorization =
86+
CallAuthorization { msg_sender: context.maybe_msg_sender().unwrap(), selector: context.selector(), args_hash };
87+
let inner_hash = compute_inner_authwit_hash(authorization.serialize());
88+
// Safety: Offchain effects are by definition unconstrained. They are emitted via an oracle which we don't use for
89+
// anything besides its side effects, therefore this is safe to call.
90+
unsafe { emit_authorization_as_offchain_effect::<N>(authorization, inner_hash, on_behalf_of) };
91+
92+
assert_inner_hash_valid_authwit(context, on_behalf_of, inner_hash);
93+
}
94+
95+
/// Assert that a specific `inner_hash` is valid for the `on_behalf_of` address
96+
///
97+
/// Used as an internal function for `assert_current_call_valid_authwit` and can be used as a standalone function when
98+
/// the `inner_hash` is from a different source, e.g., say a block of text etc.
99+
///
100+
/// @param on_behalf_of The address that has allegedly authorized the current call @param inner_hash The hash of the
101+
/// message to authorize
102+
pub fn assert_inner_hash_valid_authwit(context: &mut PrivateContext, on_behalf_of: AztecAddress, inner_hash: Field) {
103+
// We perform a static call here and not a standard one to ensure that the account contract cannot re-enter.
104+
let result: Field = context
105+
.static_call_private_function(
106+
on_behalf_of,
107+
comptime { FunctionSelector::from_signature("verify_private_authwit(Field)") },
108+
[inner_hash],
109+
)
110+
.get_preimage();
111+
assert(result == IS_VALID_SELECTOR, "Message not authorized by account");
112+
// Compute the nullifier, similar computation to the outer hash, but without the chain_id and version. Those should
113+
// already be handled in the verification, so we just need something to nullify, that allows the same inner_hash
114+
// for multiple actors.
115+
let nullifier = compute_authwit_nullifier(on_behalf_of, inner_hash);
116+
context.push_nullifier(nullifier);
117+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
use crate::{authwit::utils::{compute_inner_authwit_hash, IS_VALID_SELECTOR}, context::{gas::GasOpts, PublicContext}};
2+
use crate::protocol::{abis::function_selector::FunctionSelector, address::AztecAddress, traits::ToField};
3+
use standard_addresses::AUTH_REGISTRY_ADDRESS;
4+
5+
/// Public-flow authwit helpers.
6+
///
7+
/// In public, we cannot use oracles to "ask" the user for an authentication witness like we do in private, since the
8+
/// public execution is performed by the sequencer. Instead, an account uses a "registry" to record the messages they
9+
/// have approved. To approve a message, `Alice Account` makes a `set_authorized` call to the registry, recording a
10+
/// `message_hash -> true` mapping for `Alice Contract`. Every account has its own map in the registry, so `Alice`
11+
/// cannot approve a message for `Bob`.
12+
///
13+
/// The `Token` contract can then try to "spend" the approval by calling `consume` on the registry. If the message was
14+
/// approved, the value is updated to `false`, and we return the success flag. For more information on the registry,
15+
/// see `main.nr` in `auth_registry_contract`.
16+
///
17+
/// Person Contract Contract Contract Contract
18+
/// Alice Alice Account Registry Token DeFi
19+
/// | | | | |
20+
/// | Registry.set_authorized(..., true) | | |
21+
/// |----------------->| | | |
22+
/// | | set_authorized(..., true) | |
23+
/// | |------------------->| | |
24+
/// | | | | |
25+
/// | | set authorized to true | |
26+
/// | | | | |
27+
/// | | | | |
28+
/// | Defi.deposit(Token, 1000) | | |
29+
/// |----------------->| | | |
30+
/// | | deposit(Token, 1000) | |
31+
/// | |-------------------------------------------------------------->|
32+
/// | | | | |
33+
/// | | | transfer(Alice, Defi, 1000) |
34+
/// | | | |<---------------------|
35+
/// | | | | |
36+
/// | | | Check if Defi may call transfer(Alice, Defi, 1000)
37+
/// | | |<------------------| |
38+
/// | | | | |
39+
/// | | throw if invalid AuthWit | |
40+
/// | | | | |
41+
/// | | | | |
42+
/// | | set authorized to false | |
43+
/// | | | | |
44+
/// | | | | |
45+
/// | | | AuthWit validity | |
46+
/// | | |------------------>| |
47+
/// | | | | |
48+
/// | | | | transfer(Alice, Defi, 1000)
49+
/// | | | |<-------------------->|
50+
/// | | | | |
51+
/// | | | | success |
52+
/// | | | |--------------------->|
53+
/// | | | | |
54+
/// | | | | deposit(Token, 1000)
55+
/// | | | | |
56+
57+
/// Assert that `on_behalf_of` has authorized the current call in the authentication registry
58+
///
59+
/// Compute the `inner_hash` using the `msg_sender`, `selector` and `args_hash` and then make a call out to the
60+
/// `on_behalf_of` contract to verify that the `inner_hash` is valid.
61+
///
62+
/// Note that the authentication registry will take the `msg_sender` into account as the consumer, so this will only
63+
/// work if the `msg_sender` is the same as the `consumer` when the `message_hash` was inserted into the registry.
64+
///
65+
/// @param on_behalf_of The address that has allegedly authorized the current call
66+
pub unconstrained fn assert_current_call_valid_authwit_public(context: PublicContext, on_behalf_of: AztecAddress) {
67+
let inner_hash = compute_inner_authwit_hash([
68+
context.maybe_msg_sender().unwrap().to_field(),
69+
context.selector().to_field(),
70+
context.get_args_hash(),
71+
]);
72+
assert_inner_hash_valid_authwit_public(context, on_behalf_of, inner_hash);
73+
}
74+
75+
/// Assert that `on_behalf_of` has authorized a specific `inner_hash` in the authentication registry
76+
///
77+
/// Compute the `inner_hash` using the `msg_sender`, `selector` and `args_hash` and then make a call out to the
78+
/// `on_behalf_of` contract to verify that the `inner_hash` is valid.
79+
///
80+
/// Note that the authentication registry will take the `msg_sender` into account as the consumer, so this will only
81+
/// work if the `msg_sender` is the same as the `consumer` when the `message_hash` was inserted into the registry.
82+
///
83+
/// @param on_behalf_of The address that has allegedly authorized the `inner_hash`
84+
pub unconstrained fn assert_inner_hash_valid_authwit_public(
85+
context: PublicContext,
86+
on_behalf_of: AztecAddress,
87+
inner_hash: Field,
88+
) {
89+
let results: [Field] = context.call_public_function(
90+
AUTH_REGISTRY_ADDRESS,
91+
comptime { FunctionSelector::from_signature("consume((Field),Field)") },
92+
[on_behalf_of.to_field(), inner_hash],
93+
GasOpts::default(),
94+
);
95+
assert(results.len() == 1, "Invalid response from registry");
96+
assert(results[0] == IS_VALID_SELECTOR, "Message not authorized by account");
97+
}
98+
99+
/// Helper function to set the authorization status of a message hash
100+
///
101+
/// Wraps a public call to the authentication registry to set the authorization status of a `message_hash`
102+
///
103+
/// @param message_hash The hash of the message to authorize @param authorize True if the message should be authorized,
104+
/// false if it should be revoked
105+
pub unconstrained fn set_authorized(context: PublicContext, message_hash: Field, authorize: bool) {
106+
let res = context.call_public_function(
107+
AUTH_REGISTRY_ADDRESS,
108+
comptime { FunctionSelector::from_signature("set_authorized(Field,bool)") },
109+
[message_hash, authorize as Field],
110+
GasOpts::default(),
111+
);
112+
assert(res.len() == 0);
113+
}
114+
115+
/// Helper function to reject all authwits
116+
///
117+
/// Wraps a public call to the authentication registry to set the `reject_all` flag
118+
///
119+
/// @param reject True if all authwits should be rejected, false otherwise
120+
pub unconstrained fn set_reject_all(context: PublicContext, reject: bool) {
121+
let res = context.call_public_function(
122+
AUTH_REGISTRY_ADDRESS,
123+
comptime { FunctionSelector::from_signature("set_reject_all(bool)") },
124+
[reject as Field],
125+
GasOpts::default(),
126+
);
127+
assert(res.len() == 0);
128+
}

0 commit comments

Comments
 (0)