ARC-403: AuthToken

Aztec Request for Comment 403, aka AuthToken, an extension of the ARC-20, named after the Forbidden Error.

Motivation

Aztec’s blockchain infrastructure provides transaction confidentiality through its ARC-20 Token standard (aka Vanilla). Within this framework, users exercise granular control over their privacy, choosing between transferring private balances, visible only to intended recipients, or public balances, fully transparent to all network observers.

While this privacy-by-design approach fulfills Aztec’s core mission, many token issuers express a legitimate need for conditional privacy. Specifically, they seek mechanisms to ensure that transaction confidentiality privileges extend exclusively to validated participants, e.g. those who have completed KYC procedures, belong to permissible jurisdictions, or satisfy other compliance parameters.

This requirement becomes particularly critical in Aztec’s privacy-centric environment, where the attack surface for sybil manipulation is expansive. The blockchain’s privacy features mean that new account contracts remain invisible to observers, creating a significant challenge for implementing conventional compliance frameworks.

What’s needed is a cryptographic solution that preserves the integrity of Aztec’s privacy guarantees while enabling issuers to verify participant eligibility without compromising the token composability. Such a mechanism would allow legitimate regulatory requirements to coexist with privacy, rather than standing in opposition to it, while preserving full compatibility with ARC-20 specific apps.

Specs

Preserving the ARC-20 interface is important for being fully compatible with ARC-20 specific apps (e.g. an AMM), hence, none of the transfer / minting / burning interfaces should be modified to apply the compliance layer to the Token.

Leveraging the versatile ARC-20 constructor (used to initialize, for example, capped supply tokens or mintable ones), an address is being added to it, called transfer_authority, that in case is different than AztecAddress::zero(), it will execute an external call to it each time the token is being transferred.

The ARC-403 specifies the calling interface to the Transfer Authority, as well as invites compliance framework creators to jump into the conversation of what the ideal interface should look like and design transfer authority contracts that can adapt to multiple use cases.

// on each Token Transfer...
if (!transfer_authority.eq(AztecAddress::zero())) {
    ComplianceCheck::at(transfer_authority)
        .authorize_transfer_{private/public}(
		        context.msg_sender(), 
		        from, 
		        amount
				).call(context);
}

The Transfer Authority will then have available the following information:

fn authorize_transfer_{private/public}(_sender: AztecAddress, _from: AztecAddress, _amount: u128) {
		context.msg_sender(); // the Token being transferred
		_sender; // the requestor of the token transfer (e.g. the AMM)
		_from;   // the account from which the tokens are being transferred
		_amount; // the amount of tokens being transferred

		// only in private context 👇🏻
		unsafe { capsules::load(...) } // arbitrary data (e.g. a zk Proof)
}

To be noticed:

  • The Transfer Authority is, within this (opinionated) Token implementation, an immutable AztecAddress, that may be an upgradeable contract
  • The recipient of the transfer isn’t being passed in the call, yet whenever the recipient wants to spend its balance, it’ll need to be authorized (warning: this can lead to bricked funds)
  • The Transfer Authority may include any logic within the authorize_transfer_* call

Open questions:

  • Is authorize_transfer_public needed?

    This would mean authorizing, for example, an AMM contract, as when a user swaps token A for token B (being token B an AuthToken), the AMM will appear in the Transfer Authority as the “sender”.

  • Is the transfer nonce needed?

    The nonce is currently being used to disambiguate private transfer Authwits, it could be sent in the authorize_transfer_private call, in order to maintain its functionality, and disambiguate also proofs being generated to authorize transfers.

Possible implementations

This section is supposed to work as an inspiration to the logic that may live within the Transfer Authorization contracts. These are NOT to be implemented per-se, but showing what’s the possible scope of design of this feature.

  • Transfer accumulator:

    An entity could perform KYC on its users, and allow an amount to be authorized to be transferred, without the entity knowing which amount has each user transferred.

    • On KYC, the entity would create an initial note of H(user,allowance,nonce), let’s say H(alice, 10_000, 0).
    • When Alice spends some tokens (let’s say, spends 1000), she’ll nullify this Note, creating a new one: H(alice, 9000, 1), while the circuit will check that:
      • Another Note with H(alice, X, 1) doesn’t exist
      • The allowance amount hasn’t underflowed (Alice didn’t spend more than the allowed amount)
    • On the next transfer, Alice creates a new note: H(alice, 8000, 2), and the circuit enforces the same checks.
    • When Alice decides so, because she has depleted her allowance, or because she just wants to, she’ll return to the entity to reset her allowance (providing perhaps, once again, her KYC documents).
  • Leaking information:

    The Transfer Authority may not implement any checks, but enforce an encrypted log to themselves, in order to be able to provide information to authorities in case being asked.

  • Signature by Authority:

    The entity could create an off-chain channel in which the sender can request a signed authorization, that the circuit may enforce to be correct. This would allow the entity to control which transfers happen, while the users keep their privacy when interacting with the blockchain apps.

  • ZK Proving Identity:

    As mentioned in the motivation, ensuring participant eligibility for a service provision (as a Token may be understood) is important for the issuer entities, this is nowadays enabled by, for example, ZkPassport checks. Where an entity could chose to allow the token to be transferred by: elder than 18 yo, participants of a certain nationality, or other information provided by their SDK.

    It’s important to note, that an Account must be permanently linked to 1 Identity, to avoid these proofs being shared to skip these checks, while 1 Identity may have more than 1 Accounts.

1 Like