From b7c2bc0e70faebb60e2051e0330e94937a1e3711 Mon Sep 17 00:00:00 2001 From: Lasse Herskind <16536249+LHerskind@users.noreply.github.com> Date: Wed, 28 Feb 2024 12:04:04 +0100 Subject: [PATCH] feat: AUTHWIT cancellations (#4799) Fixes #4786 and #3007 --- .circleci/config.yml | 15 ++++ .../resources/common_patterns/authwit.md | 17 ++-- .../resources/common_patterns/main.md | 2 +- .../tutorials/uniswap/l2_contract_setup.md | 5 +- noir-projects/aztec-nr/authwit/src/account.nr | 41 +++++++--- noir-projects/aztec-nr/authwit/src/auth.nr | 78 ++++++------------ .../docs_example_contract/src/main.nr | 10 +++ .../ecdsa_account_contract/src/main.nr | 25 +++--- .../schnorr_account_contract/src/main.nr | 25 +++--- .../src/main.nr | 28 ++++--- .../src/main.nr | 30 ++++--- .../contracts/uniswap_contract/src/main.nr | 17 +++- .../src/crates/types/src/constants.nr | 3 +- yarn-project/aztec.js/src/index.ts | 2 + yarn-project/aztec.js/src/utils/authwit.ts | 52 ++++++++++-- .../aztec.js/src/wallet/account_wallet.ts | 38 +++++++-- yarn-project/circuits.js/src/constants.gen.ts | 3 +- .../end-to-end/src/e2e_authwit.test.ts | 81 ++++++++++++++++++ .../end-to-end/src/e2e_token_contract.test.ts | 82 ++++++++++++++++++- .../end-to-end/src/shared/uniswap_l1_l2.ts | 4 +- .../src/sequencer/abstract_phase_manager.ts | 2 +- .../src/sequencer/public_processor.ts | 2 +- 22 files changed, 417 insertions(+), 145 deletions(-) create mode 100644 yarn-project/end-to-end/src/e2e_authwit.test.ts diff --git a/.circleci/config.yml b/.circleci/config.yml index dc87d950ce7..7f07b00d1e3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -675,6 +675,19 @@ jobs: command: cond_spot_run_compose end-to-end 4 ./scripts/docker-compose.yml TEST=e2e_token_contract.test.ts aztec_manifest_key: end-to-end + e2e-authwit-test: + docker: + - image: aztecprotocol/alpine-build-image + resource_class: small + steps: + - *checkout + - *setup_env + - run: + name: "Test" + command: cond_spot_run_compose end-to-end 4 ./scripts/docker-compose.yml TEST=e2e_authwit.test.ts + aztec_manifest_key: end-to-end + + e2e-blacklist-token-contract: docker: - image: aztecprotocol/alpine-build-image @@ -1432,6 +1445,7 @@ workflows: - e2e-deploy-contract: *e2e_test - e2e-lending-contract: *e2e_test - e2e-token-contract: *e2e_test + - e2e-authwit-test: *e2e_test - e2e-blacklist-token-contract: *e2e_test # TODO(3458): Investigate intermittent failure # - e2e-slow-tree: *e2e_test @@ -1479,6 +1493,7 @@ workflows: - e2e-deploy-contract - e2e-lending-contract - e2e-token-contract + - e2e-authwit-test - e2e-blacklist-token-contract - e2e-sandbox-example - e2e-state-vars diff --git a/docs/docs/developers/contracts/resources/common_patterns/authwit.md b/docs/docs/developers/contracts/resources/common_patterns/authwit.md index f2dc1bc723c..bb3c5eb367a 100644 --- a/docs/docs/developers/contracts/resources/common_patterns/authwit.md +++ b/docs/docs/developers/contracts/resources/common_patterns/authwit.md @@ -70,13 +70,13 @@ As part of `AuthWit` we are assuming that the `on_behalf_of` implements the priv ```rust #[aztec(private)] -fn is_valid(message_hash: Field) -> Field; +fn spend_private_authwit(inner_hash: Field) -> Field; #[aztec(public)] -fn is_valid_public(message_hash: Field) -> Field; +fn spend_public_authwit(inner_hash: Field) -> Field; ``` -Both return the value `0xe86ab4ff` (`is_valid` selector) for a successful authentication, and `0x00000000` for a failed authentication. You might be wondering why we are expecting the return value to be a selector instead of a boolean. This is mainly to account for a case of selector collisions where the same selector is used for different functions, and we don't want an account to mistakenly allow a different function to be called on its behalf - it is hard to return the selector by mistake, but you might have other functions returning a bool. +Both return the value `0xabf64ad4` (`IS_VALID` selector) for a successful authentication, and `0x00000000` for a failed authentication. You might be wondering why we are expecting the return value to be a selector instead of a boolean. This is mainly to account for a case of selector collisions where the same selector is used for different functions, and we don't want an account to mistakenly allow a different function to be called on its behalf - it is hard to return the selector by mistake, but you might have other functions returning a bool. ## The `AuthWit` library. @@ -102,11 +102,10 @@ As you can see above, this function takes a `caller` and a `request`. The `reque 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`. -This function computes the message hash, and then forwards the call to the more generic `assert_valid_authwit`. This validating function will then: - -- make a call to `on_behalf_of` to validate that the call is authenticated -- emit a nullifier for the action to prevent replay attacks -- throw if the action is not authenticated by `on_behalf_of` +This function will then make a to `on_behalf_of` to execute the `spend_private_authwit` function which validates that the call is authenticated. +The `on_behalf_of` should assert that we are indeed authenticated and then emit a nullifier when we are spending the authwit to prevent replay attacks. +If the return value is not as expected, we throw an error. +This is to cover the case where the `on_behalf_of` might implemented some function with the same selector as the `spend_private_authwit` that could be used to authenticate unintentionally. #### Example @@ -176,7 +175,7 @@ In the snippet below, this is done as a separate contract call, but can also be We have cases where we need a non-wallet contract to approve an action to be executed by another contract. One of the cases could be when making more complex defi where funds are passed along. When doing so, we need the intermediate contracts to support approving of actions on their behalf. -To support this, we must implement the `is_valid_public` function as seen in the snippet below. +To support this, we must implement the `spend_public_authwit` function as seen in the snippet below. #include_code authwit_uniswap_get /noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr rust diff --git a/docs/docs/developers/contracts/resources/common_patterns/main.md b/docs/docs/developers/contracts/resources/common_patterns/main.md index c6c31296a0e..e00f9770397 100644 --- a/docs/docs/developers/contracts/resources/common_patterns/main.md +++ b/docs/docs/developers/contracts/resources/common_patterns/main.md @@ -38,7 +38,7 @@ E.g. you don't want a user to subscribe once they have subscribed already. Or yo Emit a nullifier in your function. By adding this nullifier into the tree, you prevent another nullifier from being added again. This is also why in authwit, we emit a nullifier, to prevent someone from reusing their approval. -#include_code assert_valid_authwit_public /noir-projects/aztec-nr/authwit/src/auth.nr rust +#include_code spend_private_authwit /noir-projects/aztec-nr/authwit/src/account.nr rust Note be careful to ensure that the nullifier is not deterministic and that no one could do a preimage analysis attack. More in [the anti pattern section on deterministic nullifiers](#deterministic-nullifiers) diff --git a/docs/docs/developers/tutorials/uniswap/l2_contract_setup.md b/docs/docs/developers/tutorials/uniswap/l2_contract_setup.md index a7b54b17975..71284fafe79 100644 --- a/docs/docs/developers/tutorials/uniswap/l2_contract_setup.md +++ b/docs/docs/developers/tutorials/uniswap/l2_contract_setup.md @@ -22,7 +22,10 @@ Next, paste this function: #include_code authwit_uniswap_get noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr rust -In this function, the token contract calls the Uniswap contract to check if Uniswap has indeed done the approval. The token contract expects a `is_valid()` function to exit for private approvals and `is_valid_public()` for public approvals. If the action is indeed approved, it expects that the contract would return the function selector for `is_valid()`  in both cases. The Aztec.nr library exposes this constant for ease of use. The token contract also emits a nullifier for this message so that this approval (with the nonce) can’t be used again. +In this function, the token contract calls the Uniswap contract to check if Uniswap has indeed done the approval. +The token contract expects a `spend_private_authwit()` function to exit for private approvals and `spend_public_authwit()` for public approvals. +If the action is indeed approved, it expects that the contract will emit a nullifier and return the function selector for `IS_VALID()`  in both cases. +The Aztec.nr library exposes this constant for ease of use. This is similar to the [Authwit flow](../../contracts/resources/common_patterns/authwit.md). diff --git a/noir-projects/aztec-nr/authwit/src/account.nr b/noir-projects/aztec-nr/authwit/src/account.nr index 20816c6ae61..2a25420feaf 100644 --- a/noir-projects/aztec-nr/authwit/src/account.nr +++ b/noir-projects/aztec-nr/authwit/src/account.nr @@ -1,8 +1,9 @@ use dep::aztec::context::{PrivateContext, PublicContext, Context}; use dep::aztec::state_vars::{Map, PublicMutable}; +use dep::aztec::protocol_types::{address::AztecAddress, abis::function_selector::FunctionSelector, hash::{pedersen_hash}}; use crate::entrypoint::{app::AppPayload, fee::FeePayload}; -use crate::auth::IS_VALID_SELECTOR; +use crate::auth::{IS_VALID_SELECTOR, compute_outer_authwit_hash}; struct AccountActions { context: Context, @@ -69,21 +70,37 @@ impl AccountActions { } // docs:end:entrypoint - pub fn is_valid(self, message_hash: Field) -> Field { + // docs:start:spend_private_authwit + pub fn spend_private_authwit(self, inner_hash: Field) -> Field { + let context = self.context.private.unwrap(); + // The `inner_hash` is "siloed" with the `msg_sender` to ensure that only it can + // consume the message. + // This ensures that contracts cannot consume messages that are not intended for them. + let message_hash = compute_outer_authwit_hash(context.msg_sender(), inner_hash); let valid_fn = self.is_valid_impl; - if (valid_fn(self.context.private.unwrap(), message_hash)) { - IS_VALID_SELECTOR - } else { - 0 - } + assert(valid_fn(context, message_hash) == true, "Message not authorized by account"); + context.push_new_nullifier(message_hash, 0); + IS_VALID_SELECTOR } + // docs:end:spend_private_authwit - pub fn is_valid_public(self, message_hash: Field) -> Field { - let value = self.approved_action.at(message_hash).read(); - if (value) { IS_VALID_SELECTOR } else { 0 } + // docs:start:spend_public_authwit + pub fn spend_public_authwit(self, inner_hash: Field) -> Field { + let context = self.context.public.unwrap(); + // The `inner_hash` is "siloed" with the `msg_sender` to ensure that only it can + // consume the message. + // This ensures that contracts cannot consume messages that are not intended for them. + let message_hash = compute_outer_authwit_hash(context.msg_sender(), inner_hash); + let is_valid = self.approved_action.at(message_hash).read(); + assert(is_valid == true, "Message not authorized by account"); + context.push_new_nullifier(message_hash, 0); + IS_VALID_SELECTOR } + // docs:end:spend_public_authwit - pub fn internal_set_is_valid_storage(self, message_hash: Field, value: bool) { - self.approved_action.at(message_hash).write(value); + // docs:start:approve_public_authwit + pub fn approve_public_authwit(self, message_hash: Field) { + self.approved_action.at(message_hash).write(true); } + // docs:end:approve_public_authwit } diff --git a/noir-projects/aztec-nr/authwit/src/auth.nr b/noir-projects/aztec-nr/authwit/src/auth.nr index 34af8aa8bf9..e1904352cf2 100644 --- a/noir-projects/aztec-nr/authwit/src/auth.nr +++ b/noir-projects/aztec-nr/authwit/src/auth.nr @@ -1,78 +1,48 @@ use dep::aztec::protocol_types::{ abis::function_selector::FunctionSelector, address::AztecAddress, - constants::{GENERATOR_INDEX__AUTHWIT}, hash::{hash_args, pedersen_hash} + constants::{GENERATOR_INDEX__AUTHWIT_INNER, GENERATOR_INDEX__AUTHWIT_OUTER}, + hash::{hash_args, pedersen_hash} }; use dep::aztec::context::{PrivateContext, PublicContext, Context}; -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. - -// docs:start:assert_valid_authwit -// Assert that `on_behalf_of` have authorized `message_hash` with a valid authentication witness -pub fn assert_valid_authwit( - context: &mut PrivateContext, - on_behalf_of: AztecAddress, - message_hash: Field -) { - let is_valid_selector = FunctionSelector::from_field(IS_VALID_SELECTOR); - let result = context.call_private_function(on_behalf_of, is_valid_selector, [message_hash])[0]; - context.push_new_nullifier(message_hash, 0); - assert(result == IS_VALID_SELECTOR, "Message not authorized by account"); -} -// docs:end:assert_valid_authwit +global IS_VALID_SELECTOR = 0xabf64ad4; // 4 first bytes of keccak256("IS_VALID()") // docs:start:assert_current_call_valid_authwit // Assert that `on_behalf_of` have authorized the current call with a valid authentication witness pub fn assert_current_call_valid_authwit(context: &mut PrivateContext, on_behalf_of: AztecAddress) { - // message_hash = H(caller, contract_this, selector, args_hash) - let message_hash = pedersen_hash( - [ - context.msg_sender().to_field(), context.this_address().to_field(), context.selector().to_field(), context.args_hash - ], - GENERATOR_INDEX__AUTHWIT - ); - assert_valid_authwit(context, on_behalf_of, message_hash); -} -// docs:end:assert_current_call_valid_authwit - -// docs:start:assert_valid_authwit_public -// Assert that `on_behalf_of` have authorized `message_hash` in a public context -pub fn assert_valid_authwit_public(context: &mut PublicContext, on_behalf_of: AztecAddress, message_hash: Field) { - let is_valid_public_selector = FunctionSelector::from_field(IS_VALID_PUBLIC_SELECTOR); - let result = context.call_public_function(on_behalf_of, is_valid_public_selector, [message_hash])[0]; - context.push_new_nullifier(message_hash, 0); + let function_selector = FunctionSelector::from_signature("spend_private_authwit(Field)"); + let inner_hash = compute_inner_authwit_hash([context.msg_sender().to_field(), context.selector().to_field(), context.args_hash]); + let result = context.call_private_function(on_behalf_of, function_selector, [inner_hash])[0]; assert(result == IS_VALID_SELECTOR, "Message not authorized by account"); } -// docs:end:assert_valid_authwit_public +// docs:end:assert_current_call_valid_authwit // docs:start:assert_current_call_valid_authwit_public // Assert that `on_behalf_of` have authorized the current call in a public context pub fn assert_current_call_valid_authwit_public(context: &mut PublicContext, on_behalf_of: AztecAddress) { - // message_hash = H(caller, contract_this, selector, args_hash) - let message_hash = pedersen_hash( - [ - context.msg_sender().to_field(), context.this_address().to_field(), context.selector().to_field(), context.args_hash - ], - GENERATOR_INDEX__AUTHWIT - ); - assert_valid_authwit_public(context, on_behalf_of, message_hash); + let function_selector = FunctionSelector::from_signature("spend_public_authwit(Field)"); + let inner_hash = compute_inner_authwit_hash([context.msg_sender().to_field(), context.selector().to_field(), context.args_hash]); + let result = context.call_public_function(on_behalf_of, function_selector, [inner_hash])[0]; + assert(result == IS_VALID_SELECTOR, "Message not authorized by account"); } // docs:end:assert_current_call_valid_authwit_public // docs:start:compute_authwit_message_hash // Compute the message hash to be used by an authentication witness -pub fn compute_authwit_message_hash( - caller: AztecAddress, - target: AztecAddress, - selector: FunctionSelector, - args: [Field; N] -) -> Field { +pub fn compute_call_authwit_hash(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 + +pub fn compute_inner_authwit_hash(args: [Field; N]) -> Field { + pedersen_hash(args, GENERATOR_INDEX__AUTHWIT_INNER) +} + +pub fn compute_outer_authwit_hash(consumer: AztecAddress, inner_hash: Field) -> Field { pedersen_hash( - [caller.to_field(), target.to_field(), selector.to_field(), args_hash], - GENERATOR_INDEX__AUTHWIT + [consumer.to_field(), inner_hash], + GENERATOR_INDEX__AUTHWIT_OUTER ) } -// docs:end:compute_authwit_message_hash diff --git a/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr b/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr index d78408256a1..e528a39f047 100644 --- a/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr @@ -184,6 +184,16 @@ contract DocsExample { ); } + #[aztec(private)] + fn spend_private_authwit(inner_hash: Field) -> Field { + 1 + } + + #[aztec(public)] + fn spend_public_authwit(inner_hash: Field) -> Field { + 1 + } + #[aztec(public)] internal fn update_leader(account: AztecAddress, points: u8) { let new_leader = Leader { account, points }; diff --git a/noir-projects/noir-contracts/contracts/ecdsa_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/ecdsa_account_contract/src/main.nr index f9dac6425b8..e741663b5df 100644 --- a/noir-projects/noir-contracts/contracts/ecdsa_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/ecdsa_account_contract/src/main.nr @@ -3,7 +3,7 @@ mod ecdsa_public_key_note; // Account contract that uses ECDSA signatures for authentication on the same curve as Ethereum. // The signing key is stored in an immutable private note and should be different from the signing key. contract EcdsaAccount { - use dep::aztec::protocol_types::{abis::call_context::CallContext, address::AztecAddress}; + use dep::aztec::protocol_types::{abis::{call_context::CallContext, function_selector::FunctionSelector}, address::AztecAddress}; use dep::std; use dep::std::option::Option; use dep::aztec::{ @@ -40,31 +40,36 @@ contract EcdsaAccount { } #[aztec(private)] - fn is_valid(message_hash: Field) -> Field { + fn spend_private_authwit(inner_hash: Field) -> Field { let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); - actions.is_valid(message_hash) + actions.spend_private_authwit(inner_hash) } #[aztec(public)] - fn is_valid_public(message_hash: Field) -> Field { + fn spend_public_authwit(inner_hash: Field) -> Field { let actions = AccountActions::public(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); - actions.is_valid_public(message_hash) + actions.spend_public_authwit(inner_hash) + } + + #[aztec(private)] + internal fn cancel_authwit(outer_hash: Field) { + context.push_new_nullifier(outer_hash, 0); } #[aztec(public)] - internal fn set_is_valid_storage(message_hash: Field, value: bool) { + internal fn approve_public_authwit(outer_hash: Field) { let actions = AccountActions::public(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); - actions.internal_set_is_valid_storage(message_hash, value) + actions.approve_public_authwit(outer_hash) } #[contract_library_method] - fn is_valid_impl(context: &mut PrivateContext, message_field: Field) -> pub bool { + fn is_valid_impl(context: &mut PrivateContext, outer_hash: Field) -> bool { // Load public key from storage let storage = Storage::init(Context::private(context)); let public_key = storage.public_key.get_note(); // Load auth witness - let witness: [Field; 64] = get_auth_witness(message_field); + let witness: [Field; 64] = get_auth_witness(outer_hash); let mut signature: [u8; 64] = [0; 64]; for i in 0..64 { signature[i] = witness[i] as u8; @@ -72,7 +77,7 @@ contract EcdsaAccount { // Verify payload signature using Ethereum's signing scheme // Note that noir expects the hash of the message/challenge as input to the ECDSA verification. - let hashed_message: [u8; 32] = std::hash::sha256(message_field.to_be_bytes(32)); + let hashed_message: [u8; 32] = std::hash::sha256(outer_hash.to_be_bytes(32)); let verification = std::ecdsa_secp256k1::verify_signature(public_key.x, public_key.y, signature, hashed_message); assert(verification == true); diff --git a/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/main.nr index 6e4eec425c9..e7e4ebdafa0 100644 --- a/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/main.nr @@ -6,7 +6,7 @@ contract SchnorrAccount { use dep::std; use dep::std::option::Option; - use dep::aztec::protocol_types::address::AztecAddress; + use dep::aztec::protocol_types::{address::AztecAddress, abis::function_selector::FunctionSelector}; use dep::aztec::{ context::{PrivateContext, Context}, note::{note_header::NoteHeader, utils as note_utils}, @@ -45,25 +45,30 @@ contract SchnorrAccount { } #[aztec(private)] - fn is_valid(message_hash: Field) -> Field { + fn spend_private_authwit(inner_hash: Field) -> Field { let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); - actions.is_valid(message_hash) + actions.spend_private_authwit(inner_hash) } #[aztec(public)] - fn is_valid_public(message_hash: Field) -> Field { + fn spend_public_authwit(inner_hash: Field) -> Field { let actions = AccountActions::public(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); - actions.is_valid_public(message_hash) + actions.spend_public_authwit(inner_hash) + } + + #[aztec(private)] + internal fn cancel_authwit(outer_hash: Field) { + context.push_new_nullifier(outer_hash, 0); } #[aztec(public)] - internal fn set_is_valid_storage(message_hash: Field, value: bool) { + internal fn approve_public_authwit(outer_hash: Field) { let actions = AccountActions::public(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); - actions.internal_set_is_valid_storage(message_hash, value) + actions.approve_public_authwit(outer_hash) } #[contract_library_method] - fn is_valid_impl(context: &mut PrivateContext, message_hash: Field) -> pub bool { + fn is_valid_impl(context: &mut PrivateContext, outer_hash: Field) -> bool { // docs:start:entrypoint // Load public key from storage let storage = Storage::init(Context::private(context)); @@ -71,7 +76,7 @@ contract SchnorrAccount { let public_key = storage.signing_public_key.get_note(); // docs:end:get_note // Load auth witness - let witness: [Field; 64] = get_auth_witness(message_hash); + let witness: [Field; 64] = get_auth_witness(outer_hash); let mut signature: [u8; 64] = [0; 64]; for i in 0..64 { signature[i] = witness[i] as u8; @@ -82,7 +87,7 @@ contract SchnorrAccount { public_key.x, public_key.y, signature, - message_hash.to_be_bytes(32) + outer_hash.to_be_bytes(32) ); assert(verification == true); // docs:end:entrypoint diff --git a/noir-projects/noir-contracts/contracts/schnorr_hardcoded_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/schnorr_hardcoded_account_contract/src/main.nr index 88f6b7cf5e7..dc1d8912af6 100644 --- a/noir-projects/noir-contracts/contracts/schnorr_hardcoded_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/schnorr_hardcoded_account_contract/src/main.nr @@ -2,7 +2,10 @@ // Account contract that uses Schnorr signatures for authentication using a hardcoded public key. contract SchnorrHardcodedAccount { use dep::std; - use dep::aztec::{context::PrivateContext, protocol_types::address::AztecAddress}; + use dep::aztec::{ + context::PrivateContext, + protocol_types::{address::AztecAddress, abis::function_selector::FunctionSelector} + }; use dep::authwit::{ entrypoint::{app::AppPayload, fee::FeePayload}, account::AccountActions, @@ -25,28 +28,33 @@ contract SchnorrHardcodedAccount { } #[aztec(private)] - fn is_valid(message_hash: Field) -> Field { + fn spend_private_authwit(inner_hash: Field) -> Field { let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); - actions.is_valid(message_hash) + actions.spend_private_authwit(inner_hash) } #[aztec(public)] - fn is_valid_public(message_hash: Field) -> Field { + fn spend_public_authwit(inner_hash: Field) -> Field { let actions = AccountActions::public(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); - actions.is_valid_public(message_hash) + actions.spend_public_authwit(inner_hash) + } + + #[aztec(private)] + internal fn cancel_authwit(outer_hash: Field) { + context.push_new_nullifier(outer_hash, 0); } #[aztec(public)] - internal fn set_is_valid_storage(message_hash: Field, value: bool) { + internal fn approve_public_authwit(outer_hash: Field) { let actions = AccountActions::public(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); - actions.internal_set_is_valid_storage(message_hash, value) + actions.approve_public_authwit(outer_hash) } // docs:start:is-valid #[contract_library_method] - fn is_valid_impl(_context: &mut PrivateContext, message_hash: Field) -> pub bool { + fn is_valid_impl(_context: &mut PrivateContext, outer_hash: Field) -> bool { // Load auth witness and format as an u8 array - let witness: [Field; 64] = get_auth_witness(message_hash); + let witness: [Field; 64] = get_auth_witness(outer_hash); let mut signature: [u8; 64] = [0; 64]; for i in 0..64 { signature[i] = witness[i] as u8; @@ -57,7 +65,7 @@ contract SchnorrHardcodedAccount { public_key_x, public_key_y, signature, - message_hash.to_be_bytes(32) + outer_hash.to_be_bytes(32) ); assert(verification == true); true diff --git a/noir-projects/noir-contracts/contracts/schnorr_single_key_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/schnorr_single_key_account_contract/src/main.nr index b5633b70009..cdaf950180f 100644 --- a/noir-projects/noir-contracts/contracts/schnorr_single_key_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/schnorr_single_key_account_contract/src/main.nr @@ -2,12 +2,15 @@ mod util; mod auth_oracle; contract SchnorrSingleKeyAccount { - use dep::std::{option::Option}; - use dep::aztec::{context::{PrivateContext, PublicContext, Context}, protocol_types::address::AztecAddress}; + use dep::aztec::{ + context::{PrivateContext}, + protocol_types::{address::AztecAddress, abis::function_selector::FunctionSelector} + }; use dep::authwit::{entrypoint::{app::AppPayload, fee::FeePayload}, account::AccountActions}; use crate::{util::recover_address, auth_oracle::get_auth_witness}; + use dep::aztec::oracle::debug_log::debug_log_format; global ACCOUNT_ACTIONS_STORAGE_SLOT = 1; @@ -22,27 +25,32 @@ contract SchnorrSingleKeyAccount { } #[aztec(private)] - fn is_valid(message_hash: Field) -> Field { + fn spend_private_authwit(inner_hash: Field) -> Field { let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); - actions.is_valid(message_hash) + actions.spend_private_authwit(inner_hash) } #[aztec(public)] - fn is_valid_public(message_hash: Field) -> Field { + fn spend_public_authwit(inner_hash: Field) -> Field { let actions = AccountActions::public(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); - actions.is_valid_public(message_hash) + actions.spend_public_authwit(inner_hash) + } + + #[aztec(private)] + internal fn cancel_authwit(outer_hash: Field) { + context.push_new_nullifier(outer_hash, 0); } #[aztec(public)] - internal fn set_is_valid_storage(message_hash: Field, value: bool) { + internal fn approve_public_authwit(outer_hash: Field) { let actions = AccountActions::public(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl); - actions.internal_set_is_valid_storage(message_hash, value) + actions.approve_public_authwit(outer_hash) } #[contract_library_method] - fn is_valid_impl(context: &mut PrivateContext, message_hash: Field) -> pub bool { - let witness = get_auth_witness(message_hash); - assert(recover_address(message_hash, witness).eq(context.this_address())); + fn is_valid_impl(context: &mut PrivateContext, outer_hash: Field) -> bool { + let witness = get_auth_witness(outer_hash); + assert(recover_address(outer_hash, witness).eq(context.this_address())); true } } diff --git a/noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr b/noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr index f92dddff2f2..1188a027c8e 100644 --- a/noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr @@ -9,7 +9,10 @@ contract Uniswap { use dep::aztec::protocol_types::{abis::function_selector::FunctionSelector, address::{AztecAddress, EthAddress}}; use dep::aztec::{oracle::{context::get_portal_address}, state_vars::{Map, PublicMutable}}; - use dep::authwit::auth::{IS_VALID_SELECTOR, assert_current_call_valid_authwit_public, compute_authwit_message_hash}; + use dep::authwit::auth::{ + IS_VALID_SELECTOR, assert_current_call_valid_authwit_public, compute_call_authwit_hash, + compute_outer_authwit_hash + }; use crate::interfaces::{Token, TokenBridge}; use crate::util::{compute_swap_private_content_hash, compute_swap_public_content_hash}; @@ -173,9 +176,15 @@ contract Uniswap { // implementation is similar to how account contracts validate public approvals. // if valid, it returns the IS_VALID selector which is expected by token contract #[aztec(public)] - fn is_valid_public(message_hash: Field) -> Field { + fn spend_public_authwit(inner_hash: Field) -> Field { + let message_hash = compute_outer_authwit_hash(context.msg_sender(), inner_hash); let value = storage.approved_action.at(message_hash).read(); - if (value) { IS_VALID_SELECTOR } else { 0 } + if (value) { + context.push_new_nullifier(message_hash, 0); + IS_VALID_SELECTOR + } else { + 0 + } } // docs:end:authwit_uniswap_get @@ -190,7 +199,7 @@ contract Uniswap { // approve bridge to burn this contract's funds (required when exiting on L1, as it burns funds on L2): let nonce_for_burn_approval = storage.nonce_for_burn_approval.read(); let selector = FunctionSelector::from_signature("burn_public((Field),Field,Field)"); - let message_hash = compute_authwit_message_hash( + let message_hash = compute_call_authwit_hash( token_bridge, token, selector, diff --git a/noir-projects/noir-protocol-circuits/src/crates/types/src/constants.nr b/noir-projects/noir-protocol-circuits/src/crates/types/src/constants.nr index 49fcbd999bf..9a529b82afd 100644 --- a/noir-projects/noir-protocol-circuits/src/crates/types/src/constants.nr +++ b/noir-projects/noir-protocol-circuits/src/crates/types/src/constants.nr @@ -232,4 +232,5 @@ global GENERATOR_INDEX__VK = 41; global GENERATOR_INDEX__PRIVATE_CIRCUIT_PUBLIC_INPUTS = 42; global GENERATOR_INDEX__PUBLIC_CIRCUIT_PUBLIC_INPUTS = 43; global GENERATOR_INDEX__FUNCTION_ARGS = 44; -global GENERATOR_INDEX__AUTHWIT = 45; +global GENERATOR_INDEX__AUTHWIT_INNER = 45; +global GENERATOR_INDEX__AUTHWIT_OUTER = 46; diff --git a/yarn-project/aztec.js/src/index.ts b/yarn-project/aztec.js/src/index.ts index c4b950f624d..225de5d90df 100644 --- a/yarn-project/aztec.js/src/index.ts +++ b/yarn-project/aztec.js/src/index.ts @@ -44,6 +44,8 @@ export { isContractDeployed, EthCheatCodes, computeAuthWitMessageHash, + computeInnerAuthWitHash, + computeOuterAuthWitHash, waitForPXE, waitForAccountSynch, } from './utils/index.js'; diff --git a/yarn-project/aztec.js/src/utils/authwit.ts b/yarn-project/aztec.js/src/utils/authwit.ts index 79898897e9a..865f278dfa5 100644 --- a/yarn-project/aztec.js/src/utils/authwit.ts +++ b/yarn-project/aztec.js/src/utils/authwit.ts @@ -1,24 +1,62 @@ import { FunctionCall, PackedArguments } from '@aztec/circuit-types'; -import { AztecAddress, GeneratorIndex } from '@aztec/circuits.js'; +import { AztecAddress, Fr, GeneratorIndex } from '@aztec/circuits.js'; import { pedersenHash } from '@aztec/foundation/crypto'; // docs:start:authwit_computeAuthWitMessageHash /** * Compute an authentication witness message hash from a caller and a request - * H(caller: AztecAddress, target: AztecAddress, selector: Field, args_hash: Field) + * H(target: AztecAddress, H(caller: AztecAddress, selector: Field, args_hash: Field)) + * Example usage would be `bob` authenticating `alice` to perform a transfer of `10` + * tokens from his account to herself: + * H(token, H(alice, transfer_selector, H(bob, alice, 10, nonce))) + * `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) * @returns The message hash for the witness */ export const computeAuthWitMessageHash = (caller: AztecAddress, request: FunctionCall) => { - return pedersenHash( - [ + return computeOuterAuthWitHash( + request.to.toField(), + computeInnerAuthWitHash([ caller.toField(), - request.to.toField(), request.functionData.selector.toField(), PackedArguments.fromArgs(request.args).hash, - ].map(fr => fr.toBuffer()), - GeneratorIndex.AUTHWIT, + ]), ); }; // docs:end:authwit_computeAuthWitMessageHash + +/** + * Compute the inner hash for an authentication witness. + * This is the "intent" of the message, before siloed with the consumer. + * It is used as part of the `computeAuthWitMessageHash` but can also be used + * in case the message is not a "call" to a function, but arbitrary data. + * @param args - The arguments to hash + * @returns The inner hash for the witness + */ +export const computeInnerAuthWitHash = (args: Fr[]) => { + return pedersenHash( + args.map(fr => fr.toBuffer()), + GeneratorIndex.AUTHWIT_INNER, + ); +}; + +/** + * Compute the outer hash for an authentication witness. + * This is the value siloed with its "consumer" and what the `on_behalf_of` + * should be signing. + * The consumer is who will be consuming the message, for token approvals it + * is the token contract itself (because the token makes the call to check the approval). + * It is used as part of the `computeAuthWitMessageHash` but can also be used + * in case the message is not a "call" to a function, but arbitrary data. + * @param consumer - The address that can "consume" the authwit + * @param innerHash - The inner hash for the witness + * @returns The outer hash for the witness + */ +export const computeOuterAuthWitHash = (consumer: AztecAddress, innerHash: Fr) => { + return pedersenHash( + [consumer.toField(), innerHash].map(fr => fr.toBuffer()), + GeneratorIndex.AUTHWIT_OUTER, + ); +}; diff --git a/yarn-project/aztec.js/src/wallet/account_wallet.ts b/yarn-project/aztec.js/src/wallet/account_wallet.ts index 428d4b46fd0..b8db6836ff4 100644 --- a/yarn-project/aztec.js/src/wallet/account_wallet.ts +++ b/yarn-project/aztec.js/src/wallet/account_wallet.ts @@ -33,8 +33,21 @@ export class AccountWallet extends BaseWallet { * @returns - A function interaction. */ public setPublicAuth(message: Fr | Buffer, authorized: boolean): ContractFunctionInteraction { - const args = [message, authorized]; - return new ContractFunctionInteraction(this, this.getAddress(), this.getSetIsValidStorageAbi(), args); + if (authorized) { + return new ContractFunctionInteraction(this, this.getAddress(), this.getApprovePublicAuthwitAbi(), [message]); + } else { + return this.cancelAuthWit(message); + } + } + + /** + * Returns a function interaction to cancel a message hash as authorized in this account. + * @param message - Message hash to authorize. + * @returns - A function interaction. + */ + public cancelAuthWit(message: Fr | Buffer): ContractFunctionInteraction { + const args = [message]; + return new ContractFunctionInteraction(this, this.getAddress(), this.getCancelAuthwitAbi(), args); } /** Returns the complete address of the account that implements this wallet. */ @@ -47,10 +60,10 @@ export class AccountWallet extends BaseWallet { return this.getCompleteAddress().address; } - private getSetIsValidStorageAbi(): FunctionAbi { + private getApprovePublicAuthwitAbi(): FunctionAbi { return { - name: 'set_is_valid_storage', - functionType: 'open' as FunctionType, + name: 'approve_public_authwit', + functionType: FunctionType.OPEN, isInternal: true, parameters: [ { @@ -58,9 +71,20 @@ export class AccountWallet extends BaseWallet { type: { kind: 'field' }, visibility: 'private' as ABIParameterVisibility, }, + ], + returnTypes: [], + }; + } + + private getCancelAuthwitAbi(): FunctionAbi { + return { + name: 'cancel_authwit', + functionType: FunctionType.SECRET, + isInternal: true, + parameters: [ { - name: 'value', - type: { kind: 'boolean' }, + name: 'message_hash', + type: { kind: 'field' }, visibility: 'private' as ABIParameterVisibility, }, ], diff --git a/yarn-project/circuits.js/src/constants.gen.ts b/yarn-project/circuits.js/src/constants.gen.ts index d4c77e327b6..0f3fa386ed8 100644 --- a/yarn-project/circuits.js/src/constants.gen.ts +++ b/yarn-project/circuits.js/src/constants.gen.ts @@ -145,5 +145,6 @@ export enum GeneratorIndex { PRIVATE_CIRCUIT_PUBLIC_INPUTS = 42, PUBLIC_CIRCUIT_PUBLIC_INPUTS = 43, FUNCTION_ARGS = 44, - AUTHWIT = 45, + AUTHWIT_INNER = 45, + AUTHWIT_OUTER = 46, } diff --git a/yarn-project/end-to-end/src/e2e_authwit.test.ts b/yarn-project/end-to-end/src/e2e_authwit.test.ts new file mode 100644 index 00000000000..4eef8e659b2 --- /dev/null +++ b/yarn-project/end-to-end/src/e2e_authwit.test.ts @@ -0,0 +1,81 @@ +import { AccountWallet, CompleteAddress, Fr, computeInnerAuthWitHash, computeOuterAuthWitHash } from '@aztec/aztec.js'; +import { SchnorrAccountContract } from '@aztec/noir-contracts.js'; + +import { jest } from '@jest/globals'; + +import { publicDeployAccounts, setup } from './fixtures/utils.js'; + +const TIMEOUT = 90_000; + +describe('e2e_authwit_tests', () => { + jest.setTimeout(TIMEOUT); + + let wallets: AccountWallet[]; + let accounts: CompleteAddress[]; + + beforeAll(async () => { + ({ wallets, accounts } = await setup(2)); + await publicDeployAccounts(wallets[0], accounts.slice(0, 2)); + }, 100_000); + + describe('Private', () => { + describe('arbitrary data', () => { + it('happy path', async () => { + const innerHash = computeInnerAuthWitHash([Fr.fromString('0xdead')]); + const outerHash = computeOuterAuthWitHash(wallets[1].getAddress(), innerHash); + + const witness = await wallets[0].createAuthWitness(outerHash); + await wallets[1].addAuthWitness(witness); + + const c = await SchnorrAccountContract.at(wallets[0].getAddress(), wallets[0]); + await c.withWallet(wallets[1]).methods.spend_private_authwit(innerHash).send().wait(); + }); + + describe('failure case', () => { + it('cancel before usage', async () => { + const innerHash = computeInnerAuthWitHash([Fr.fromString('0xdead'), Fr.fromString('0xbeef')]); + const outerHash = computeOuterAuthWitHash(wallets[1].getAddress(), innerHash); + + const witness = await wallets[0].createAuthWitness(outerHash); + await wallets[1].addAuthWitness(witness); + await wallets[0].cancelAuthWit(outerHash).send().wait(); + + const c = await SchnorrAccountContract.at(wallets[0].getAddress(), wallets[0]); + const txCancelledAuthwit = c.withWallet(wallets[1]).methods.spend_private_authwit(innerHash).send(); + // The transaction should be dropped because of a cancelled authwit (duplicate nullifier) + await expect(txCancelledAuthwit.wait()).rejects.toThrowError('Transaction '); + }); + }); + }); + }); + + describe('Public', () => { + describe('arbitrary data', () => { + it('happy path', async () => { + const innerHash = computeInnerAuthWitHash([Fr.fromString('0xdead'), Fr.fromString('0x01')]); + const outerHash = computeOuterAuthWitHash(wallets[1].getAddress(), innerHash); + + await wallets[0].setPublicAuth(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(); + }); + + describe('failure case', () => { + it('cancel before usage', async () => { + 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].cancelAuthWit(outerHash).send().wait(); + + const c = await SchnorrAccountContract.at(wallets[0].getAddress(), wallets[0]); + const txCancelledAuthwit = c.withWallet(wallets[1]).methods.spend_public_authwit(innerHash).send(); + // The transaction should be dropped because of a cancelled authwit (duplicate nullifier) + await expect(txCancelledAuthwit.wait()).rejects.toThrowError('Transaction '); + }); + }); + }); + }); +}); diff --git a/yarn-project/end-to-end/src/e2e_token_contract.test.ts b/yarn-project/end-to-end/src/e2e_token_contract.test.ts index 6385d8ce76a..4d732410d6b 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract.test.ts @@ -12,8 +12,7 @@ import { computeMessageSecretHash, } from '@aztec/aztec.js'; import { decodeFunctionSignature } from '@aztec/foundation/abi'; -import { ReaderContract } from '@aztec/noir-contracts.js/Reader'; -import { TokenContract } from '@aztec/noir-contracts.js/Token'; +import { DocsExampleContract, ReaderContract, TokenContract } from '@aztec/noir-contracts.js'; import { jest } from '@jest/globals'; @@ -21,7 +20,7 @@ import { BITSIZE_TOO_BIG_ERROR, U128_OVERFLOW_ERROR, U128_UNDERFLOW_ERROR } from import { publicDeployAccounts, setup } from './fixtures/utils.js'; import { TokenSimulator } from './simulators/token_simulator.js'; -const TIMEOUT = 90_000; +const TIMEOUT = 100_000; describe('e2e_token_contract', () => { jest.setTimeout(TIMEOUT); @@ -35,6 +34,7 @@ describe('e2e_token_contract', () => { let logger: DebugLogger; let asset: TokenContract; + let badAccount: DocsExampleContract; let tokenSim: TokenSimulator; @@ -84,6 +84,8 @@ describe('e2e_token_contract', () => { ); expect(await asset.methods.admin().view()).toBe(accounts[0].address.toBigInt()); + + badAccount = await DocsExampleContract.deploy(wallets[0]).send().deployed(); }, 100_000); afterAll(() => teardown()); @@ -489,6 +491,42 @@ describe('e2e_token_contract', () => { expect(await asset.methods.balance_of_public(accounts[1].address).view()).toEqual(balance1); }); + it('transfer on behalf of other, cancelled authwit', async () => { + const balance0 = await asset.methods.balance_of_public(accounts[0].address).view(); + const amount = balance0 / 2n; + expect(amount).toBeGreaterThan(0n); + const nonce = Fr.random(); + + const action = asset + .withWallet(wallets[1]) + .methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce); + const messageHash = computeAuthWitMessageHash(accounts[1].address, action.request()); + + await wallets[0].setPublicAuth(messageHash, true).send().wait(); + + await wallets[0].cancelAuthWit(messageHash).send().wait(); + + // Check that the message hash is no longer valid. Need to try to send since nullifiers are handled by sequencer. + const txCancelledAuthwit = asset + .withWallet(wallets[1]) + .methods.transfer_public(accounts[0].address, accounts[1].address, amount, nonce) + .send(); + await expect(txCancelledAuthwit.wait()).rejects.toThrowError('Transaction '); + }); + + it('transfer on behalf of other, invalid spend_public_authwit on "from"', async () => { + const nonce = Fr.random(); + + // Should fail as the returned value from the badAccount is malformed + const txCancelledAuthwit = asset + .withWallet(wallets[1]) + .methods.transfer_public(badAccount.address, accounts[1].address, 0, nonce) + .send(); + await expect(txCancelledAuthwit.wait()).rejects.toThrowError( + "Assertion failed: Message not authorized by account 'result == IS_VALID_SELECTOR'", + ); + }); + it.skip('transfer into account to overflow', () => { // This should already be covered by the mint case earlier. e.g., since we cannot mint to overflow, there is not // a way to get funds enough to overflow. @@ -641,6 +679,44 @@ describe('e2e_token_contract', () => { ); expect(await asset.methods.balance_of_private(accounts[0].address).view()).toEqual(balance0); }); + + it('transfer on behalf of other, cancelled authwit', async () => { + const balance0 = await asset.methods.balance_of_private(accounts[0].address).view(); + const amount = balance0 / 2n; + const nonce = Fr.random(); + expect(amount).toBeGreaterThan(0n); + + // We need to compute the message we want to sign and add it to the wallet as approved + const action = asset + .withWallet(wallets[1]) + .methods.transfer(accounts[0].address, accounts[1].address, amount, nonce); + const messageHash = computeAuthWitMessageHash(accounts[1].address, action.request()); + + const witness = await wallets[0].createAuthWitness(messageHash); + await wallets[1].addAuthWitness(witness); + + await wallets[0].cancelAuthWit(messageHash).send().wait(); + + // Perform the transfer, should fail because nullifier already emitted + const txCancelledAuthwit = asset + .withWallet(wallets[1]) + .methods.transfer(accounts[0].address, accounts[1].address, amount, nonce) + .send(); + await expect(txCancelledAuthwit.wait()).rejects.toThrowError('Transaction '); + }); + + it('transfer on behalf of other, invalid spend_private_authwit on "from"', async () => { + const nonce = Fr.random(); + + // Should fail as the returned value from the badAccount is malformed + const txCancelledAuthwit = asset + .withWallet(wallets[1]) + .methods.transfer(badAccount.address, accounts[1].address, 0, nonce) + .send(); + await expect(txCancelledAuthwit.wait()).rejects.toThrowError( + "Assertion failed: Message not authorized by account 'result == IS_VALID_SELECTOR'", + ); + }); }); }); }); diff --git a/yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts b/yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts index a59426d3a9a..e76cef9f711 100644 --- a/yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts +++ b/yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts @@ -591,7 +591,7 @@ export const uniswapL1L2TestSuite = ( // Swap! await expect(action.simulate()).rejects.toThrowError( - "Assertion failed: Message not authorized by account 'result == IS_VALID_SELECTOR'", + "Assertion failed: Message not authorized by account 'is_valid == true'", ); }); @@ -625,7 +625,7 @@ export const uniswapL1L2TestSuite = ( Fr.ZERO, ) .simulate(), - ).rejects.toThrowError(`Assertion failed: Message not authorized by account 'result == IS_VALID_SELECTOR'`); + ).rejects.toThrowError(`Assertion failed: Message not authorized by account 'is_valid == true'`); }); // tests when trying to mix private and public flows: diff --git a/yarn-project/sequencer-client/src/sequencer/abstract_phase_manager.ts b/yarn-project/sequencer-client/src/sequencer/abstract_phase_manager.ts index 980f5de1e8f..33fd74e3669 100644 --- a/yarn-project/sequencer-client/src/sequencer/abstract_phase_manager.ts +++ b/yarn-project/sequencer-client/src/sequencer/abstract_phase_manager.ts @@ -230,7 +230,7 @@ export abstract class AbstractPhaseManager { newUnencryptedFunctionLogs.push(result.unencryptedLogs); const functionSelector = result.execution.functionData.selector.toString(); this.log.debug( - `Running public kernel circuit for ${functionSelector}@${result.execution.contractAddress.toString()}`, + `Running public kernel circuit for ${result.execution.contractAddress.toString()}:${functionSelector}`, ); executionStack.push(...result.nestedExecutions); const callData = await this.getPublicCallData(result, isExecutionRequest); diff --git a/yarn-project/sequencer-client/src/sequencer/public_processor.ts b/yarn-project/sequencer-client/src/sequencer/public_processor.ts index 99189c376a1..1cacdd2d5e8 100644 --- a/yarn-project/sequencer-client/src/sequencer/public_processor.ts +++ b/yarn-project/sequencer-client/src/sequencer/public_processor.ts @@ -125,7 +125,7 @@ export class PublicProcessor { const processedTransaction = makeProcessedTx(tx, publicKernelPublicInput, publicKernelProof); result.push(processedTransaction); - this.log(`Processed public part of ${tx.data.endNonRevertibleData.newNullifiers[0]}`, { + this.log(`Processed public part of ${tx.data.endNonRevertibleData.newNullifiers[0].value}`, { eventName: 'tx-sequencer-processing', duration: timer.ms(), publicDataUpdateRequests: