Skip to main content
Version: dev

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

note

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:

  1. The account that is giving permission to an account to act on behalf of it (authwit giver)
  2. The account that does the action (authwit receiver)

Here is an example implementation:

public_deploy_accounts
export async function ensureAccountContractsPublished(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.getContractMetadata(address)).isContractPublished,
};
}),
);
const instances = (
await Promise.all(
accountsAndAddresses
.filter(({ deployed }) => !deployed)
.map(({ address }) => sender.getContractMetadata(address)),
)
).map(contractMetadata => contractMetadata.contractInstance);
const contractClass = await getContractClassFromArtifact(SchnorrAccountContractArtifact);
if (!(await sender.getContractClassMetadata(contractClass.id, true)).isContractClassPubliclyRegistered) {
await (await publishContractClass(sender, SchnorrAccountContractArtifact))
.send({ from: accountsToDeploy[0].getAddress() })
.wait();
}
const requests = await Promise.all(instances.map(async instance => await publishInstance(sender, instance!)));
const batch = new BatchCall(sender, requests);
await batch.send({ from: accountsToDeploy[0].getAddress() }).wait();
}
Source code: yarn-project/end-to-end/src/fixtures/utils.ts#L735-L765

You would then call this like so:

public_deploy_accounts
[account1, account2] = wallets;
[account1Address, account2Address] = accounts;
await ensureAccountContractsPublished(account1, wallets.slice(0, 2));
Source code: yarn-project/end-to-end/src/e2e_authwit.test.ts#L34-L38

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:

authwit_computeAuthWitMessageHash
const action = asset.withWallet(other).methods.transfer(adminAddress, otherAddress, amount, authwitNonce);
Source code: yarn-project/end-to-end/src/e2e_blacklist_token_contract/transfer_private.test.ts#L53-L55

In this example,

  • asset refers to a token contract
  • withWallet(wallets[1]) is specifying the authwit receiver (wallets[1]) will do this action
  • .methods.transfer() is specifying that the action is calling the transfer method on the token contract
  • (wallets[0].getAddress(), wallets[1].getAddress(), amount, nonce); are the args of this method - it will send the amount from wallets[0] to wallets[1]

Arbitrary message

You can hash your own authwit message by creating an inner hash with the data, like this:

compute_inner_authwit_hash
const innerHash = await computeInnerAuthWitHash([Fr.fromHexString('0xdead')]);
Source code: yarn-project/end-to-end/src/e2e_authwit.test.ts#L57-L59

Then create the message hash by hashing the inner hash with the authwit receiver address, chainId, and version:

compute_arbitrary_authwit_hash

const intent = { consumer: auth.address, innerHash };
Source code: yarn-project/end-to-end/src/e2e_authwit.test.ts#L60-L63

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:

create_authwit
const witness = await admin.createAuthWit({ caller: otherAddress, action });
Source code: yarn-project/end-to-end/src/e2e_blacklist_token_contract/transfer_private.test.ts#L56-L58

In this example,

  • wallets[0] is the authwit giver
  • wallets[1] is the authwit receiver and caller of the function
  • action was defined previously

If you created an arbitrary message, you can create the authwit by replacing these params with the outer hash:

compute_arbitrary_authwit_hash

const intent = { consumer: auth.address, innerHash };
Source code: yarn-project/end-to-end/src/e2e_authwit.test.ts#L60-L63

Then add it to the wallet of the authwit receiver (the caller of the function):

add_authwit
await action.send({ from: otherAddress, authWitnesses: [witness] }).wait();
Source code: yarn-project/end-to-end/src/e2e_blacklist_token_contract/transfer_private.test.ts#L62-L64

Public

This is expected to be used alongside public authwits in Aztec.nr contract.

Set a public authwit like this:

set_public_authwit
const validateActionInteraction = await admin.setPublicAuthWit({ caller: otherAddress, action }, true);
await validateActionInteraction.send({ from: adminAddress }).wait();
Source code: yarn-project/end-to-end/src/e2e_blacklist_token_contract/transfer_public.test.ts#L118-L121

Remember it is a transaction and calls a method in the account contract. In this example,

  • wallets[0] is the authwit giver
  • wallets[1] is the authwit receiver and caller of the function
  • action was defined previously
  • true sets the authorized boolean (false would revoke this authwit)

If you created an arbitrary message, you would replace the first param struct with the outer hash:

set_public_authwit
const validateActionInteraction = await account1.setPublicAuthWit(intent, true);
await validateActionInteraction.send({ from: account1Address }).wait();
Source code: yarn-project/end-to-end/src/e2e_authwit.test.ts#L167-L170

Further reading