Skip to content

Commit

Permalink
feat(AuthWit): simplify create authwit syntax (#5132)
Browse files Browse the repository at this point in the history
Fixes #4821 and #5075. 

Renames the functions related to `AuthWit` to use a similar naming scheme.

Extends the `createAuthWit`, `setPublicAuthWit` and `cancelAuthWit` functions such that they can now take an object 
```typescript
{
  caller: AztecAddress;
  action: ContractFunctionInteraction | FunctionCall;
},
```

Allowing for adding a new authwit more simply.

Example:
```typescript
await user1Wallet.createAuthWit({
  caller: l2Bridge.address,
  action: l2Token.methods.burn(ownerAddress, withdrawAmount, nonce)
});
```
  • Loading branch information
LHerskind authored Mar 18, 2024
1 parent 86e1a86 commit d0a5b19
Show file tree
Hide file tree
Showing 27 changed files with 325 additions and 223 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,12 @@ For our purposes here (not building a wallet), the most important part of the li

### 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). You can view the implementation of this function [here](https://github.com/AztecProtocol/aztec-packages/blob/master/noir-projects/aztec-nr/authwit/src/auth.nr).
The primary general utility is the `compute_call_authwit_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). You can view the implementation of this function [here](https://github.com/AztecProtocol/aztec-packages/blob/master/noir-projects/aztec-nr/authwit/src/auth.nr).

#### 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. Implementation [here](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec.js/src/utils/authwit.ts).

As you can see above, this function takes a `caller` and a `request`. 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`.
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/developers/wallets/main.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ There are no proofs generated as of the Sandbox release. This will be included i

Account contracts in Aztec expose an interface for other contracts to validate [whether an action is authorized by the account or not](../../learn/concepts/accounts/main.md#authorizing-actions). For example, an application contract may want to transfer tokens on behalf of a user, in which case the token contract will check with the account contract whether the application is authorized to do so. These actions may be carried out in private or in public functions, and in transactions originated by the user or by someone else.

Wallets should manage these authorizations, prompting the user when they are requested by an application. Authorizations in private executions come in the form of _auth witnesses_, which are usually signatures over an identifier for an action. Applications can request the wallet to produce an auth witness via the `createAuthWitness` call. In public functions, authorizations are pre-stored in the account contract storage, which is handled by a call to an internal function in the account contract implementation.
Wallets should manage these authorizations, prompting the user when they are requested by an application. Authorizations in private executions come in the form of _auth witnesses_, which are usually signatures over an identifier for an action. Applications can request the wallet to produce an auth witness via the `createAuthWit` call. In public functions, authorizations are pre-stored in the account contract storage, which is handled by a call to an internal function in the account contract implementation.

## Key management

Expand Down
4 changes: 2 additions & 2 deletions noir-projects/aztec-nr/authwit/src/auth.nr
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ pub fn assert_current_call_valid_authwit_public(context: &mut PublicContext, on_
}
// docs:end:assert_current_call_valid_authwit_public

// docs:start:compute_authwit_message_hash
// docs:start:compute_call_authwit_hash
// Compute the message hash to be used by an authentication witness
pub fn compute_call_authwit_hash<N>(caller: AztecAddress, consumer: AztecAddress, selector: FunctionSelector, args: [Field; N]) -> Field {
let args_hash = hash_args(args);
let inner_hash = compute_inner_authwit_hash([caller.to_field(), selector.to_field(), args_hash]);
compute_outer_authwit_hash(consumer, inner_hash)
}
// docs:end:compute_authwit_message_hash
// docs:end:compute_call_authwit_hash

pub fn compute_inner_authwit_hash<N>(args: [Field; N]) -> Field {
pedersen_hash(args, GENERATOR_INDEX__AUTHWIT_INNER)
Expand Down
4 changes: 2 additions & 2 deletions yarn-project/accounts/src/defaults/account_interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export class DefaultAccountInterface implements AccountInterface {
return this.entrypoint.createTxExecutionRequest(executions, fee);
}

createAuthWitness(message: Fr): Promise<AuthWitness> {
return this.authWitnessProvider.createAuthWitness(message);
createAuthWit(message: Fr): Promise<AuthWitness> {
return this.authWitnessProvider.createAuthWit(message);
}

getCompleteAddress(): CompleteAddress {
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/accounts/src/ecdsa/account_contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class EcdsaAccountContract extends DefaultAccountContract {
class EcdsaAuthWitnessProvider implements AuthWitnessProvider {
constructor(private signingPrivateKey: Buffer) {}

createAuthWitness(message: Fr): Promise<AuthWitness> {
createAuthWit(message: Fr): Promise<AuthWitness> {
const ecdsa = new Ecdsa();
const signature = ecdsa.constructSignature(message.toBuffer(), this.signingPrivateKey);
return Promise.resolve(new AuthWitness(message, [...signature.r, ...signature.s]));
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/accounts/src/schnorr/account_contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class SchnorrAccountContract extends DefaultAccountContract {
class SchnorrAuthWitnessProvider implements AuthWitnessProvider {
constructor(private signingPrivateKey: GrumpkinPrivateKey) {}

createAuthWitness(message: Fr): Promise<AuthWitness> {
createAuthWit(message: Fr): Promise<AuthWitness> {
const schnorr = new Schnorr();
const signature = schnorr.constructSignature(message.toBuffer(), this.signingPrivateKey).toBuffer();
return Promise.resolve(new AuthWitness(message, [...signature]));
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/accounts/src/single_key/account_contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export class SingleKeyAccountContract extends DefaultAccountContract {
class SingleKeyAuthWitnessProvider implements AuthWitnessProvider {
constructor(private privateKey: GrumpkinPrivateKey, private partialAddress: PartialAddress) {}

createAuthWitness(message: Fr): Promise<AuthWitness> {
createAuthWit(message: Fr): Promise<AuthWitness> {
const schnorr = new Schnorr();
const signature = schnorr.constructSignature(message.toBuffer(), this.privateKey);
const publicKey = generatePublicKey(this.privateKey);
Expand Down
20 changes: 17 additions & 3 deletions yarn-project/aztec.js/src/account/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AuthWitness, CompleteAddress, FunctionCall, TxExecutionRequest } from '
import { AztecAddress } from '@aztec/circuits.js';
import { Fr } from '@aztec/foundation/fields';

import { ContractFunctionInteraction } from '../contract/contract_function_interaction.js';
import { FeePaymentMethod } from '../fee/fee_payment_method.js';

/**
Expand All @@ -18,10 +19,23 @@ export type FeeOptions = {
/** Creates authorization witnesses. */
export interface AuthWitnessProvider {
/**
* Create an authorization witness for the given message.
* @param message - Message to authorize.
* 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 messageHashOrIntent - The message hash or the intent (caller and action) to approve
* @returns The authentication witness
*/
createAuthWitness(message: Fr): Promise<AuthWitness>;
createAuthWit(
messageHashOrIntent:
| Fr
| Buffer
| {
/** The caller to approve */
caller: AztecAddress;
/** The action to approve */
action: ContractFunctionInteraction | FunctionCall;
},
): Promise<AuthWitness>;
}

/** Creates transaction execution requests out of a set of function calls. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class PrivateFeePaymentMethod implements FeePaymentMethod {
functionData: new FunctionData(FunctionSelector.fromSignature('unshield((Field),(Field),Field,Field)'), true),
to: this.asset,
});
await this.wallet.createAuthWitness(messageHash);
await this.wallet.createAuthWit(messageHash);

const secretHashForRebate = computeMessageSecretHash(this.rebateSecret);

Expand Down
2 changes: 1 addition & 1 deletion yarn-project/aztec.js/src/fee/public_fee_payment_method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export class PublicFeePaymentMethod implements FeePaymentMethod {
});

return Promise.resolve([
this.wallet.setPublicAuth(messageHash, true).request(),
this.wallet.setPublicAuthWit(messageHash, true).request(),
{
to: this.getPaymentContract(),
functionData: new FunctionData(
Expand Down
10 changes: 5 additions & 5 deletions yarn-project/aztec.js/src/utils/authwit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ import { pedersenHash } from '@aztec/foundation/crypto';
* `bob` then signs the message hash and gives it to `alice` who can then perform the
* action.
* @param caller - The caller approved to make the call
* @param request - The request to be made (function call)
* @param action - The request to be made (function call)
* @returns The message hash for the witness
*/
export const computeAuthWitMessageHash = (caller: AztecAddress, request: FunctionCall) => {
export const computeAuthWitMessageHash = (caller: AztecAddress, action: FunctionCall) => {
return computeOuterAuthWitHash(
request.to.toField(),
action.to.toField(),
computeInnerAuthWitHash([
caller.toField(),
request.functionData.selector.toField(),
PackedArguments.fromArgs(request.args).hash,
action.functionData.selector.toField(),
PackedArguments.fromArgs(action.args).hash,
]),
);
};
Expand Down
85 changes: 76 additions & 9 deletions yarn-project/aztec.js/src/wallet/account_wallet.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { AuthWitness, FunctionCall, PXE, TxExecutionRequest } from '@aztec/circuit-types';
import { Fr } from '@aztec/circuits.js';
import { AztecAddress, Fr } from '@aztec/circuits.js';
import { ABIParameterVisibility, FunctionAbi, FunctionType } from '@aztec/foundation/abi';

import { AccountInterface, FeeOptions } from '../account/interface.js';
import { ContractFunctionInteraction } from '../contract/contract_function_interaction.js';
import { computeAuthWitMessageHash } from '../utils/authwit.js';
import { BaseWallet } from './base_wallet.js';

/**
Expand All @@ -18,21 +19,76 @@ export class AccountWallet extends BaseWallet {
return this.account.createTxExecutionRequest(execs, fee);
}

async createAuthWitness(message: Fr | Buffer): Promise<AuthWitness> {
message = Buffer.isBuffer(message) ? Fr.fromBuffer(message) : message;
const witness = await this.account.createAuthWitness(message);
/**
* Computes an authentication witness from either a message or a caller and an action.
* If a message is provided, it will create a witness for the message directly.
* Otherwise, it will compute the message using the caller and the action.
* @param messageHashOrIntent - The message or the caller and action to approve
* @returns The authentication witness
*/
async createAuthWit(
messageHashOrIntent:
| Fr
| Buffer
| {
/** The caller to approve */
caller: AztecAddress;
/** The action to approve */
action: ContractFunctionInteraction | FunctionCall;
},
): Promise<AuthWitness> {
const messageHash = this.getMessageHash(messageHashOrIntent);
const witness = await this.account.createAuthWit(messageHash);
await this.pxe.addAuthWitness(witness);
return witness;
}

/**
* Returns a function interaction to set a message hash as authorized in this account.
* Returns the message hash for the given message or authwit input.
* @param messageHashOrIntent - The message hash or the caller and action to authorize
* @returns The message hash
*/
private getMessageHash(
messageHashOrIntent:
| Fr
| Buffer
| {
/** The caller to approve */
caller: AztecAddress;
/** The action to approve */
action: ContractFunctionInteraction | FunctionCall;
},
): Fr {
if (Buffer.isBuffer(messageHashOrIntent)) {
return Fr.fromBuffer(messageHashOrIntent);
} else if (messageHashOrIntent instanceof Fr) {
return messageHashOrIntent;
} else if (messageHashOrIntent.action instanceof ContractFunctionInteraction) {
return computeAuthWitMessageHash(messageHashOrIntent.caller, messageHashOrIntent.action.request());
}
return computeAuthWitMessageHash(messageHashOrIntent.caller, messageHashOrIntent.action);
}

/**
* Returns a function interaction to set a message hash as authorized or revoked in this account.
* Public calls can then consume this authorization.
* @param message - Message hash to authorize.
* @param messageHashOrIntent - The message or the caller and action to authorize/revoke
* @param authorized - True to authorize, false to revoke authorization.
* @returns - A function interaction.
*/
public setPublicAuth(message: Fr | Buffer, authorized: boolean): ContractFunctionInteraction {
public setPublicAuthWit(
messageHashOrIntent:
| Fr
| Buffer
| {
/** The caller to approve */
caller: AztecAddress;
/** The action to approve */
action: ContractFunctionInteraction | FunctionCall;
},
authorized: boolean,
): ContractFunctionInteraction {
const message = this.getMessageHash(messageHashOrIntent);
if (authorized) {
return new ContractFunctionInteraction(this, this.getAddress(), this.getApprovePublicAuthwitAbi(), [message]);
} else {
Expand All @@ -42,10 +98,21 @@ export class AccountWallet extends BaseWallet {

/**
* Returns a function interaction to cancel a message hash as authorized in this account.
* @param message - Message hash to authorize.
* @param messageHashOrIntent - The message or the caller and action to authorize/revoke
* @returns - A function interaction.
*/
public cancelAuthWit(message: Fr | Buffer): ContractFunctionInteraction {
public cancelAuthWit(
messageHashOrIntent:
| Fr
| Buffer
| {
/** The caller to approve */
caller: AztecAddress;
/** The action to approve */
action: ContractFunctionInteraction | FunctionCall;
},
): ContractFunctionInteraction {
const message = this.getMessageHash(messageHashOrIntent);
const args = [message];
return new ContractFunctionInteraction(this, this.getAddress(), this.getCancelAuthwitAbi(), args);
}
Expand Down
13 changes: 12 additions & 1 deletion yarn-project/aztec.js/src/wallet/base_wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { NodeInfo } from '@aztec/types/interfaces';

import { FeeOptions } from '../account/interface.js';
import { Wallet } from '../account/wallet.js';
import { ContractFunctionInteraction } from '../contract/contract_function_interaction.js';

/**
* A base class for Wallet implementations
Expand All @@ -32,7 +33,17 @@ export abstract class BaseWallet implements Wallet {

abstract createTxExecutionRequest(execs: FunctionCall[], fee?: FeeOptions): Promise<TxExecutionRequest>;

abstract createAuthWitness(message: Fr): Promise<AuthWitness>;
abstract createAuthWit(
messageHashOrIntent:
| Fr
| Buffer
| {
/** The caller to approve */
caller: AztecAddress;
/** The action to approve */
action: ContractFunctionInteraction | FunctionCall;
},
): Promise<AuthWitness>;

getAddress() {
return this.getCompleteAddress().address;
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/aztec.js/src/wallet/signerless_wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class SignerlessWallet extends BaseWallet {
throw new Error('Method not implemented.');
}

createAuthWitness(_message: Fr): Promise<AuthWitness> {
createAuthWit(_message: Fr): Promise<AuthWitness> {
throw new Error('Method not implemented.');
}
}
8 changes: 4 additions & 4 deletions yarn-project/end-to-end/src/e2e_authwit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('e2e_authwit_tests', () => {
const innerHash = computeInnerAuthWitHash([Fr.fromString('0xdead')]);
const outerHash = computeOuterAuthWitHash(wallets[1].getAddress(), innerHash);

const witness = await wallets[0].createAuthWitness(outerHash);
const witness = await wallets[0].createAuthWit(outerHash);
await wallets[1].addAuthWitness(witness);

const c = await SchnorrAccountContract.at(wallets[0].getAddress(), wallets[0]);
Expand All @@ -36,7 +36,7 @@ describe('e2e_authwit_tests', () => {
const innerHash = computeInnerAuthWitHash([Fr.fromString('0xdead'), Fr.fromString('0xbeef')]);
const outerHash = computeOuterAuthWitHash(wallets[1].getAddress(), innerHash);

const witness = await wallets[0].createAuthWitness(outerHash);
const witness = await wallets[0].createAuthWit(outerHash);
await wallets[1].addAuthWitness(witness);
await wallets[0].cancelAuthWit(outerHash).send().wait();

Expand All @@ -55,7 +55,7 @@ describe('e2e_authwit_tests', () => {
const innerHash = computeInnerAuthWitHash([Fr.fromString('0xdead'), Fr.fromString('0x01')]);
const outerHash = computeOuterAuthWitHash(wallets[1].getAddress(), innerHash);

await wallets[0].setPublicAuth(outerHash, true).send().wait();
await wallets[0].setPublicAuthWit(outerHash, true).send().wait();

const c = await SchnorrAccountContract.at(wallets[0].getAddress(), wallets[0]);
await c.withWallet(wallets[1]).methods.spend_public_authwit(innerHash).send().wait();
Expand All @@ -66,7 +66,7 @@ describe('e2e_authwit_tests', () => {
const innerHash = computeInnerAuthWitHash([Fr.fromString('0xdead'), Fr.fromString('0x02')]);
const outerHash = computeOuterAuthWitHash(wallets[1].getAddress(), innerHash);

await wallets[0].setPublicAuth(outerHash, true).send().wait();
await wallets[0].setPublicAuthWit(outerHash, true).send().wait();

await wallets[0].cancelAuthWit(outerHash).send().wait();

Expand Down
Loading

0 comments on commit d0a5b19

Please sign in to comment.