diff --git a/docs/docs/guides/smart_contracts/writing_contracts/authwit.md b/docs/docs/guides/smart_contracts/writing_contracts/authwit.md index 22e0e3943122..8f74c3bf3409 100644 --- a/docs/docs/guides/smart_contracts/writing_contracts/authwit.md +++ b/docs/docs/guides/smart_contracts/writing_contracts/authwit.md @@ -101,7 +101,7 @@ To make it convenient to compute the message hashes in TypeScript, the `aztec.js For private calls where we allow execution on behalf of others, we generally want to check if the current call is authenticated by `on_behalf_of`. To easily do so, we can use the `assert_current_call_valid_authwit` which fetches information from the current context without us needing to provide much beyond the `on_behalf_of`. -This function will then make a to `on_behalf_of` to execute the `verify_private_authwit` function which validates that the call is authenticated. +This function will then make a call to `on_behalf_of` to execute the `verify_private_authwit` function which validates that the call is authenticated. The `on_behalf_of` should assert that we are indeed authenticated and then return the `IS_VALID` selector. If the return value is not as expected, we throw an error. This is to cover the case where the `on_behalf_of` might implemented some function with the same selector as the `verify_private_authwit` that could be used to authenticate unintentionally. #### Example diff --git a/docs/docs/migration_notes.md b/docs/docs/migration_notes.md index ee0a7c0dfce4..60c12bc059e5 100644 --- a/docs/docs/migration_notes.md +++ b/docs/docs/migration_notes.md @@ -12,13 +12,13 @@ Aztec is in full-speed development. Literally every version breaks compatibility The private authwit validation is now making a static call to the account contract instead of passing over control flow. This is to ensure that it cannot be used for re-entry. -To make this change however, we cannot allow emitting a nullifying from the account contract, since that would break the static call. Instead, we will be changing the `verify_private_authwit` to a `verify_private_authwit` and in the `auth` library emit the nullifier. This means that the "calling" contract will now be emitting the nullifier, and not the account. For example, for a token contract, the nullifier is now emitted by the token contract. However, as this is done inside the `auth` library, the token contract don't need to change much. +To make this change however, we cannot allow emitting a nullifier from the account contract, since that would break the static call. Instead, we will be changing the `spend_private_authwit` to a `verify_private_authwit` and in the `auth` library emit the nullifier. This means that the "calling" contract will now be emitting the nullifier, and not the account. For example, for a token contract, the nullifier is now emitted by the token contract. However, as this is done inside the `auth` library, the token contract doesn't need to change much. The biggest difference is related to "cancelling" an authwit. Since it is no longer in the account contract, you cannot just emit a nullifier from it anymore. Instead it must rely on the token contract providing functionality for cancelling. There are also a few general changes to how authwits are generated, namely to more easily support the data required for a validity lookup now. Previously we could lookup the `message_hash` directly at the account contract, now we instead need to use the `inner_hash` and the contract of the consumer to figure out if it have already been emitted. -A minor extension have been made to the authwit creations to make it easier specific a hash that needs to be signed with a specific caller, e.g., the `inner_hash` can be provided as `{consumer, inner_hash}` to the `createAuthWit` where it previously needed to do a couple of manual steps to compute the outer hash. The `computeOuterAuthWitHash` have been amde internal and the `computeAuthWitMessageHash` can instead be used to compute the values similarly to other authwit computations. +A minor extension have been made to the authwit creations to make it easier to sign a specific a hash with a specific caller, e.g., the `inner_hash` can be provided as `{consumer, inner_hash}` to the `createAuthWit` where it previously needed to do a couple of manual steps to compute the outer hash. The `computeOuterAuthWitHash` have been amde internal and the `computeAuthWitMessageHash` can instead be used to compute the values similarly to other authwit computations. ```diff const innerHash = computeInnerAuthWitHash([Fr.ZERO, functionSelector.toField(), entrypointPackedArgs.hash]); diff --git a/noir-projects/aztec-nr/authwit/src/auth.nr b/noir-projects/aztec-nr/authwit/src/auth.nr index 3471f6297480..18342ce4f7b6 100644 --- a/noir-projects/aztec-nr/authwit/src/auth.nr +++ b/noir-projects/aztec-nr/authwit/src/auth.nr @@ -19,6 +19,7 @@ pub fn assert_current_call_valid_authwit(context: &mut PrivateContext, on_behalf // docs:end:assert_current_call_valid_authwit pub fn assert_inner_hash_valid_authwit(context: &mut PrivateContext, on_behalf_of: AztecAddress, inner_hash: Field) { + // We perform a static call here and not a standard one to ensure that the account contract cannot re-enter. let result: Field = context.static_call_private_function( on_behalf_of, FunctionSelector::from_signature("verify_private_authwit(Field)"), diff --git a/noir-projects/aztec-nr/aztec/src/keys/public_keys.nr b/noir-projects/aztec-nr/aztec/src/keys/public_keys.nr index 35f646a12420..fe65ff9e37ed 100644 --- a/noir-projects/aztec-nr/aztec/src/keys/public_keys.nr +++ b/noir-projects/aztec-nr/aztec/src/keys/public_keys.nr @@ -4,8 +4,6 @@ use dep::protocol_types::{ }; use crate::keys::constants::{NUM_KEY_TYPES, NULLIFIER_INDEX, INCOMING_INDEX, OUTGOING_INDEX}; -use dep::std::println; - global PUBLIC_KEYS_LENGTH = 8; struct PublicKeys { diff --git a/yarn-project/aztec.js/src/account/interface.ts b/yarn-project/aztec.js/src/account/interface.ts index 8ea11e91fc02..cafef217f9e1 100644 --- a/yarn-project/aztec.js/src/account/interface.ts +++ b/yarn-project/aztec.js/src/account/interface.ts @@ -8,10 +8,8 @@ import { type EntrypointInterface } from '../entrypoint/entrypoint.js'; /** Creates authorization witnesses. */ export interface AuthWitnessProvider { /** - * Computes an authentication witness from either a message hash or an intent (caller and an action). - * If a message hash is provided, it will create a witness for that directly. - * Otherwise, it will compute the message hash using the caller and the action of the intent. - * @param messageHash - The message hash or the intent (caller and action) to approve + * Computes an authentication witness from either a message hash + * @param messageHash - The message hash to approve * @returns The authentication witness */ createAuthWit(messageHash: Fr | Buffer): Promise; diff --git a/yarn-project/aztec.js/src/utils/authwit.ts b/yarn-project/aztec.js/src/utils/authwit.ts index b1a6c243a87e..e3df2b9a5271 100644 --- a/yarn-project/aztec.js/src/utils/authwit.ts +++ b/yarn-project/aztec.js/src/utils/authwit.ts @@ -34,9 +34,10 @@ export type IntentAction = { * * If using the `IntentInnerHash`, the consumer is the address that can "consume" the authwit, for token approvals it is the token contract itself. * The `innerHash` itself will be the message that a contract is allowed to execute. - * At the point of "approval checking", the validating contract (account for private and registry for public) - * will be computing the message hash, and using that for the check. Any allowed `innerHash` will therefore also have information around - * where it can be spent (version, chainId, consumer). + * At the point of "approval checking", the validating contract (account for private and registry for public) will be computing the message hash + * (`H(consumer, chainid, version, inner_hash)`) where the all but the `inner_hash` is injected from the context (consumer = msg_sender), + * and use it for the authentication check. + * Therefore, any allowed `innerHash` will therefore also have information around where it can be spent (version, chainId) and who can spend it (consumer). * * If using the `IntentAction`, the caller is the address that is making the call, for a token approval from Alice to Bob, this would be Bob. * The action is then used along with the `caller` to compute the `innerHash` and the consumer. @@ -45,7 +46,7 @@ export type IntentAction = { * @param intent - The intent to approve (consumer and innerHash or caller and action) * The consumer is the address that can "consume" the authwit, for token approvals it is the token contract itself. * The caller is the address that is making the call, for a token approval from Alice to Bob, this would be Bob. - * The caller is only a concept in the aztec structure, as it becomes part of the inner hash, and is simple + * The caller becomes part of the `inner_hash` and is dealt with entirely in application logic. * @param metadata - The metadata for the intent (chainId, version) * @returns The message hash for the action */ @@ -55,7 +56,6 @@ export const computeAuthWitMessageHash = (intent: IntentInnerHash | IntentAction if ('caller' in intent) { const action = intent.action instanceof ContractFunctionInteraction ? intent.action.request() : intent.action; - // return computeAuthWitMessageHash(intent.caller, chainId, version, action); return computeOuterAuthWitHash( action.to.toField(), chainId, diff --git a/yarn-project/end-to-end/src/e2e_authwit.test.ts b/yarn-project/end-to-end/src/e2e_authwit.test.ts index 00bf9bd95b31..471aca74685d 100644 --- a/yarn-project/end-to-end/src/e2e_authwit.test.ts +++ b/yarn-project/end-to-end/src/e2e_authwit.test.ts @@ -36,7 +36,7 @@ describe('e2e_authwit_tests', () => { it('happy path', async () => { // What are we doing here: // 1. We compute an inner hash which is here just a hash of random data - // 2. We then compute the other, which is binding it to a "caller", here the "auth" contract + // 2. We then compute the outer, which is binding it to a "consumer" (here the "auth" contract) // 3. We then create an authwit for this outer hash. // 4. We add this authwit to the wallet[1] // 5. We check that the authwit is valid in private for wallet[0] (check that it is signed by 0) @@ -66,7 +66,7 @@ describe('e2e_authwit_tests', () => { isValidInPublic: false, }); - // Consume the inner hash using the wallets[0] as the on behalf of. + // Consume the inner hash using the wallets[0] as the "on behalf of". await auth.withWallet(wallets[1]).methods.consume(wallets[0].getAddress(), innerHash).send().wait(); expect(await wallets[0].lookupValidity(wallets[0].getAddress(), intent)).toEqual({