diff --git a/docs/docs/concepts/foundation/accounts/authwit.md b/docs/docs/concepts/foundation/accounts/authwit.md
new file mode 100644
index 00000000000..bb0e5ca367f
--- /dev/null
+++ b/docs/docs/concepts/foundation/accounts/authwit.md
@@ -0,0 +1,199 @@
+---
+title: Authentication Witness
+---
+
+Authentication Witness is a scheme for authenticating actions on Aztec, so users can allow third-parties (eg protocols or other users) to execute an action on their behalf.
+
+## Background
+
+When building DeFi or other smart contracts, it is often desired to interact with other contracts to execute some action on behalf of the user. For example, when you want to deposit funds into a lending protocol, the protocol wants to perform a transfer of [ERC20](https://eips.ethereum.org/EIPS/eip-20) tokens from the user's account to the protocol's account.
+
+In the EVM world, this is often accomplished by having the user `approve` the protocol to transfer funds from their account, and then calling a `deposit` function on it afterwards.
+
+```mermaid
+sequenceDiagram
+ actor Alice
+ Alice->>Token: approve(Defi, 1000);
+ Alice->>Defi: deposit(Token, 1000);
+ activate Defi
+ Defi->>Token: transferFrom(Alice, Defi, 1000);
+ deactivate Defi
+```
+
+This flow makes it rather simple for the application developer to implement the deposit function, but does not come without its downsides.
+
+One main downside, which births a bunch of other issues, is that the user needs to send two transactions to make the deposit - first the `approve` and then the `deposit`.
+
+To limit the annoyance for return-users, some front-ends will use the `approve` function with an infinite amount, which means that the user will only has to sign the `approve` transaction once, and every future `deposit` with then use some of that "allowance" to transfer funds from the user's account to the protocol's account.
+
+This can lead to a series of issues though, eg:
+
+- The user is not aware of how much they have allowed the protocol to transfer.
+- The protocol can transfer funds from the user's account at any time. This means that if the protocol is rugged or exploited, it can transfer funds from the user's account without the user having to sign any transaction. This is especially an issue if the protocol is upgradable, as it could be made to steal the user's approved funds at any time in the future.
+
+To avoid this, many protocols implement the `permit` flow, which uses a meta-transaction to let the user sign the approval off-chain, and pass it as an input to the `deposit` function, that way the user only have to send one transaction to make the deposit.
+
+```mermaid
+sequenceDiagram
+ actor Alice
+ Alice->>Alice: sign permit(Defi, 1000);
+ Alice->>Defi: deposit(Token, 1000, signature);
+ activate Defi
+ Defi->>Token: permit(Alice, Defi, 1000, signature);
+ Defi->>Token: transferFrom(Alice, Defi, 1000);
+ deactivate Defi
+```
+
+This is a great improvement to infinite approvals, but still has its own sets of issues. For example, if the user is using a smart-contract wallet (such as Argent or Gnosis Safe), they will not be able to sign the permit message since the usual signature validation does not work well with contracts. [EIP-1271](https://eips.ethereum.org/EIPS/eip-1271) was proposed to give contracts a way to emulate this, but it is not widely adopted.
+
+Separately, the message that the user signs can seem opaque to the user and they might not understand what they are signing. This is generally an issue with `approve` as well.
+
+All of these issues have been discussed in the community for a while, and there are many proposals to solve them. However, none of them have been widely adopted - ERC20 is so commonly used and changing a standard is hard.
+
+## In Aztec
+
+Adopting ERC20 for Aztec is not as simple as it might seem because of private state.
+
+If you recall from [State model](./../state_model.md), private state is generally only known by its owner and those they have shared it with. Because it relies on secrets, private state might be "owned" by a contract, but it needs someone with knowledge of these secrets to actually spend it. You might see where this is going.
+
+If we were to implement the `approve` with an allowance in private, you might know the allowance, but unless you also know about the individual notes that make up the user's balances, it would be of no use to you! It is private after all. To spend the user's funds you would need to know the decryption key, see [keys for more](../accounts/keys.md).
+
+While this might sound limiting in what we can actually do, the main use of approvals have been for simplifying contract interactions that the user is doing. In the case of private transactions, this is executed on the user device, so it is not a blocker that the user need to tell the executor a secret - the user is the executor!
+### So what can we do?
+
+A few more things we need to remember about private execution:
+
+- To stay private, it all happens on the user device.
+- Because it happens on the user device, additional user-provided information can be passed to the contract mid-execution via an oracle call.
+
+For example, when executing a private transfer, the wallet will be providing the notes that the user wants to transfer through one of these oracle calls instead of the function arguments. This allows us to keep the function signature simple, and have the user provide the notes they want to transfer through the oracle call.
+
+For a transfer, it could be the notes provided, but we could also use the oracle to provide any type of data to the contract. So we can borrow the idea from `permit` that the user can provide a signature (or witness) to the contract which allows it to perform some action on behalf of the user.
+
+:::info Witness or signature?
+The doc refers to a witness instead of a signature because it is not necessarily a signature that is required to convince the account contract that we are allowed to perform the action. It depends on the contract implementation, and could also be a password or something similar.
+:::
+
+Since the witness is used to authenticate that someone can execute an action on behalf of the user, we call it an Authentication Witness or `AuthWit` for short. An "action", in this meaning, is a blob of data that specifies what call is approved, what arguments it is approved with, and the actor that is authenticated to perform the call.
+
+In practice, this blob is currently outlined to be a hash of the content mentioned, but it might change over time to make ["simulating simulations"](https://discourse.aztec.network/t/simulating-simulations/2218) easier.
+
+Outlined more clearly, we have the following, where the `H` is a SNARK-friendly hash function and `argsHash` is the hash of function arguments:
+
+```rust
+authentication_witness_action = H(
+ caller: AztecAddress,
+ contract: AztecAddress,
+ selector: Field,
+ argsHash: Field
+);
+```
+
+To outline an example as mentioned earlier, let's say that we have a token that implements `AuthWit` such that transfer funds from A to B is valid if A is doing the transfer, or there is a witness that authenticates the caller to transfer funds from A's account. While this specifies the spending rules, one must also know of the notes to use them for anything. This means that a witness in itself is only half the information.
+
+Creating the authentication action for the transfer of funds to the Defi contract would look like this:
+
+```rust
+action = H(defi, token, transfer_selector, H(alice_account, defi, 1000));
+```
+
+This can be read as "defi is allowed to call token transfer function with the arguments (alice_account, defi, 1000)".
+
+With this out of the way, let's look at how this would work in the graph below. The exact contents of the witness will differ between implementations as mentioned before, but for the sake of simplicity you can think of it as a signature, which the account contract can then use to validate if it really should allow the action.
+
+```mermaid
+sequenceDiagram
+ actor Alice
+ participant AC as Alice Account
+ participant Token
+ Alice->>AC: Defi.deposit(Token, 1000);
+ activate AC
+ AC->>Defi: deposit(Token, 1000);
+ activate Defi
+ Defi->>Token: transfer(Alice, Defi, 1000);
+ activate Token
+ Token->>AC: Check if Defi may call transfer(Alice, Defi, 1000);
+ AC-->>Alice: Please give me AuthWit for DeFi
calling transfer(Alice, Defi, 1000);
+ activate Alice
+ Alice-->>Alice: Produces Authentication witness
+ Alice-->>AC: AuthWit for transfer(Alice, Defi, 1000);
+ AC->>Token: AuthWit validity
+ deactivate Alice
+ Token->>Token: throw if invalid AuthWit
+ Token->>Token: transfer(Alice, Defi, 1000);
+ Token->>Defi: success
+ deactivate Token
+ Defi->>Defi: deposit(Token, 1000);
+ deactivate Defi
+ deactivate AC
+```
+
+:::info Static call for AuthWit checks
+The call to the account contract for checking authentication should be a static call, meaning that it cannot change state or make calls that change state. If this call is not static, it could be used to re-enter the flow and change the state of the contract.
+:::
+
+:::danger Static call currently unsupported
+The current execution layer does not implement static call. So currently you will be passing along the control flow :grimacing:.
+:::
+
+:::danger Re-entries
+The above flow could be re-entered at token transfer. It is mainly for show to illustrate a logic outline.
+:::
+
+### What about public
+
+As noted earlier, we could use the ERC20 standard for public. But this seems like a waste when we have the ability to try righting some wrongs. Instead, we can expand our AuthWit scheme to also work in public. This is actually quite simple, instead of asking an oracle (which we can't do as easily because not private execution) we can just store the AuthWit in the account contract, and look it up when we need it. While this needs the storage to be updated ahead of time, we can quite easily do so by batching the AuthWit updates with the interaction - a benefit of Account Contracts.
+
+```mermaid
+sequenceDiagram
+ actor Alice
+ participant AC as Alice Account
+ participant Token
+ rect rgb(191, 223, 255)
+ note right of Alice: Alice sends a batch
+ Alice->>AC: Allow Defi to call transfer(Alice, Defi, 1000);
+ activate AC
+ Alice->>AC: Defi.deposit(Token, 1000);
+ end
+ AC->>Defi: deposit(Token, 1000);
+ activate Defi
+ Defi->>Token: transfer(Alice, Defi, 1000);
+ activate Token
+ Token->>AC: Check if Defi may call transfer(Alice, Defi, 1000);
+ AC->>Token: AuthWit validity
+ Token->>Token: throw if invalid AuthWit
+ Token->>Token: transfer(Alice, Defi, 1000);
+ Token->>Defi: success
+ deactivate Token
+ Defi->>Defi: deposit(Token, 1000);
+ deactivate Defi
+ deactivate AC
+```
+
+### Replays
+
+To ensure that the authentication witness can only be used once, we can emit the action itself as a nullifier. This way, the authentication witness can only be used once. This is similar to how notes are used, and we can use the same nullifier scheme for this.
+
+Note however, that it means that the same action cannot be authenticated twice, so if you want to allow the same action to be authenticated multiple times, we should include a nonce in the arguments, such that the action is different each time.
+
+For the transfer, this could be done simply by appending a nonce to the arguments.
+
+```rust
+action = H(defi, token, transfer_selector, H(alice_account, defi, 1000, nonce));
+```
+
+Beware that since the the account contract will be unable to emit the nullifier since it is checked with a static call, so the calling contract must do it. This is similar to nonces in ERC20 tokens today. We provide a small library that handles this which we will see in the [developer documentation](./../../../dev_docs/contracts/resources/common_patterns/authwit.md).
+
+### Differences to approval
+
+The main difference is that we are not setting up an allowance, but allowing the execution of a specific action. We decided on this option as the default since it is more explicit and the user can agree exactly what they are signing.
+
+Also, most uses of the approvals are for contracts where the following interactions are called by the user themselves, so it is not a big issue that they are not as easily "transferrable" as the `permit`s.
+
+### Other use-cases
+
+We don't need to limit ourselves to the `transfer` function, we can use the same scheme for any function that requires authentication. For example, for authenticating to burn or shield assets or to vote in a governance contract or perform an operation on a lending protocol.
+
+### Next Steps
+
+Check out the [developer documentation](./../../../dev_docs/contracts/resources/common_patterns/authwit.md) to see how to implement this in your own contracts.
\ No newline at end of file
diff --git a/docs/docs/dev_docs/contracts/resources/common_patterns/authwit.md b/docs/docs/dev_docs/contracts/resources/common_patterns/authwit.md
new file mode 100644
index 00000000000..6beb7ea887a
--- /dev/null
+++ b/docs/docs/dev_docs/contracts/resources/common_patterns/authwit.md
@@ -0,0 +1,232 @@
+---
+title: Authentication Witness
+description: Developer Documentation to use Authentication Witness for authentication actions on Aztec.
+---
+## Prerequisite reading
+- [Authwit from Foundational Concepts](./../../../../concepts/foundation/accounts/authwit.md)
+
+## Introduction
+
+Authentication Witness is a scheme for authentication actions on Aztec, so users can allow third-parties (eg protocols or other users) to execute an action on their behalf.
+
+How it works logically is explained in the [foundational concepts](./../../../../concepts/foundation/accounts/authwit.md) but we will do a short recap here.
+
+An authentication witness is defined for a specific action, such as allowing a Defi protocol to transfer funds on behalf of the user. An action is here something that could be explained as `A is allowed to perform X operation on behalf of B` and we define it as a hash computed as such:
+
+```rust
+authentication_witness_action = H(
+ caller: AztecAddress,
+ contract: AztecAddress,
+ selector: Field,
+ argsHash: Field
+);
+
+// Example action that authenticates:
+// defi contract to transfer 1000 tokens to itself on behalf of alice_account
+authentication_witness_action = H(
+ defi,
+ token,
+ transfer_selector,
+ H(alice_account, defi, 1000)
+);
+```
+
+Given the action, the developer can ask the `on_behalf_of` account contract if the action is authenticated or not.
+
+```mermaid
+sequenceDiagram
+ actor Alice
+ participant AC as Alice Account
+ participant Token
+ Alice->>AC: Defi.deposit(Token, 1000);
+ activate AC
+ AC->>Defi: deposit(Token, 1000);
+ activate Defi
+ Defi->>Token: transfer(Alice, Defi, 1000);
+ activate Token
+ Token->>AC: Check if Defi may call transfer(Alice, Defi, 1000);
+ AC-->>Alice: Please give me AuthWit for DeFi
calling transfer(Alice, Defi, 1000);
+ activate Alice
+ Alice-->>Alice: Produces Authentication witness
+ Alice-->>AC: AuthWit for transfer(Alice, Defi, 1000);
+ AC->>Token: AuthWit validity
+ deactivate Alice
+ Token->>Token: throw if invalid AuthWit
+ Token->>Token: transfer(Alice, Defi, 1000);
+ Token->>Defi: success
+ deactivate Token
+ Defi->>Defi: deposit(Token, 1000);
+ deactivate Defi
+ deactivate AC
+```
+
+:::info
+Note in particular that the request for a witness is done by the token contract, and the user will have to provide it to the contract before it can continue execution. Since the request is made all the way into the contract where it is to be used, we don't need to pass it along as an extra input to the functions before it which gives us a cleaner interface.
+:::
+
+As part of `AuthWit` we are assuming that the `on_behalf_of` implements the private and/or public functions:
+
+```rust
+#[aztec(private)]
+fn is_valid(message_hash: Field) -> Field;
+
+#[aztec(public)]
+fn is_valid_public(message_hash: Field) -> Field;
+```
+
+Both return the value `0xe86ab4ff` (`is_valid` selector) for a successful authentication, and `0x00000000` for a failed authentication. You might be wondering why we are expecting the return value to be a selector instead of a boolean. This is mainly to account for a case of selector collisions where the same selector is used for different functions, and we don't want an account to mistakenly allow a different function to be called on its behalf - it is hard to return the selector by mistake, but you might have other functions returning a bool.
+
+## The `AuthWit` library.
+
+As part of [Aztec.nr](https://aztec.nr), we are providing a library that can be used to implement authentication witness for your contracts.
+
+This library also provides a basis for account implementations such that these can more easily implement authentication witness. For more on the wallets, see [writing an account contract](./../../../wallets/writing_an_account_contract.md).
+
+For our purposes here (not building a wallet), the most important part of the library is the `auth` utility which exposes a couple of helper methods for computing the action hash, retrieving witnesses, validating them and emitting the nullifier.
+
+### General utilities
+
+The primary general utility is the `compute_authwit_message_hash` function which computes the action hash from its components. This is useful for when you need to generate a hash that is not for the current call, such as when you want to update a public approval state value that is later used for [authentication in public](#updating-approval-state-in-noir).
+
+#include_code compute_authwit_message_hash /yarn-project/aztec-nr/authwit/src/auth.nr rust
+
+#### TypeScript utilities
+
+To make it convenient to compute the message hashes in TypeScript, the `aztec.js` package includes a `computeAuthWitMessageHash` function that you can use.
+
+#include_code authwit_computeAuthWitMessageHash /yarn-project/aztec.js/src/utils/authwit.ts typescript
+
+As you can see above, this function takes a `caller` and a `request`. So let's quickly see how we can get those. Luckily for us, the `request` can be easily prepared similarly to how we are making contract calls from TypeScript.
+
+#include_code authwit_computeAuthWitMessageHash /yarn-project/end-to-end/src/e2e_token_contract.test.ts typescript
+
+### Utilities for private calls
+
+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`.
+
+#include_code assert_current_call_valid_authwit /yarn-project/aztec-nr/authwit/src/auth.nr rust
+
+As seen above, we mainly compute the message hash, and then forward the call to the more generic `assert_valid_authwit`. This validating function will then:
+
+- make a call to `on_behalf_of` to validate that the call is authenticated
+- emit a nullifier for the action to prevent replay attacks
+- throw if the action is not authenticated by `on_behalf_of`
+
+#include_code assert_valid_authwit /yarn-project/aztec-nr/authwit/src/auth.nr rust
+
+### Utilities for public calls
+
+Very similar to above, we have variations that work in the public domain. These functions are wrapped to give a similar flow for both cases, but behind the scenes the logic of the account contracts is slightly different since they cannot use the oracle as they are not in the private domain.
+
+#include_code assert_current_call_valid_authwit_public /yarn-project/aztec-nr/authwit/src/auth.nr rust
+
+#include_code assert_valid_authwit_public /yarn-project/aztec-nr/authwit/src/auth.nr rust
+
+## Usage
+
+Ok, enough talking, how the hell do we use this?
+
+### Importing it
+
+To add it to your project, add the `authwit` library to your `Nargo.toml` file.
+
+```toml
+[dependencies]
+aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="yarn-project/aztec-nr/aztec" }
+authwit = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="yarn-project/aztec-nr/authwit"}
+```
+
+Then you will be able to import it into your contracts as follows.
+
+#include_code import_authwit /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust
+
+### Private Functions
+
+#### Checking if the current call is authenticated
+
+Based on the diagram earlier on this page let's take a look at how we can implement the `transfer` function such that it checks if the tokens are to be transferred `from` the caller or needs to be authenticated with an authentication witness.
+
+#include_code transfer /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust
+
+The first thing we see in the snippet above, is that if `from` is not the call we are calling the `assert_current_call_valid_authwit` function from [earlier](#private-functions). If the call is not throwing, we are all good and can continue with the transfer.
+
+In the snippet we are constraining the `else` case such that only `nonce = 0` is supported. This is not strictly necessary, but because I can't stand dangling useless values. By making it constrained, we can limit what people guess it does, I hope.
+
+#### Authenticating an action in TypeScript
+
+Cool, so we have a function that checks if the current call is authenticated, but how do we actually authenticate it? Well, assuming that we use a wallet that is following the spec, we import `computeAuthWitMessageHash` from `aztec.js` to help us compute the hash, and then we simply `addAuthWitness` to the wallet. Behind the scenes this will make the witness available to the oracle.
+
+#include_code authwit_transfer_example /yarn-project/end-to-end/src/e2e_token_contract.test.ts typescript
+
+### Public Functions
+
+With private functions covered, how can we use this in a public function? Well, the answer is that we simply change one name of a function and then we are good to go :eyes: (almost).
+
+#### Checking if the current call is authenticated
+
+#include_code transfer_public /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust
+
+#### Authenticating an action in TypeScript
+
+Authenticating an action in the public domain is quite similar to the private domain, with the difference that we are executing a function on the account contract to add the witness, if you recall, this is because we don't have access to the oracle in the public domain.
+
+In the snippet below, this is done as a separate contract call, but can also be done as part of a batch as mentioned in the [foundational concepts](./../../../../concepts/foundation/accounts/authwit.md#what-about-public).
+
+#include_code authwit_public_transfer_example /yarn-project/end-to-end/src/e2e_token_contract.test.ts typescript
+
+#### Updating approval state in Noir
+
+We have cases where we need a non-wallet contract to approve an action to be executed by another contract. One of the cases could be when making more complex defi where funds are passed along. When doing so, we need the intermediate contracts to support approving of actions on their behalf.
+
+To support this, we must implement the `is_valid_public` function as seen in the snippet below.
+
+#include_code authwit_uniswap_get /yarn-project/noir-contracts/src/contracts/uniswap_contract/src/main.nr rust
+
+It also needs a way to update those storage values. Since we want the updates to be trustless, we can compute the action based on the function inputs, and then have the contract compute the key at which it must add a `true` to approve the action.
+
+An example of this would be our Uniswap example which performs a cross chain swap on L1. In here, we both do private and public auth witnesses, where the public is set by the uniswap L2 contract itself. In the below snippet, you can see that we compute the action hash, and then update an `approved_action` mapping with the hash as key and `true` as value. When we then call the `token_bridge` to execute afterwards, it reads this value, burns the tokens, and consumes the authentication.
+
+#include_code authwit_uniswap_set /yarn-project/noir-contracts/src/contracts/uniswap_contract/src/main.nr rust
+
+Outlining more of the `swap` flow: this simplified diagram shows how it will look for contracts that are not wallets but also need to support authentication witnesses.
+
+```mermaid
+sequenceDiagram
+ actor A as Alice
+ participant AC as Alice Account
+ participant CC as Crosschain Swap
+ participant TB as Token Bridge
+ participant T as Token
+
+ A->>AC: Swap 1000 token A to B on Uniswap L1
+ activate AC;
+ AC->>CC: Swap 1000 token A to B
+ activate CC;
+ CC->>T: unshield 1000 tokens from Alice Account to CCS
+ activate T;
+ T->>AC: Have you approved this??
+ AC-->>A: Please give me an AuthWit
+ A-->>AC: Here is AuthWit
+ AC-->>AC: Validate AuthWit
+ AC->>T: Yes
+ deactivate T;
+ CC-->>CC: Setting flag to true
+ CC->>TB: Exit 1000 tokens to CCS
+ activate TB;
+ TB->>T: Burn 1000 tokens from CCS
+ activate T;
+ T->>CC: Have you approved this?
+ CC->>T: Yes
+ T-->>T: Burn
+ Token->>Defi: success
+ deactivate T;
+ TB-->>TB: Emit L2->L1 message
+ deactivate TB;
+ CC-->>CC: Emit L2->L1 message
+ deactivate CC;
+ deactivate AC;
+```
+
+:::info **TODO**
+Add a link to the blog-posts.
+:::
diff --git a/docs/sidebars.js b/docs/sidebars.js
index 536e0b9411a..3327fbf99e8 100644
--- a/docs/sidebars.js
+++ b/docs/sidebars.js
@@ -80,7 +80,10 @@ const sidebars = {
label: "Accounts",
type: "category",
link: { type: "doc", id: "concepts/foundation/accounts/main" },
- items: ["concepts/foundation/accounts/keys"],
+ items: [
+ "concepts/foundation/accounts/keys",
+ "concepts/foundation/accounts/authwit",
+ ],
},
"concepts/foundation/contracts",
"concepts/foundation/transactions",
@@ -273,27 +276,28 @@ const sidebars = {
],
},
"dev_docs/contracts/common_errors",
- // {
- // label: "Resources",
- // type: "category",
- // items: [
- // "dev_docs/contracts/resources/style_guide",
- // {
- // label: "Common Patterns",
- // type: "category",
+ {
+ label: "Resources",
+ type: "category",
+ items: [
+ //"dev_docs/contracts/resources/style_guide",
+ {
+ label: "Common Patterns",
+ type: "category",
// link: {
// type: "doc",
// id: "dev_docs/contracts/resources/common_patterns/main",
// },
- // items: [
+ items: [
+ "dev_docs/contracts/resources/common_patterns/authwit",
// "dev_docs/contracts/resources/common_patterns/sending_tokens_to_user",
// "dev_docs/contracts/resources/common_patterns/sending_tokens_to_contract",
// "dev_docs/contracts/resources/common_patterns/access_control",
// "dev_docs/contracts/resources/common_patterns/interacting_with_l1",
- // ],
- // },
- // ],
- // },
+ ],
+ },
+ ],
+ },
// {
// label: "Security Considerations",
// type: "category",
diff --git a/yarn-project/aztec-nr/authwit/src/auth.nr b/yarn-project/aztec-nr/authwit/src/auth.nr
index 751818587a3..2844308f52f 100644
--- a/yarn-project/aztec-nr/authwit/src/auth.nr
+++ b/yarn-project/aztec-nr/authwit/src/auth.nr
@@ -12,36 +12,57 @@ global IS_VALID_PUBLIC_SELECTOR = 0xf3661153;
// @todo #2676 Should use different generator than the payload to limit probability of collisions.
-// Assert that `whom` have authorized `message_hash` with a valid authentication witness
-pub fn assert_valid_authwit(context: &mut PrivateContext, whom: AztecAddress, message_hash: Field) {
- let result = context.call_private_function(whom.address, IS_VALID_SELECTOR, [message_hash])[0];
+// docs:start:assert_valid_authwit
+// Assert that `on_behalf_of` have authorized `message_hash` with a valid authentication witness
+pub fn assert_valid_authwit(context: &mut PrivateContext, on_behalf_of: AztecAddress, message_hash: Field) {
+ let result = context.call_private_function(on_behalf_of.address, IS_VALID_SELECTOR, [message_hash])[0];
context.push_new_nullifier(message_hash, EMPTY_NULLIFIED_COMMITMENT);
assert(result == IS_VALID_SELECTOR, "Message not authorized by account");
}
+// docs:end:assert_valid_authwit
-// Assert that `whom` have authorized the current call with a valid authentication witness
-pub fn assert_current_call_valid_authwit(context: &mut PrivateContext, whom: AztecAddress) {
- let args = [context.msg_sender(), context.this_address(), context.selector(), context.args_hash];
- let message_hash = pedersen_with_separator(args, GENERATOR_INDEX__SIGNATURE_PAYLOAD)[0];
- assert_valid_authwit(context, whom, message_hash);
+// docs:start:assert_current_call_valid_authwit
+// Assert that `on_behalf_of` have authorized the current call with a valid authentication witness
+pub fn assert_current_call_valid_authwit(context: &mut PrivateContext, on_behalf_of: AztecAddress) {
+ // message_hash = H(caller, contract_this, selector, args_hash)
+ let message_hash = pedersen_with_separator(
+ [context.msg_sender(), context.this_address(), context.selector(), context.args_hash],
+ GENERATOR_INDEX__SIGNATURE_PAYLOAD
+ )[0];
+ assert_valid_authwit(context, on_behalf_of, message_hash);
}
+// docs:end:assert_current_call_valid_authwit
-// Assert that `whom` have authorized `message_hash` in a public context
-pub fn assert_valid_authwit_public(context: &mut PublicContext, whom: AztecAddress, message_hash: Field) {
- let result = context.call_public_function(whom.address, IS_VALID_PUBLIC_SELECTOR, [message_hash])[0];
+// docs:start:assert_valid_authwit_public
+// Assert that `on_behalf_of` have authorized `message_hash` in a public context
+pub fn assert_valid_authwit_public(context: &mut PublicContext, on_behalf_of: AztecAddress, message_hash: Field) {
+ let result = context.call_public_function(on_behalf_of.address, IS_VALID_PUBLIC_SELECTOR, [message_hash])[0];
context.push_new_nullifier(message_hash, EMPTY_NULLIFIED_COMMITMENT);
assert(result == IS_VALID_SELECTOR, "Message not authorized by account");
}
+// docs:end:assert_valid_authwit_public
-// Assert that `whom` have authorized the current call in a public context
-pub fn assert_current_call_valid_authwit_public(context: &mut PublicContext, whom: AztecAddress) {
- let args = [context.msg_sender(), context.this_address(), context.selector(), context.args_hash];
- let message_hash = pedersen_with_separator(args, GENERATOR_INDEX__SIGNATURE_PAYLOAD)[0];
- assert_valid_authwit_public(context, whom, message_hash);
+// docs:start:assert_current_call_valid_authwit_public
+// Assert that `on_behalf_of` have authorized the current call in a public context
+pub fn assert_current_call_valid_authwit_public(context: &mut PublicContext, on_behalf_of: AztecAddress) {
+ // message_hash = H(caller, contract_this, selector, args_hash)
+ let message_hash = pedersen_with_separator(
+ [context.msg_sender(), context.this_address(), context.selector(), context.args_hash],
+ GENERATOR_INDEX__SIGNATURE_PAYLOAD
+ )[0];
+ assert_valid_authwit_public(context, on_behalf_of, message_hash);
}
+// docs:end:assert_current_call_valid_authwit_public
+// docs:start:compute_authwit_message_hash
// Compute the message hash to be used by an authentication witness
-pub fn compute_authwit_message_hash(caller: AztecAddress, target: AztecAddress, selector: Field, args: [Field; N]) -> Field {
+pub fn compute_authwit_message_hash(
+ caller: AztecAddress,
+ target: AztecAddress,
+ selector: Field,
+ args: [Field; N]
+) -> Field {
let args_hash = hash_args(args);
pedersen_with_separator([caller.address, target.address, selector, args_hash], GENERATOR_INDEX__SIGNATURE_PAYLOAD)[0]
-}
\ No newline at end of file
+}
+// docs:end:compute_authwit_message_hash
\ No newline at end of file
diff --git a/yarn-project/aztec.js/src/utils/authwit.ts b/yarn-project/aztec.js/src/utils/authwit.ts
index cbad0a84c94..7f38fd271f3 100644
--- a/yarn-project/aztec.js/src/utils/authwit.ts
+++ b/yarn-project/aztec.js/src/utils/authwit.ts
@@ -2,6 +2,7 @@ import { AztecAddress, CircuitsWasm, GeneratorIndex } from '@aztec/circuits.js';
import { pedersenPlookupCompressWithHashIndex } from '@aztec/circuits.js/barretenberg';
import { FunctionCall, PackedArguments } from '@aztec/types';
+// docs:start:authwit_computeAuthWitMessageHash
/**
* Compute an authentication witness message hash from a caller and a request
* H(caller: AztecAddress, target: AztecAddress, selector: Field, args_hash: Field)
@@ -22,3 +23,4 @@ export const computeAuthWitMessageHash = async (caller: AztecAddress, request: F
GeneratorIndex.SIGNATURE_PAYLOAD,
);
};
+// docs:end:authwit_computeAuthWitMessageHash
diff --git a/yarn-project/end-to-end/src/e2e_token_contract.test.ts b/yarn-project/end-to-end/src/e2e_token_contract.test.ts
index bdda6e4cbbc..7606a5f6556 100644
--- a/yarn-project/end-to-end/src/e2e_token_contract.test.ts
+++ b/yarn-project/end-to-end/src/e2e_token_contract.test.ts
@@ -245,14 +245,14 @@ describe('e2e_token_contract', () => {
expect(amount).toBeGreaterThan(0n);
const nonce = Fr.random();
+ // docs:start:authwit_public_transfer_example
const action = asset
.withWallet(wallets[1])
.methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce);
const messageHash = await computeAuthWitMessageHash(accounts[1].address, action.request());
- // We need to compute the message we want to sign and add it to the wallet as approved
- //const messageHash = await transferMessageHash(accounts[1], accounts[0], accounts[1], amount, nonce);
await wallets[0].setPublicAuth(messageHash, true).send().wait();
+ // docs:end:authwit_public_transfer_example
// Perform the transfer
const tx = action.send();
@@ -404,16 +404,17 @@ describe('e2e_token_contract', () => {
expect(amount).toBeGreaterThan(0n);
// We need to compute the message we want to sign and add it to the wallet as approved
+ // docs:start:authwit_transfer_example
+ // docs:start:authwit_computeAuthWitMessageHash
const action = asset
.withWallet(wallets[1])
.methods.transfer(accounts[0].address, accounts[1].address, amount, nonce);
const messageHash = await computeAuthWitMessageHash(accounts[1].address, action.request());
+ // docs:end:authwit_computeAuthWitMessageHash
- // Both wallets are connected to same node and PXE so we could just insert directly using
- // await wallet.signAndAddAuthWitness(messageHash, );
- // But doing it in two actions to show the flow.
const witness = await wallets[0].createAuthWitness(messageHash);
await wallets[1].addAuthWitness(witness);
+ // docs:end:authwit_transfer_example
// Perform the transfer
const tx = action.send();
diff --git a/yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr
index 3c6a4427d2a..73e0c9f3360 100644
--- a/yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr
+++ b/yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr
@@ -33,12 +33,14 @@ contract Token {
selector::compute_selector,
};
+ // docs:start:import_authwit
use dep::authwit::{
auth::{
assert_current_call_valid_authwit,
assert_current_call_valid_authwit_public,
},
};
+ // docs:end:import_authwit
use crate::types::{
transparent_note::{TransparentNote, TransparentNoteMethods, TRANSPARENT_NOTE_LEN},
diff --git a/yarn-project/noir-contracts/src/contracts/uniswap_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/uniswap_contract/src/main.nr
index 754ba49df06..ce007c5672c 100644
--- a/yarn-project/noir-contracts/src/contracts/uniswap_contract/src/main.nr
+++ b/yarn-project/noir-contracts/src/contracts/uniswap_contract/src/main.nr
@@ -180,6 +180,7 @@ contract Uniswap {
1
}
+ // docs:start:authwit_uniswap_get
// Since the token bridge burns funds on behalf of this contract, this contract has to tell the token contract if the signature is valid
// implementation is similar to how account contracts validate public approvals.
// if valid, it returns the IS_VALID selector which is expected by token contract
@@ -192,7 +193,9 @@ contract Uniswap {
0
}
}
+ // docs:end:authwit_uniswap_get
+ // docs:start:authwit_uniswap_set
// This helper method approves the bridge to burn this contract's funds and exits the input asset to L1
// Assumes contract already has funds.
// Assume `token` relates to `token_bridge` (ie token_bridge.token == token)
@@ -222,6 +225,7 @@ contract Uniswap {
nonce_for_burn_approval,
);
}
+ // docs:end:authwit_uniswap_set
#[aztec(public)]
internal fn _assert_token_is_same(token: AztecAddress, token_bridge: AztecAddress) {