Skip to content

Commit

Permalink
refactor: Update authwit computation (#2651)
Browse files Browse the repository at this point in the history
Fixes #2448
  • Loading branch information
LHerskind authored Oct 4, 2023
1 parent e262793 commit fdbe2b2
Show file tree
Hide file tree
Showing 38 changed files with 354 additions and 537 deletions.
4 changes: 2 additions & 2 deletions docs/docs/dev_docs/wallets/writing_an_account_contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ Public Key: 0x0ede151adaef1cfcc1b3e152ea39f00c5cda3f3857cef00decb049d283672dc71

The important part of this contract is the `entrypoint` function, which will be the first function executed in any transaction originated from this account. This function has two main responsibilities: authenticating the transaction and executing calls. It receives a `payload` with the list of function calls to execute, and requests a corresponding auth witness from an oracle to validate it. You will find this logic implemented in the `AccountActions` module, which uses the `EntrypointPayload` struct:

#include_code entrypoint yarn-project/aztec-nr/aztec/src/account.nr rust
#include_code entrypoint yarn-project/aztec-nr/authwit/src/account.nr rust

#include_code entrypoint-struct yarn-project/aztec-nr/aztec/src/entrypoint.nr rust
#include_code entrypoint-struct yarn-project/aztec-nr/authwit/src/entrypoint.nr rust

:::info
Using the `AccountActions` module and the `EntrypointPayload` struct is not mandatory. You can package the instructions to be carried out by your account contract however you want. However, using these modules can save you a lot of time when writing a new account contract, both in Noir and in Typescript.
Expand Down
8 changes: 8 additions & 0 deletions yarn-project/aztec-nr/authwit/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "auth_wit"
authors = ["aztec-labs"]
compiler_version = "0.1"
type = "lib"

[dependencies]
aztec = { path = "../aztec" }
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
mod entrypoint;
mod auth;

use dep::aztec::context::{PrivateContext, PublicContext, Context};
use dep::aztec::oracle::compute_selector::compute_selector;
use dep::aztec::state_vars::{map::Map, public_state::PublicState};
use dep::aztec::types::type_serialization::bool_serialization::{BoolSerializationMethods,BOOL_SERIALIZED_LEN};

use crate::entrypoint::EntrypointPayload;
use crate::context::{PrivateContext, PublicContext, Context};
use crate::oracle::compute_selector::compute_selector;
use crate::state_vars::{map::Map, public_state::PublicState};
use crate::types::type_serialization::bool_serialization::{BoolSerializationMethods,BOOL_SERIALIZED_LEN};
use crate::auth::IS_VALID_SELECTOR;

struct AccountActions {
Expand Down
47 changes: 47 additions & 0 deletions yarn-project/aztec-nr/authwit/src/auth.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use dep::std::hash::pedersen_with_separator;

use dep::aztec::{
context::{PrivateContext, PublicContext, Context},
constants_gen::{EMPTY_NULLIFIED_COMMITMENT, GENERATOR_INDEX__SIGNATURE_PAYLOAD},
types::address::AztecAddress,
abi::hash_args,
};

global IS_VALID_SELECTOR = 0xe86ab4ff;
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
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];
context.push_new_nullifier(message_hash, EMPTY_NULLIFIED_COMMITMENT);
assert(result == IS_VALID_SELECTOR, "Message not authorized by account");
}

// Assert that `whom` have authorized the current call with a valid authentication witness
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);
}

// Assert that `whom` have authorized `message_hash` in a public context
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];
context.push_new_nullifier(message_hash, EMPTY_NULLIFIED_COMMITMENT);
assert(result == IS_VALID_SELECTOR, "Message not authorized by account");
}

// Assert that `whom` have authorized the current call in a public context
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);
}

// Compute the message hash to be used by an authentication witness
fn compute_authwit_message_hash<N>(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]
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::abi;
use crate::types::vec::BoundedVec;
use crate::context::PrivateContext;
use crate::private_call_stack_item::PrivateCallStackItem;
use crate::public_call_stack_item::PublicCallStackItem;
use crate::constants_gen::GENERATOR_INDEX__SIGNATURE_PAYLOAD;
use dep::aztec::abi;
use dep::aztec::types::vec::BoundedVec;
use dep::aztec::context::PrivateContext;
use dep::aztec::private_call_stack_item::PrivateCallStackItem;
use dep::aztec::public_call_stack_item::PublicCallStackItem;
use dep::aztec::constants_gen::GENERATOR_INDEX__SIGNATURE_PAYLOAD;

use dep::std::hash;

Expand Down
4 changes: 4 additions & 0 deletions yarn-project/aztec-nr/authwit/src/lib.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mod account;
mod auth_witness;
mod auth;
mod entrypoint;
18 changes: 0 additions & 18 deletions yarn-project/aztec-nr/aztec/src/auth.nr

This file was deleted.

6 changes: 0 additions & 6 deletions yarn-project/aztec-nr/aztec/src/hash.nr
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,6 @@ fn sha256_to_field<N>(bytes_to_hash: [u8; N]) -> Field {
hash_in_a_field
}

fn compute_message_hash<N>(args: [Field; N]) -> Field {
// @todo @lherskind We should probably use a separate generator for this,
// to avoid any potential collisions with payloads.
pedersen_with_separator(args, GENERATOR_INDEX__SIGNATURE_PAYLOAD)[0]
}

fn compute_secret_hash(secret: Field) -> Field {
// TODO(#1205) This is probably not the right index to use
pedersen_with_separator([secret], GENERATOR_INDEX__L1_TO_L2_MESSAGE_SECRET)[0]
Expand Down
3 changes: 0 additions & 3 deletions yarn-project/aztec-nr/aztec/src/lib.nr
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
mod abi;
mod account;
mod address;
mod auth;
mod constants_gen;
mod context;
mod entrypoint;
mod hash;
mod log;
mod messaging;
Expand Down
3 changes: 1 addition & 2 deletions yarn-project/aztec-nr/aztec/src/oracle.nr
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,4 @@ mod public_call;
mod notes;
mod storage;
mod logs;
mod compute_selector;
mod auth_witness;
mod compute_selector;
4 changes: 2 additions & 2 deletions yarn-project/aztec.js/src/abis/ecdsa_account_contract.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
"name": "payload",
"type": {
"kind": "struct",
"path": "aztec::entrypoint::EntrypointPayload",
"path": "authwit::entrypoint::EntrypointPayload",
"fields": [
{
"name": "function_calls",
Expand All @@ -105,7 +105,7 @@
"length": 4,
"type": {
"kind": "struct",
"path": "aztec::entrypoint::FunctionCall",
"path": "authwit::entrypoint::FunctionCall",
"fields": [
{
"name": "args_hash",
Expand Down
4 changes: 2 additions & 2 deletions yarn-project/aztec.js/src/abis/schnorr_account_contract.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
"name": "payload",
"type": {
"kind": "struct",
"path": "aztec::entrypoint::EntrypointPayload",
"path": "authwit::entrypoint::EntrypointPayload",
"fields": [
{
"name": "function_calls",
Expand All @@ -93,7 +93,7 @@
"length": 4,
"type": {
"kind": "struct",
"path": "aztec::entrypoint::FunctionCall",
"path": "authwit::entrypoint::FunctionCall",
"fields": [
{
"name": "args_hash",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"name": "payload",
"type": {
"kind": "struct",
"path": "aztec::entrypoint::EntrypointPayload",
"path": "authwit::entrypoint::EntrypointPayload",
"fields": [
{
"name": "function_calls",
Expand All @@ -28,7 +28,7 @@
"length": 4,
"type": {
"kind": "struct",
"path": "aztec::entrypoint::FunctionCall",
"path": "authwit::entrypoint::FunctionCall",
"fields": [
{
"name": "args_hash",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class DefaultAccountEntrypoint implements EntrypointInterface {
name: 'payload',
type: {
kind: 'struct',
path: 'aztec::entrypoint::EntrypointPayload',
path: 'authwit::entrypoint::EntrypointPayload',
fields: [
{
name: 'function_calls',
Expand All @@ -55,7 +55,7 @@ export class DefaultAccountEntrypoint implements EntrypointInterface {
length: 4,
type: {
kind: 'struct',
path: 'aztec::entrypoint::FunctionCall',
path: 'authwit::entrypoint::FunctionCall',
fields: [
{
name: 'args_hash',
Expand Down
24 changes: 24 additions & 0 deletions yarn-project/aztec.js/src/utils/authwit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { AztecAddress, CircuitsWasm, GeneratorIndex } from '@aztec/circuits.js';
import { pedersenPlookupCompressWithHashIndex } from '@aztec/circuits.js/barretenberg';
import { FunctionCall, PackedArguments } from '@aztec/types';

/**
* Compute an authentication witness message hash from a caller and a request
* H(caller: AztecAddress, target: AztecAddress, selector: Field, args_hash: Field)
* @param caller - The caller approved to make the call
* @param request - The request to be made (function call)
* @returns The message hash for the witness
*/
export const computeAuthWitMessageHash = async (caller: AztecAddress, request: FunctionCall) => {
const wasm = await CircuitsWasm.get();
return pedersenPlookupCompressWithHashIndex(
wasm,
[
caller.toField(),
request.to.toField(),
request.functionData.selector.toField(),
(await PackedArguments.fromArgs(request.args, wasm)).hash,
].map(fr => fr.toBuffer()),
GeneratorIndex.SIGNATURE_PAYLOAD,
);
};
1 change: 1 addition & 0 deletions yarn-project/aztec.js/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './l1_contracts.js';
export * from './l2_contracts.js';
export * from './abi_types.js';
export * from './cheat_codes.js';
export * from './authwit.js';
19 changes: 8 additions & 11 deletions yarn-project/canary/src/uniswap_trade_on_l1_from_l2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
NotePreimage,
TxHash,
TxStatus,
computeAuthWitMessageHash,
computeMessageSecretHash,
createDebugLogger,
createPXEClient,
Expand All @@ -14,7 +15,6 @@ import {
sleep,
waitForSandbox,
} from '@aztec/aztec.js';
import { FunctionSelector } from '@aztec/circuits.js';
import { UniswapPortalAbi, UniswapPortalBytecode } from '@aztec/l1-artifacts';
import { TokenBridgeContract, TokenContract, UniswapContract } from '@aztec/noir-contracts/types';

Expand All @@ -32,7 +32,7 @@ import {
import { mnemonicToAccount } from 'viem/accounts';
import { Chain, foundry } from 'viem/chains';

import { deployAndInitializeTokenAndBridgeContracts, deployL1Contract, hashPayload } from './utils.js';
import { deployAndInitializeTokenAndBridgeContracts, deployL1Contract } from './utils.js';

const logger = createDebugLogger('aztec:canary');

Expand Down Expand Up @@ -335,15 +335,12 @@ describe('uniswap_trade_on_l1_from_l2', () => {
// 4. Owner gives uniswap approval to unshield funds to self on its behalf
logger('Approving uniswap to unshield funds to self on my behalf');
const nonceForWETHUnshieldApproval = new Fr(2n);
const unshieldToUniswapMessageHash = await hashPayload([
uniswapL2Contract.address.toField(),
wethL2Contract.address.toField(),
FunctionSelector.fromSignature('unshield((Field),(Field),Field,Field)').toField(),
ownerAddress.toField(),
uniswapL2Contract.address.toField(),
new Fr(wethAmountToBridge),
nonceForWETHUnshieldApproval,
]);
const unshieldToUniswapMessageHash = await computeAuthWitMessageHash(
uniswapL2Contract.address,
wethL2Contract.methods
.unshield(ownerAddress, uniswapL2Contract.address, wethAmountToBridge, nonceForWETHUnshieldApproval)
.request(),
);
await ownerWallet.createAuthWitness(Fr.fromBuffer(unshieldToUniswapMessageHash));

// 5. Swap on L1 - sends L2 to L1 message to withdraw WETH to L1 and another message to swap assets.
Expand Down
15 changes: 0 additions & 15 deletions yarn-project/canary/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { AztecAddress, EthAddress, Fr, TxStatus, Wallet } from '@aztec/aztec.js';
import { CircuitsWasm, GeneratorIndex } from '@aztec/circuits.js';
import { pedersenPlookupCompressWithHashIndex } from '@aztec/circuits.js/barretenberg';
import { PortalERC20Abi, PortalERC20Bytecode, TokenPortalAbi, TokenPortalBytecode } from '@aztec/l1-artifacts';
import { TokenBridgeContract, TokenContract } from '@aztec/noir-contracts/types';

Expand Down Expand Up @@ -136,16 +134,3 @@ export async function deployL1Contract(

return EthAddress.fromString(receipt.contractAddress!);
}

/**
* Hash a payload to generate a signature on an account contract
* @param payload - payload to hash
* @returns the hashed message
*/
export const hashPayload = async (payload: Fr[]) => {
return pedersenPlookupCompressWithHashIndex(
await CircuitsWasm.get(),
payload.map(fr => fr.toBuffer()),
GeneratorIndex.SIGNATURE_PAYLOAD,
);
};
30 changes: 11 additions & 19 deletions yarn-project/end-to-end/src/e2e_cross_chain_messaging.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { AccountWallet, AztecAddress } from '@aztec/aztec.js';
import { Fr, FunctionSelector } from '@aztec/circuits.js';
import { AccountWallet, AztecAddress, computeAuthWitMessageHash } from '@aztec/aztec.js';
import { Fr } from '@aztec/circuits.js';
import { EthAddress } from '@aztec/foundation/eth-address';
import { DebugLogger } from '@aztec/foundation/log';
import { TokenBridgeContract, TokenContract } from '@aztec/noir-contracts/types';
import { TxStatus } from '@aztec/types';

import { CrossChainTestHarness } from './fixtures/cross_chain_test_harness.js';
import { delay, hashPayload, setup } from './fixtures/utils.js';
import { delay, setup } from './fixtures/utils.js';

describe('e2e_cross_chain_messaging', () => {
let logger: DebugLogger;
Expand Down Expand Up @@ -107,14 +107,10 @@ describe('e2e_cross_chain_messaging', () => {
// 4. Give approval to bridge to burn owner's funds:
const withdrawAmount = 9n;
const nonce = Fr.random();
const burnMessageHash = await hashPayload([
l2Bridge.address.toField(),
l2Token.address.toField(),
FunctionSelector.fromSignature('burn((Field),Field,Field)').toField(),
ownerAddress.toField(),
new Fr(withdrawAmount),
nonce,
]);
const burnMessageHash = await computeAuthWitMessageHash(
l2Bridge.address,
l2Token.methods.burn(ownerAddress, withdrawAmount, nonce).request(),
);
await user1Wallet.createAuthWitness(burnMessageHash);

// 5. Withdraw owner's funds from L2 to L1
Expand Down Expand Up @@ -201,14 +197,10 @@ describe('e2e_cross_chain_messaging', () => {

const withdrawAmount = 9n;
const nonce = Fr.random();
const expectedBurnMessageHash = await hashPayload([
l2Bridge.address.toField(),
l2Token.address.toField(),
FunctionSelector.fromSignature('burn((Field),Field,Field)').toField(),
user1Wallet.getAddress().toField(),
new Fr(withdrawAmount),
nonce,
]);
const expectedBurnMessageHash = await computeAuthWitMessageHash(
l2Bridge.address,
l2Token.methods.burn(user1Wallet.getAddress(), withdrawAmount, nonce).request(),
);
// Should fail as owner has not given approval to bridge burn their funds.
await expect(
l2Bridge
Expand Down
Loading

0 comments on commit fdbe2b2

Please sign in to comment.