Skip to content

Commit 4af88df

Browse files
authored
docs: protocol contracts (#20470)
Code docs in protocol contracts where needed as it's a core piece of infrastructure and they are going for an audit today. For this reason I decided to go ahead and write them. **Update**: Just realized that AuthRegistry is not a protocol contracts. I've already wrote the docs so sending it for the review here. We really need to move the non-protocol contracts out.
2 parents a477c02 + 86b5726 commit 4af88df

File tree

5 files changed

+190
-148
lines changed
  • noir-projects/noir-contracts/contracts/protocol

5 files changed

+190
-148
lines changed

noir-projects/noir-contracts/contracts/protocol/auth_registry_contract/src/main.nr

Lines changed: 66 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
1-
/**
2-
* @title AuthRegistry Contract
3-
* @notice Manages authorization of public actions through authentication witnesses (authwits)
4-
* @dev This contract allows users to approve/reject public actions that can be performed on their behalf by other
5-
* addresses
6-
*/
1+
/// A contract that manages public authentication witnesses (authwits) on the Aztec network.
2+
///
3+
/// In Aztec, private authwits are verified by account contracts directly (via oracles on the user's device). Public
4+
/// authwits, however, require this onchain registry because local oracles cannot be used for global execution. Users pre-approve
5+
/// actions by storing `message_hash -> true` mappings via `set_authorized`, and consumer contracts verify and
6+
/// atomically revoke these approvals via `consume`.
7+
///
8+
/// The `message_hash` includes the consumer address, chain ID, and protocol version, preventing cross-chain and
9+
/// cross-contract replay. Each approval can only be consumed once. Users can also enable `reject_all` as an emergency
10+
/// kill switch to invalidate all outstanding approvals at once.
11+
///
12+
/// A private-to-public bridge is provided via `set_authorized_private`: a user signs a private authwit, and any party
13+
/// holding it can call this function to insert the corresponding public approval.
14+
///
15+
/// Note that there is no expiration time enforced on the approved actions in this contract as this can be achieved by
16+
/// including an expiration timestamp in the `message` (`message_hash` preimage) and having the consumer contract
17+
/// constrain that value.
718
pub contract AuthRegistry {
819
use aztec::{
920
authwit::auth::{
@@ -22,19 +33,16 @@ pub contract AuthRegistry {
2233
};
2334

2435
struct Storage<Context> {
25-
/// Map of addresses that have rejected all actions
36+
/// Per-address flag that, when true, causes all `consume` calls for that address to revert. Provides an
37+
/// emergency "mass revocation" mechanism. Does not delete existing approvals - if later set back to false,
38+
/// unconsumed approvals become consumable again.
2639
reject_all: Map<AztecAddress, PublicMutable<bool, Context>, Context>,
27-
/// Nested map of approvers to their authorized message hashes
28-
/// First key is the approver address, second key is the message hash, value is authorization status
40+
/// Per-user, per-message authorization status. `approved_actions[user][message_hash]` is set to true by
41+
/// `set_authorized` and atomically revoked by `consume` to prevent replay.
2942
approved_actions: Map<AztecAddress, Map<Field, PublicMutable<bool, Context>, Context>, Context>,
3043
}
3144

32-
/**
33-
* Updates the `authorized` value for `msg_sender` for `message_hash`.
34-
*
35-
* @param message_hash The message hash being authorized
36-
* @param authorize True if the caller is authorized to perform the message hash, false otherwise
37-
*/
45+
/// Approves or revokes a `message_hash` for the caller.
3846
#[aztec::macros::internals_functions_generation::abi_attributes::abi_public]
3947
unconstrained fn set_authorized(message_hash: Field, authorize: bool) {
4048
// MACRO CODE START
@@ -51,13 +59,8 @@ pub contract AuthRegistry {
5159
storage.approved_actions.at(avm::sender()).at(message_hash).write(authorize);
5260
}
5361

54-
/**
55-
* Updates the `reject_all` value for `msg_sender`.
56-
*
57-
* When `reject_all` is `true` any `consume` on `msg_sender` will revert.
58-
*
59-
* @param reject True if all actions should be rejected, false otherwise
60-
*/
62+
/// Enables or disables mass rejection of all authwits for the caller. When enabled, all `consume` calls for the
63+
/// caller's address will revert regardless of individual approvals.
6164
#[aztec::macros::internals_functions_generation::abi_attributes::abi_public]
6265
unconstrained fn set_reject_all(reject: bool) {
6366
// MACRO CODE START
@@ -73,16 +76,19 @@ pub contract AuthRegistry {
7376
storage.reject_all.at(avm::sender()).write(reject);
7477
}
7578

76-
/**
77-
* Consumes an `inner_hash` on behalf of `on_behalf_of` if the caller is authorized to do so.
78-
*
79-
* Will revert even if the caller is authorized if `reject_all` is set to true for `on_behalf_of`.
80-
* This is to support "mass-revoke".
81-
*
82-
* @param on_behalf_of The address on whose behalf the action is being consumed
83-
* @param inner_hash The inner_hash of the authwit
84-
* @return `IS_VALID_SELECTOR` if the action was consumed, revert otherwise
85-
*/
79+
/// Consumes (verifies and atomically revokes) an authorization on behalf of `on_behalf_of`.
80+
///
81+
/// Called by consumer contracts (e.g. Token) to verify a user has authorized an action. This function:
82+
/// 1. Checks that `on_behalf_of` has not enabled `reject_all`.
83+
/// 2. Recomputes the `message_hash` from the caller (consumer), chain ID, version, and `inner_hash`, binding the
84+
/// approval to this specific consumer contract.
85+
/// 3. Verifies the message was approved and atomically revokes it to prevent replay.
86+
///
87+
/// Returns `IS_VALID_SELECTOR` (0x47dacd73) on success instead of a boolean. This follows the EIP-1271 pattern:
88+
/// a failed or malformed call would return the default zero value, which is indistinguishable from `false`. By
89+
/// requiring a specific magic value, the caller can reliably distinguish a successful validation from a failed
90+
/// call. The function also reverts on failure as a first line of defense, making the magic return value a
91+
/// defense-in-depth measure against subtle integration bugs on the caller side.
8692
#[aztec::macros::internals_functions_generation::abi_attributes::abi_public]
8793
unconstrained fn consume(on_behalf_of: AztecAddress, inner_hash: Field) -> pub Field {
8894
// MACRO CODE START
@@ -96,8 +102,11 @@ pub contract AuthRegistry {
96102
let storage: Storage<PublicContext> = Storage::init(context);
97103
// MACRO CODE END
98104

105+
// reject_all is checked first so it takes precedence over individual approvals.
99106
assert_eq(false, storage.reject_all.at(on_behalf_of).read(), "rejecting all");
100107

108+
// The msg_sender here is the consumer contract, not the original user. This binds
109+
// the approval to a specific consumer, preventing cross-contract replay.
101110
let message_hash = compute_authwit_message_hash(
102111
context.maybe_msg_sender().unwrap(),
103112
context.chain_id(),
@@ -106,24 +115,23 @@ pub contract AuthRegistry {
106115
);
107116

108117
let authorized = storage.approved_actions.at(on_behalf_of).at(message_hash).read();
109-
110118
assert_eq(true, authorized, "unauthorized");
119+
// Revoke the approval to prevent replay.
111120
storage.approved_actions.at(on_behalf_of).at(message_hash).write(false);
112121

113122
IS_VALID_SELECTOR
114123
}
115124

116-
/**
117-
* Updates a public authwit using a private authwit
118-
*
119-
* Useful for the case where you want someone else to insert a public authwit for you.
120-
* For example, if Alice wants Bob to insert an authwit in public, such that they can execute
121-
* a trade, Alice can create a private authwit, and Bob can call this function with it.
122-
*
123-
* @param approver The address of the approver (Alice in the example)
124-
* @param message_hash The message hash to authorize
125-
* @param authorize True if the message hash should be authorized, false otherwise
126-
*/
125+
/// Bridges a private authwit into a public authorization entry.
126+
///
127+
/// Allows any party to insert a public approval on behalf of `approver`, provided they present a valid private
128+
/// authwit from that approver. Useful when e.g. Alice wants Bob to insert a public authwit for her so they can
129+
/// execute a trade - Alice signs a private authwit and Bob calls this function.
130+
///
131+
/// This function:
132+
/// 1. Verifies the approver's private authwit via `assert_current_call_valid_authwit` (static call + nullifier
133+
/// emission to prevent replay).
134+
/// 2. Enqueues a public call to `_set_authorized` to write the approval during the public phase.
127135
#[aztec::macros::internals_functions_generation::abi_attributes::abi_private]
128136
fn set_authorized_private(
129137
inputs: aztec::context::inputs::PrivateContextInputs,
@@ -143,12 +151,10 @@ pub contract AuthRegistry {
143151

144152
// MACRO CODE END
145153

146-
// 3 corresponds to the number of arguments of the function
154+
// The generic parameter `3` is the number of function arguments (approver, message_hash, authorize).
147155
assert_current_call_valid_authwit::<3>(&mut context, approver);
148156

149-
// Enqueue call to _set_authorized
150-
// Note: This was originally just `self.enqueue_self._set_authorized(approver, message_hash, authorize);`
151-
// before de-macroification.
157+
// Enqueue a public call to _set_authorized to write the approval into public storage.
152158
{
153159
let enqueue_params: [Field; 3] =
154160
[approver.to_field(), message_hash, authorize.to_field()];
@@ -169,14 +175,8 @@ pub contract AuthRegistry {
169175
// MACRO CODE END
170176
}
171177

172-
/**
173-
* Internal function to update the `authorized` value for `approver` for `messageHash`.
174-
* Used along with `set_authorized_private` to update the public authwit.
175-
*
176-
* @param approver The address of the approver
177-
* @param message_hash The message hash being authorized
178-
* @param authorize True if the caller is authorized to perform the message hash, false otherwise
179-
*/
178+
/// A function that writes an authorization entry for an arbitrary `approver`. Only callable by this contract
179+
/// itself (`#[only_self]`), ensuring it is only reachable through the validated `set_authorized_private` flow.
180180
#[aztec::macros::internals_functions_generation::abi_attributes::abi_public]
181181
#[aztec::macros::internals_functions_generation::abi_attributes::abi_only_self]
182182
unconstrained fn _set_authorized(approver: AztecAddress, message_hash: Field, authorize: bool) {
@@ -193,7 +193,6 @@ pub contract AuthRegistry {
193193
);
194194
let storage: Storage<PublicContext> = Storage::init(context);
195195

196-
// Originally injected by the #[only_self] macro
197196
assert(
198197
avm::sender() == context.this_address(),
199198
"Function _set_authorized can only be called by the same contract",
@@ -203,12 +202,7 @@ pub contract AuthRegistry {
203202
storage.approved_actions.at(approver).at(message_hash).write(authorize);
204203
}
205204

206-
/**
207-
* Fetches the `reject_all` value for `on_behalf_of`.
208-
*
209-
* @param on_behalf_of The address to check
210-
* @return True if all actions are rejected, false otherwise
211-
*/
205+
/// Returns whether `on_behalf_of` has enabled the `reject_all` flag.
212206
#[aztec::macros::internals_functions_generation::abi_attributes::abi_public]
213207
#[aztec::macros::internals_functions_generation::abi_attributes::abi_view]
214208
unconstrained fn is_reject_all(on_behalf_of: AztecAddress) -> pub bool {
@@ -222,20 +216,14 @@ pub contract AuthRegistry {
222216
);
223217
let storage: Storage<PublicContext> = Storage::init(context);
224218

225-
// Originally injected by the #[view] macro
226219
assert(context.is_static_call(), "Function is_reject_all can only be called statically");
227220
// MACRO CODE END
228221

229222
storage.reject_all.at(on_behalf_of).read()
230223
}
231224

232-
/**
233-
* Fetches the `authorized` value for `on_behalf_of` for `message_hash`.
234-
*
235-
* @param on_behalf_of The address on whose behalf the action is being consumed
236-
* @param message_hash The message hash to check
237-
* @return True if the caller is authorized to perform the action, false otherwise
238-
*/
225+
/// Returns whether a specific `message_hash` is currently approved for `on_behalf_of`.
226+
/// Does NOT check the `reject_all` flag - also check `is_reject_all` for a complete picture.
239227
#[aztec::macros::internals_functions_generation::abi_attributes::abi_public]
240228
#[aztec::macros::internals_functions_generation::abi_attributes::abi_view]
241229
unconstrained fn is_consumable(on_behalf_of: AztecAddress, message_hash: Field) -> pub bool {
@@ -249,16 +237,13 @@ pub contract AuthRegistry {
249237
);
250238
let storage: Storage<PublicContext> = Storage::init(context);
251239

252-
// Originally injected by the #[view] macro
253240
assert(context.is_static_call(), "Function is_consumable can only be called statically");
254241
// MACRO CODE END
255242

256243
storage.approved_actions.at(on_behalf_of).at(message_hash).read()
257244
}
258245

259-
/**
260-
* Just like `is_consumable`, but a utility function and not public.
261-
*/
246+
/// Utility version of `is_consumable`
262247
#[aztec::macros::internals_functions_generation::abi_attributes::abi_utility]
263248
unconstrained fn utility_is_consumable(
264249
on_behalf_of: AztecAddress,
@@ -274,20 +259,24 @@ pub contract AuthRegistry {
274259
storage.approved_actions.at(on_behalf_of).at(message_hash).read()
275260
}
276261

277-
// // THE REST OF THE CODE IN THIS CONTRACT WAS ORIGINALLY INJECTED BY THE #[aztec] MACRO.
262+
// THE REST OF THE CODE IN THIS CONTRACT WAS ORIGINALLY INJECTED BY THE #[aztec] MACRO.
278263

279-
// Function selectors computed at comptime from function signatures
280264
global SET_AUTHORIZED_SELECTOR: Field =
281265
comptime { FunctionSelector::from_signature("set_authorized(Field,bool)").to_field() };
266+
282267
global SET_REJECT_ALL_SELECTOR: Field =
283268
comptime { FunctionSelector::from_signature("set_reject_all(bool)").to_field() };
269+
284270
global CONSUME_SELECTOR: Field =
285271
comptime { FunctionSelector::from_signature("consume((Field),Field)").to_field() };
272+
286273
global _SET_AUTHORIZED_SELECTOR: Field = comptime {
287274
FunctionSelector::from_signature("_set_authorized((Field),Field,bool)").to_field()
288275
};
276+
289277
global IS_REJECT_ALL_SELECTOR: Field =
290278
comptime { FunctionSelector::from_signature("is_reject_all((Field))").to_field() };
279+
291280
global IS_CONSUMABLE_SELECTOR: Field =
292281
comptime { FunctionSelector::from_signature("is_consumable((Field),Field)").to_field() };
293282

@@ -365,7 +354,6 @@ pub contract AuthRegistry {
365354
}
366355
}
367356

368-
// Parameter structs for ABI
369357
pub struct _set_authorized_parameters {
370358
pub _approver: AztecAddress,
371359
pub _message_hash: Field,
@@ -406,7 +394,6 @@ pub contract AuthRegistry {
406394
pub _message_hash: Field,
407395
}
408396

409-
// ABI structs
410397
#[abi(functions)]
411398
pub struct _set_authorized_abi {
412399
parameters: _set_authorized_parameters,

noir-projects/noir-contracts/contracts/protocol/contract_class_registry_contract/src/main.nr

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,24 @@
11
mod events;
22
mod validate_bytecode;
33

4+
/// Protocol contract that tracks which contract classes have been published to the network.
5+
///
6+
/// In Aztec, contracts are split into *classes* and *instances* (deployments of a class at a unique address). This
7+
/// contract handles publishing of contract classes.
8+
///
9+
/// Every contract class must be registered here before any instance of that class can be deployed via the
10+
/// ContractInstanceRegistry - this applies even to contracts with no public functions. Registration is also
11+
/// a prerequisite for contract upgrades: the *new* class must be published before an existing deployed contract
12+
/// instance can update its class id (a contract instance that is not registered in ContractInstanceRegistry cannot
13+
/// be upgraded).
14+
///
15+
/// Registration works by emitting a nullifier keyed to the contract class id. Other protocol contracts
16+
/// (e.g. ContractInstanceRegistry) verify registration by asserting this nullifier exists.
17+
///
18+
/// Contracts with public functions need to have their bytecode published in order for for the sequencer to be
19+
/// guaranteed to be able to execute a public function (this is achieved by checking contract instance address
20+
/// nullifier exists by the AVM - see ContractInstanceRegistry for more details). ACIR of private functions does not
21+
/// need to be published here as private functions are executed on user devices.
422
pub contract ContractClassRegistry {
523
use crate::events::class_published::ContractClassPublished;
624
use crate::validate_bytecode::validate_packed_public_bytecode;
@@ -16,6 +34,23 @@ pub contract ContractClassRegistry {
1634
},
1735
};
1836

37+
/// Publishes a contract class to the network.
38+
///
39+
/// The caller provides the three components of the contract class id (artifact_hash, private_functions_root,
40+
/// public_bytecode_commitment) together with the packed public bytecode loaded via a capsule. For contracts with
41+
/// no public functions the bytecode is empty, but registration is still possible so that the instance can later be
42+
/// deployed or upgraded.
43+
///
44+
/// Note that there is only one public bytecode commitment per contract instead of per contract-function as
45+
/// bytecode of public functions is merged into one `public_dispatch` function and the relevant code branch is then
46+
/// executed based on the function selector.
47+
///
48+
/// This function:
49+
/// 1. Validates the packed bytecode against the provided commitment.
50+
/// 2. Recomputes and emits the contract class id as a nullifier (preventing duplicate registration and serving as
51+
/// a proof of publication).
52+
/// 3. Broadcasts a `ContractClassPublished` event containing the full class metadata and bytecode so that nodes
53+
/// can reconstruct the class.
1954
#[aztec::macros::internals_functions_generation::abi_attributes::abi_private]
2055
fn publish(
2156
inputs: aztec::context::inputs::PrivateContextInputs,

0 commit comments

Comments
 (0)