How to use authentication witnesses (authwit)
This page assumes you have authwit set up correctly in your contract. To learn how to do that, go here.
For an introduction to authentication witnesses on Aztec, read this explainer.
Import libraries
These are all the libraries you might need for using authwits in Aztec.js:
import {
computeAuthWitMessageHash,
computeInnerAuthWitHash,
} from "@aztec/aztec.js";
You may not need all of these.
Publicly deploy accounts
This is only required if you are using authwits in public
If you are using public authwit (ie using assert_current_call_valid_authwit_public
in your contract), you will need to deploy the following accounts publicly:
- The account that is giving permission to an account to act on behalf of it (authwit giver)
- The account that does the action (authwit receiver)
Here is an example implementation:
export async function ensureAccountsPubliclyDeployed(sender: Wallet, accountsToDeploy: Wallet[]) {
// We have to check whether the accounts are already deployed. This can happen if the test runs against
// the sandbox and the test accounts exist
const accountsAndAddresses = await Promise.all(
accountsToDeploy.map(async account => {
const address = account.getAddress();
return {
address,
deployed: await sender.isContractPubliclyDeployed(address),
};
}),
);
const instances = await Promise.all(
accountsAndAddresses.filter(({ deployed }) => !deployed).map(({ address }) => sender.getContractInstance(address)),
);
const contractClass = getContractClassFromArtifact(SchnorrAccountContractArtifact);
if (!(await sender.isContractClassPubliclyRegistered(contractClass.id))) {
await (await registerContractClass(sender, SchnorrAccountContractArtifact)).send().wait();
}
const batch = new BatchCall(sender, [...instances.map(instance => deployInstance(sender, instance!).request())]);
await batch.send().wait();
}
Source code: yarn-project/end-to-end/src/fixtures/utils.ts#L536-L559
You would then call this like so:
await ensureAccountsPubliclyDeployed(wallets[0], wallets.slice(0, 2));
Source code: yarn-project/end-to-end/src/e2e_authwit.test.ts#L24-L26
Define the action
When creating an authwit, you will need to pass the authwit giver, the authwit receiver (who will perform the action), and the action that is being authorized. The action can be a smart contract function call, or alternatively, arbitrary data.
When the action is a function call
You can define the action like this:
const action = asset
.withWallet(wallets[1])
.methods.transfer(wallets[0].getAddress(), wallets[1].getAddress(), amount, nonce);
Source code: yarn-project/end-to-end/src/e2e_blacklist_token_contract/transfer_private.test.ts#L55-L59
In this example,
asset
refers to a token contractwithWallet(wallets[1])
is specifying the authwit receiver (wallets[1]
) will do this action.methods.transfer()
is specifying that the action is calling thetransfer
method on the token contract(wallets[0].getAddress(), wallets[1].getAddress(), amount, nonce);
are the args of this method - it will send theamount
fromwallets[0]
towallets[1]
Arbitrary message
You can hash your own authwit message by creating an inner hash with the data, like this:
const innerHash = computeInnerAuthWitHash([Fr.fromHexString('0xdead')]);
Source code: yarn-project/end-to-end/src/e2e_authwit.test.ts#L46-L48
Then create the message hash by hashing the inner hash with the authwit receiver address, chainId, and version:
const intent = { consumer: auth.address, innerHash };
Source code: yarn-project/end-to-end/src/e2e_authwit.test.ts#L49-L52
Create the authwit
There are slightly different interfaces depending on whether your contract is checking the authwit in private or public.
Public authwits are stored in the account contract and batched with the authwit action call, so a user must send a transaction to update their account contract, authorizing an action before the authorized contract's public call will succeed.
Private execution uses oracles and are executed locally by the PXE, so the authwit needs to be created by the authwit giver and then added to the authwit receiver's PXE.
Private
This is expected to be used alongside private authwits in Aztec.nr contract.
Create a private authwit like this:
const witness = await wallets[0].createAuthWit({ caller: wallets[1].getAddress(), action });
Source code: yarn-project/end-to-end/src/e2e_blacklist_token_contract/transfer_private.test.ts#L60-L62
In this example,
wallets[0]
is the authwit giverwallets[1]
is the authwit receiver and caller of the functionaction
was defined previously
If you created an arbitrary message, you can create the authwit by replacing these params with the outer hash:
const intent = { consumer: auth.address, innerHash };
Source code: yarn-project/end-to-end/src/e2e_authwit.test.ts#L49-L52
Then add it to the wallet of the authwit receiver (the caller of the function):
await wallets[1].addAuthWitness(witness);
Source code: yarn-project/end-to-end/src/e2e_blacklist_token_contract/transfer_private.test.ts#L63-L65
Public
This is expected to be used alongside public authwits in Aztec.nr contract.
Set a public authwit like this:
await wallets[0].setPublicAuthWit({ caller: wallets[1].getAddress(), action }, true).send().wait();
Source code: yarn-project/end-to-end/src/e2e_blacklist_token_contract/transfer_public.test.ts#L115-L117
Remember it is a transaction and calls a method in the account contract. In this example,
wallets[0]
is the authwit giverwallets[1]
is the authwit receiver and caller of the functionaction
was defined previouslytrue
sets theauthorized
boolean (false
would revoke this authwit)
If you created an arbitrary message, you would replace the first param struct with the outer hash:
await wallets[0].setPublicAuthWit(intent, true).send().wait();
Source code: yarn-project/end-to-end/src/e2e_authwit.test.ts#L170-L172