Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

previously we had a TODO here reminding us to reconsider if this is the best pederson generator. Is that not the case anymore?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was a todo in the tracking issue, have created #2676 now, and will ref that.

}
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