Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Authwit): lookup the validity of authwits #5316

Merged
merged 3 commits into from
Mar 21, 2024
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
9 changes: 8 additions & 1 deletion noir-projects/aztec-nr/aztec/src/hash.nr
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
use dep::protocol_types::{constants::GENERATOR_INDEX__L1_TO_L2_MESSAGE_SECRET, hash::pedersen_hash};
use dep::protocol_types::{
address::AztecAddress,
constants::GENERATOR_INDEX__L1_TO_L2_MESSAGE_SECRET,
hash::{pedersen_hash, silo_nullifier}};

pub fn compute_secret_hash(secret: Field) -> Field {
// TODO(#1205) This is probably not the right index to use
pedersen_hash([secret], GENERATOR_INDEX__L1_TO_L2_MESSAGE_SECRET)
}

pub fn compute_siloed_nullifier(address: AztecAddress, nullifier: Field) -> Field {
silo_nullifier(address, nullifier)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,24 @@ contract SchnorrAccount {
use dep::std;

use dep::aztec::prelude::{AztecAddress, FunctionSelector, NoteHeader, PrivateContext, PrivateImmutable};

use dep::aztec::state_vars::{Map, PublicMutable};
use dep::aztec::{context::Context, oracle::get_public_key::get_public_key};
use dep::authwit::{
entrypoint::{app::AppPayload, fee::FeePayload}, account::AccountActions,
auth_witness::get_auth_witness
};
use dep::aztec::hash::compute_siloed_nullifier;
use dep::aztec::oracle::get_nullifier_membership_witness::get_low_nullifier_membership_witness;

use crate::public_key_note::{PublicKeyNote, PUBLIC_KEY_NOTE_LEN};

struct Storage {
// docs:start:storage
signing_public_key: PrivateImmutable<PublicKeyNote>,
// docs:end:storage
approved_actions: Map<Field, PublicMutable<bool>>,
}

global ACCOUNT_ACTIONS_STORAGE_SLOT = 2;

// Constructs the contract
#[aztec(private)]
#[aztec(initializer)]
Expand All @@ -37,19 +38,31 @@ contract SchnorrAccount {
// Note: If you globally change the entrypoint signature don't forget to update default_entrypoint.ts file
#[aztec(private)]
fn entrypoint(app_payload: pub AppPayload, fee_payload: pub FeePayload) {
let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl);
let actions = AccountActions::private(
&mut context,
storage.approved_actions.storage_slot,
is_valid_impl
);
actions.entrypoint(app_payload, fee_payload);
}

#[aztec(private)]
fn spend_private_authwit(inner_hash: Field) -> Field {
let actions = AccountActions::private(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl);
let actions = AccountActions::private(
&mut context,
storage.approved_actions.storage_slot,
is_valid_impl
);
actions.spend_private_authwit(inner_hash)
}

#[aztec(public)]
fn spend_public_authwit(inner_hash: Field) -> Field {
let actions = AccountActions::public(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl);
let actions = AccountActions::public(
&mut context,
storage.approved_actions.storage_slot,
is_valid_impl
);
actions.spend_public_authwit(inner_hash)
}

Expand All @@ -62,7 +75,11 @@ contract SchnorrAccount {
#[aztec(public)]
#[aztec(internal)]
fn approve_public_authwit(outer_hash: Field) {
let actions = AccountActions::public(&mut context, ACCOUNT_ACTIONS_STORAGE_SLOT, is_valid_impl);
let actions = AccountActions::public(
&mut context,
storage.approved_actions.storage_slot,
is_valid_impl
);
actions.approve_public_authwit(outer_hash)
}

Expand Down Expand Up @@ -92,4 +109,54 @@ contract SchnorrAccount {
// docs:end:entrypoint
true
}

/**
* @notice Helper function to check the existing and validity of authwitnesses
* @dev TODO: myself and block_number should be removed and passed from a context
Copy link
Contributor

Choose a reason for hiding this comment

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

This would be solved by #2665 right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not directly as this one is doing both public and private, likely better addressed by having a unconstrained context as we briefly discussed the other day in the scrum. Should likely have a larger issue as it can also align functions so they are not totally different.

* @param myself The address of the contract
* @param block_number The block number to check the nullifier against
* @param check_private Whether to check the validity of the authwitness in private state or not
* @param message_hash The message hash of the message to check the validity
nventuro marked this conversation as resolved.
Show resolved Hide resolved
* @return An array of two booleans, the first is the validity of the authwitness in the private state,
* the second is the validity of the authwitness in the public state
* Both values will be `false` if the nullifier is spent
*/
unconstrained fn lookup_validity(
myself: AztecAddress,
block_number: u32,
check_private: bool,
message_hash: Field
) -> pub [bool; 2] {
let valid_in_private = if check_private {
let public_key = storage.signing_public_key.view_note();
let witness: [Field; 64] = get_auth_witness(message_hash);
let mut signature: [u8; 64] = [0; 64];
for i in 0..64 {
signature[i] = witness[i] as u8;
}
std::schnorr::verify_signature(
public_key.x,
public_key.y,
signature,
message_hash.to_be_bytes(32)
)
} else {
false
};

let valid_in_public = storage.approved_actions.at(message_hash).read();

// Compute the nullifier and check if it is spent
// This will BLINDLY TRUST the oracle, but the oracle is us, and
// it is not as part of execution of the contract, so we are good.
let siloed_nullifier = compute_siloed_nullifier(myself, message_hash);
let lower_wit = get_low_nullifier_membership_witness(block_number, siloed_nullifier);
let is_spent = lower_wit.leaf_preimage.nullifier == siloed_nullifier;
Comment on lines +153 to +154
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why do we need a full membership witness if we're blinding trusting the oracle? We could just return a flag "spent". Or is this just to avoid introducing a new oracle call?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Was to not introduce a new oracle 👍


if is_spent {
[false, false]
} else {
[valid_in_private, valid_in_public]
}
}
}
98 changes: 84 additions & 14 deletions yarn-project/aztec.js/src/wallet/account_wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,33 @@ export class AccountWallet extends BaseWallet {
return witness;
}

/**
* Returns a function interaction to set a message hash as authorized or revoked in this account.
* Public calls can then consume this authorization.
* @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 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 {
return this.cancelAuthWit(message);
}
}

/**
* Returns the message hash for the given message or authwit input.
* @param messageHashOrIntent - The message hash or the caller and action to authorize
Expand Down Expand Up @@ -70,13 +97,14 @@ export class AccountWallet extends BaseWallet {
}

/**
* Returns a function interaction to set a message hash as authorized or revoked in this account.
* Public calls can then consume this authorization.
* @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.
* Lookup the validity of an authwit in private and public contexts.
* If the authwit have been consumed already (nullifier spent), will return false in both contexts.
* @param target - The target contract address
* @param messageHashOrIntent - The message hash or the caller and action to authorize/revoke
* @returns - A struct containing the validity of the authwit in private and public contexts.
*/
public setPublicAuthWit(
async lookupValidity(
target: AztecAddress,
messageHashOrIntent:
| Fr
| Buffer
Expand All @@ -86,14 +114,24 @@ export class AccountWallet extends BaseWallet {
/** 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 {
return this.cancelAuthWit(message);
}
): Promise<{
/** boolean flag indicating if the authwit is valid in private context */
isValidInPrivate: boolean;
/** boolean flag indicating if the authwit is valid in public context */
isValidInPublic: boolean;
}> {
const messageHash = this.getMessageHash(messageHashOrIntent);
const witness = await this.getAuthWitness(messageHash);
const blockNumber = await this.getBlockNumber();
const interaction = new ContractFunctionInteraction(this, target, this.getLookupValidityAbi(), [
target,
blockNumber,
witness != undefined,
messageHash,
]);

const [isValidInPrivate, isValidInPublic] = await interaction.view();
return { isValidInPrivate, isValidInPublic };
}

/**
Expand Down Expand Up @@ -160,4 +198,36 @@ export class AccountWallet extends BaseWallet {
returnTypes: [],
};
}

private getLookupValidityAbi(): FunctionAbi {
return {
name: 'lookup_validity',
isInitializer: false,
functionType: FunctionType.UNCONSTRAINED,
isInternal: false,
parameters: [
{
name: 'myself',
type: {
kind: 'struct',
path: 'authwit::aztec::protocol_types::address::aztec_address::AztecAddress',
fields: [{ name: 'inner', type: { kind: 'field' } }],
},
visibility: 'private' as ABIParameterVisibility,
},
{
name: 'block_number',
type: { kind: 'integer', sign: 'unsigned', width: 32 },
visibility: 'private' as ABIParameterVisibility,
},
{
name: 'check_private',
type: { kind: 'boolean' },
visibility: 'private' as ABIParameterVisibility,
},
{ name: 'message_hash', type: { kind: 'field' }, visibility: 'private' as ABIParameterVisibility },
],
returnTypes: [{ kind: 'array', length: 2, type: { kind: 'boolean' } }],
};
}
}
3 changes: 3 additions & 0 deletions yarn-project/aztec.js/src/wallet/base_wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ export abstract class BaseWallet implements Wallet {
addAuthWitness(authWitness: AuthWitness) {
return this.pxe.addAuthWitness(authWitness);
}
getAuthWitness(messageHash: Fr) {
return this.pxe.getAuthWitness(messageHash);
}
isContractClassPubliclyRegistered(id: Fr): Promise<boolean> {
return this.pxe.isContractClassPubliclyRegistered(id);
}
Expand Down
7 changes: 7 additions & 0 deletions yarn-project/circuit-types/src/interfaces/pxe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ export interface PXE {
*/
addAuthWitness(authWitness: AuthWitness): Promise<void>;

/**
* Fetches the serialized auth witness for a given message hash or returns undefined if not found.
* @param messageHash - The hash of the message for which to get the auth witness.
* @returns The serialized auth witness for the given message hash.
*/
getAuthWitness(messageHash: Fr): Promise<Fr[] | undefined>;

/**
* Adding a capsule to the capsule dispenser.
* @param capsule - An array of field elements representing the capsule.
Expand Down
Loading
Loading