-
Notifications
You must be signed in to change notification settings - Fork 598
feat!: auto-enqueue public init nullifier for contracts with public functions #20775
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d588e8c
0e5db75
ad2b242
6a622ce
f748e4b
c2184a6
e2d3423
bcc1d63
7cb5443
1488a52
c3bcee1
e5894af
528e38f
772302b
7e8877d
5b84fbe
39f5d57
c389405
3535ec3
20249d5
8aec344
91cf5c7
dc54c88
a122c76
760a963
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| use crate::macros::{ | ||
| functions::initialization_utils::{EMIT_PUBLIC_INIT_NULLIFIER_FN_NAME, has_public_init_checked_functions}, | ||
| internals_functions_generation::external_functions_registry::get_private_functions, | ||
| utils::is_fn_initializer, | ||
| }; | ||
|
|
||
| /// Returns `(has_public_init_nullifier_fn, fn_body)` for the auto-generated public init nullifier function. | ||
| /// | ||
| /// Contracts with a private initializer and public functions that check initialization need an auto-generated public | ||
| /// function to emit the public init nullifier. If these conditions are met, returns `(true, fn_body_quoted)`; | ||
| /// otherwise returns `(false, quote {})`. | ||
| pub(crate) comptime fn generate_emit_public_init_nullifier(m: Module) -> (bool, Quoted) { | ||
| let has_private_initializer = get_private_functions(m).any(|f: FunctionDefinition| is_fn_initializer(f)); | ||
| let has_public_fns_with_init_check = has_public_init_checked_functions(m); | ||
|
|
||
| if has_private_initializer & has_public_fns_with_init_check { | ||
| let name = EMIT_PUBLIC_INIT_NULLIFIER_FN_NAME; | ||
| let assertion_message = f"Function {name} can only be called by the same contract".as_quoted_str(); | ||
| let body = quote { | ||
| #[aztec::macros::internals_functions_generation::abi_attributes::abi_public] | ||
| unconstrained fn $name() { | ||
| let context = aztec::context::PublicContext::new(|| { 0 }); | ||
| assert( | ||
| context.maybe_msg_sender().unwrap() == context.this_address(), | ||
| $assertion_message, | ||
| ); | ||
| aztec::macros::functions::initialization_utils::mark_as_initialized_public(context); | ||
| } | ||
| }; | ||
| (true, body) | ||
| } else { | ||
| (false, quote {}) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,47 +1,117 @@ | ||
| use crate::protocol::{ | ||
| abis::function_selector::FunctionSelector, address::AztecAddress, constants::DOM_SEP__INITIALIZER, | ||
| hash::poseidon2_hash_with_separator, traits::ToField, | ||
| abis::function_selector::FunctionSelector, | ||
| address::AztecAddress, | ||
| constants::{DOM_SEP__INITIALIZER, DOM_SEP__PUBLIC_INITIALIZATION_NULLIFIER}, | ||
| hash::poseidon2_hash_with_separator, | ||
| traits::ToField, | ||
| }; | ||
| use std::meta::{ctstring::AsCtString, unquote}; | ||
|
|
||
| use crate::{ | ||
| context::{PrivateContext, PublicContext}, | ||
| macros::{ | ||
| internals_functions_generation::external_functions_registry::get_public_functions, utils::fn_has_noinitcheck, | ||
| }, | ||
| nullifier::utils::compute_nullifier_existence_request, | ||
| oracle::get_contract_instance::{ | ||
| get_contract_instance, get_contract_instance_deployer_avm, get_contract_instance_initialization_hash_avm, | ||
| }, | ||
| }; | ||
|
|
||
| // Used by `create_mark_as_initialized` (you won't find it through searching) | ||
| /// The name of the auto-generated function that emits the public init nullifier. | ||
| /// | ||
| /// This function is injected into the public dispatch table for contracts with initializers. | ||
| pub(crate) comptime global EMIT_PUBLIC_INIT_NULLIFIER_FN_NAME: Quoted = quote { __emit_public_init_nullifier }; | ||
|
|
||
| /// Returns `true` if the module has any public functions that require initialization checks (i.e. that don't have | ||
| /// `#[noinitcheck]`). If all public functions skip init checks, there's no point emitting the public init nullifier | ||
| /// since nothing will check it. | ||
|
Comment on lines
+26
to
+28
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we do this, it then means that |
||
| pub(crate) comptime fn has_public_init_checked_functions(m: Module) -> bool { | ||
| get_public_functions(m).any(|f: FunctionDefinition| !fn_has_noinitcheck(f)) | ||
| } | ||
|
|
||
| /// Selector for `EMIT_PUBLIC_INIT_NULLIFIER_FN_NAME`, derived at comptime so it stays in sync. | ||
| global EMIT_PUBLIC_INIT_NULLIFIER_SELECTOR: FunctionSelector = comptime { | ||
| let name = EMIT_PUBLIC_INIT_NULLIFIER_FN_NAME; | ||
| let sig = f"{name}()".as_ctstring(); | ||
| unquote!(quote { FunctionSelector::from_signature($sig) }) | ||
| }; | ||
|
|
||
| /// Emits (only) the public initialization nullifier. | ||
| /// | ||
| /// This function is called by the aztec-nr auto-generated external public contract function (enqueued by private | ||
| /// [`initializer`](crate::macros::functions::initializer) functions), and also by | ||
| /// [`mark_as_initialized_from_public_initializer`] for public initializers. | ||
| /// | ||
| /// # Warning | ||
| /// | ||
| /// This should not be called manually. Incorrect use can leave the contract in a broken initialization state (e.g. | ||
| /// emitting the public nullifier without the private one). The macro-generated code handles this automatically. | ||
| pub fn mark_as_initialized_public(context: PublicContext) { | ||
| let init_nullifier = compute_unsiloed_contract_initialization_nullifier((context).this_address()); | ||
| let init_nullifier = compute_public_init_nullifier(context.this_address()); | ||
| context.push_nullifier(init_nullifier); | ||
| } | ||
|
|
||
| // Used by `create_mark_as_initialized` (you won't find it through searching) | ||
| pub fn mark_as_initialized_private(context: &mut PrivateContext) { | ||
| let init_nullifier = compute_unsiloed_contract_initialization_nullifier((*context).this_address()); | ||
| fn mark_as_initialized_private(context: &mut PrivateContext) { | ||
| let init_nullifier = compute_private_init_nullifier((*context).this_address()); | ||
| context.push_nullifier(init_nullifier); | ||
| } | ||
|
|
||
| // Used by `create_init_check` (you won't find it through searching) | ||
| /// Emits the private initialization nullifier and, if relevant, enqueues the emission of the public one. | ||
| /// | ||
| /// If the contract has public functions that perform initialization checks (i.e. that don't have `#[noinitcheck]`), | ||
| /// this also enqueues a call to the auto-generated `__emit_public_init_nullifier` function so the public nullifier is | ||
| /// emitted in public. Called by private [`initializer`](crate::macros::functions::initializer) macros. | ||
| pub fn mark_as_initialized_from_private_initializer(context: &mut PrivateContext, emit_public_init_nullifier: bool) { | ||
| mark_as_initialized_private(context); | ||
| if emit_public_init_nullifier { | ||
| context.call_public_function((*context).this_address(), EMIT_PUBLIC_INIT_NULLIFIER_SELECTOR, [], false); | ||
| } | ||
| } | ||
|
|
||
| /// Emits both initialization nullifiers (private and public). | ||
| /// | ||
| /// Called by public [`initializer`](crate::macros::functions::initializer) macros, since public initializers must set | ||
| /// both so that both private and public functions see the contract as initialized. | ||
| pub fn mark_as_initialized_from_public_initializer(context: PublicContext) { | ||
| let private_nullifier = compute_private_init_nullifier(context.this_address()); | ||
| context.push_nullifier(private_nullifier); | ||
| mark_as_initialized_public(context); | ||
| } | ||
|
|
||
| /// Asserts that the contract has been initialized, from public's perspective. | ||
| /// | ||
| /// Checks that the public initialization nullifier exists. | ||
| pub fn assert_is_initialized_public(context: PublicContext) { | ||
| let init_nullifier = compute_unsiloed_contract_initialization_nullifier(context.this_address()); | ||
| // Safety: TODO(F-239) - this is currently unsafe, we cannot rely on the nullifier existing to determine that any | ||
| // public component of contract initialization has been complete. | ||
| let init_nullifier = compute_public_init_nullifier(context.this_address()); | ||
nchamo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // Safety: the public init nullifier is only ever emitted by public functions, and so the timing concerns from | ||
| // nullifier_exists_unsafe do not apply. Additionally, it is emitted after all initializer functions have run, | ||
| // so initialization is guaranteed to be complete by the time it exists. | ||
| assert(context.nullifier_exists_unsafe(init_nullifier, context.this_address()), "Not initialized"); | ||
| } | ||
|
|
||
| // Used by `create_init_check` (you won't find it through searching) | ||
| /// Asserts that the contract has been initialized, from private's perspective. | ||
| /// | ||
| /// Checks that the private initialization nullifier exists. | ||
| pub fn assert_is_initialized_private(context: &mut PrivateContext) { | ||
| let init_nullifier = compute_unsiloed_contract_initialization_nullifier(context.this_address()); | ||
| let init_nullifier = compute_private_init_nullifier(context.this_address()); | ||
| let nullifier_existence_request = compute_nullifier_existence_request(init_nullifier, context.this_address()); | ||
| context.assert_nullifier_exists(nullifier_existence_request); | ||
| } | ||
|
|
||
| fn compute_unsiloed_contract_initialization_nullifier(address: AztecAddress) -> Field { | ||
| // TODO(F-194): This leaks whether a contract has been initialized, since anyone who knows the address can compute this | ||
| // nullifier and check for its existence. It is also not domain separated. | ||
| fn compute_private_init_nullifier(address: AztecAddress) -> Field { | ||
| address.to_field() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (not just that, this is also not domain separated) |
||
| } | ||
|
|
||
| fn compute_public_init_nullifier(address: AztecAddress) -> Field { | ||
| poseidon2_hash_with_separator( | ||
| [address.to_field()], | ||
| DOM_SEP__PUBLIC_INITIALIZATION_NULLIFIER, | ||
| ) | ||
| } | ||
|
|
||
| // Used by `create_assert_correct_initializer_args` (you won't find it through searching) | ||
| pub fn assert_initialization_matches_address_preimage_public(context: PublicContext) { | ||
| let address = context.this_address(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is much better!