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): chain_id and version in hash #5331

Merged
merged 3 commits into from
Mar 22, 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
4 changes: 2 additions & 2 deletions noir-projects/aztec-nr/authwit/src/account.nr
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ impl AccountActions {
// 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 message_hash = compute_outer_authwit_hash(context.msg_sender(), context.chain_id(), context.version(), inner_hash);
let valid_fn = self.is_valid_impl;
assert(valid_fn(context, message_hash) == true, "Message not authorized by account");
context.push_new_nullifier(message_hash, 0);
Expand All @@ -90,7 +90,7 @@ impl AccountActions {
// 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 message_hash = compute_outer_authwit_hash(context.msg_sender(), context.chain_id(), context.version(), 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);
Expand Down
25 changes: 21 additions & 4 deletions noir-projects/aztec-nr/authwit/src/auth.nr
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,37 @@ pub fn assert_current_call_valid_authwit_public(context: &mut PublicContext, on_

// docs:start:compute_call_authwit_hash
// Compute the message hash to be used by an authentication witness
pub fn compute_call_authwit_hash<N>(caller: AztecAddress, consumer: AztecAddress, selector: FunctionSelector, args: [Field; N]) -> Field {
pub fn compute_call_authwit_hash<N>(
caller: AztecAddress,
consumer: AztecAddress,
chain_id: Field,
version: Field,
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)
compute_outer_authwit_hash(consumer, chain_id, version, inner_hash)
}
// docs:end:compute_call_authwit_hash

pub fn compute_inner_authwit_hash<N>(args: [Field; N]) -> Field {
pedersen_hash(args, GENERATOR_INDEX__AUTHWIT_INNER)
}

pub fn compute_outer_authwit_hash(consumer: AztecAddress, inner_hash: Field) -> Field {
pub fn compute_outer_authwit_hash(
consumer: AztecAddress,
chain_id: Field,
version: Field,
inner_hash: Field
) -> Field {
pedersen_hash(
[consumer.to_field(), inner_hash],
[
consumer.to_field(),
chain_id,
version,
inner_hash
],
GENERATOR_INDEX__AUTHWIT_OUTER
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ contract Uniswap {
// if valid, it returns the IS_VALID selector which is expected by token contract
#[aztec(public)]
fn spend_public_authwit(inner_hash: Field) -> Field {
let message_hash = compute_outer_authwit_hash(context.msg_sender(), inner_hash);
let message_hash = compute_outer_authwit_hash(context.msg_sender(), context.chain_id(), context.version(), inner_hash);
let value = storage.approved_action.at(message_hash).read();
if (value) {
context.push_new_nullifier(message_hash, 0);
Expand All @@ -192,6 +192,8 @@ contract Uniswap {
let message_hash = compute_call_authwit_hash(
token_bridge,
token,
context.chain_id(),
context.version(),
selector,
[context.this_address().to_field(), amount, nonce_for_burn_approval]
);
Expand Down
16 changes: 14 additions & 2 deletions yarn-project/accounts/src/defaults/account_interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { NodeInfo } from '@aztec/types/interfaces';
*/
export class DefaultAccountInterface implements AccountInterface {
private entrypoint: EntrypointInterface;
private chainId: Fr;
private version: Fr;

constructor(
private authWitnessProvider: AuthWitnessProvider,
Expand All @@ -22,14 +24,16 @@ export class DefaultAccountInterface implements AccountInterface {
nodeInfo.chainId,
nodeInfo.protocolVersion,
);
this.chainId = new Fr(nodeInfo.chainId);
this.version = new Fr(nodeInfo.protocolVersion);
}

createTxExecutionRequest(executions: FunctionCall[], fee?: FeeOptions): Promise<TxExecutionRequest> {
return this.entrypoint.createTxExecutionRequest(executions, fee);
}

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

getCompleteAddress(): CompleteAddress {
Expand All @@ -39,4 +43,12 @@ export class DefaultAccountInterface implements AccountInterface {
getAddress(): AztecAddress {
return this.address.address;
}

getChainId(): Fr {
return this.chainId;
}

getVersion(): Fr {
return this.version;
}
}
6 changes: 3 additions & 3 deletions yarn-project/accounts/src/ecdsa/account_contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ export class EcdsaAccountContract extends DefaultAccountContract {
class EcdsaAuthWitnessProvider implements AuthWitnessProvider {
constructor(private signingPrivateKey: Buffer) {}

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

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

createAuthWit(message: Fr): Promise<AuthWitness> {
createAuthWit(messageHash: Fr): Promise<AuthWitness> {
const schnorr = new Schnorr();
const signature = schnorr.constructSignature(message.toBuffer(), this.privateKey);
const signature = schnorr.constructSignature(messageHash.toBuffer(), this.privateKey);
const publicKey = generatePublicKey(this.privateKey);
const witness = [...publicKey.toFields(), ...signature.toBuffer(), this.partialAddress];
return Promise.resolve(new AuthWitness(message, witness));
return Promise.resolve(new AuthWitness(messageHash, witness));
}
}
12 changes: 12 additions & 0 deletions yarn-project/aztec.js/src/account/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export interface AuthWitnessProvider {
* If a message hash is provided, it will create a witness for that directly.
* Otherwise, it will compute the message hash using the caller and the action of the intent.
* @param messageHashOrIntent - The message hash or the intent (caller and action) to approve
* @param chainId - The chain id for the message, will default to the current chain id
* @param version - The version for the message, will default to the current protocol version
* @returns The authentication witness
*/
createAuthWit(
Expand All @@ -34,6 +36,10 @@ export interface AuthWitnessProvider {
caller: AztecAddress;
/** The action to approve */
action: ContractFunctionInteraction | FunctionCall;
/** The chain id to approve */
chainId?: Fr;
/** The version to approve */
version?: Fr;
},
): Promise<AuthWitness>;
}
Expand All @@ -59,5 +65,11 @@ export interface AccountInterface extends AuthWitnessProvider, EntrypointInterfa

/** Returns the address for this account. */
getAddress(): AztecAddress;

/** Returns the chain id for this account */
getChainId(): Fr;

/** Returns the rollup version for this account */
getVersion(): Fr;
}
// docs:end:account-interface
15 changes: 10 additions & 5 deletions yarn-project/aztec.js/src/fee/private_fee_payment_method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,16 @@ export class PrivateFeePaymentMethod implements FeePaymentMethod {
*/
async getFunctionCalls(maxFee: Fr): Promise<FunctionCall[]> {
const nonce = Fr.random();
const messageHash = computeAuthWitMessageHash(this.paymentContract, {
args: [this.wallet.getCompleteAddress().address, this.paymentContract, maxFee, nonce],
functionData: new FunctionData(FunctionSelector.fromSignature('unshield((Field),(Field),Field,Field)'), true),
to: this.asset,
});
const messageHash = computeAuthWitMessageHash(
this.paymentContract,
this.wallet.getChainId(),
this.wallet.getVersion(),
{
args: [this.wallet.getCompleteAddress().address, this.paymentContract, maxFee, nonce],
functionData: new FunctionData(FunctionSelector.fromSignature('unshield((Field),(Field),Field,Field)'), true),
to: this.asset,
},
);
await this.wallet.createAuthWit(messageHash);

const secretHashForRebate = computeMessageSecretHash(this.rebateSecret);
Expand Down
22 changes: 14 additions & 8 deletions yarn-project/aztec.js/src/fee/public_fee_payment_method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,20 @@ export class PublicFeePaymentMethod implements FeePaymentMethod {
*/
getFunctionCalls(maxFee: Fr): Promise<FunctionCall[]> {
const nonce = Fr.random();
const messageHash = computeAuthWitMessageHash(this.paymentContract, {
args: [this.wallet.getAddress(), this.paymentContract, maxFee, nonce],
functionData: new FunctionData(
FunctionSelector.fromSignature('transfer_public((Field),(Field),Field,Field)'),
false,
),
to: this.asset,
});

const messageHash = computeAuthWitMessageHash(
this.paymentContract,
this.wallet.getChainId(),
this.wallet.getVersion(),
{
args: [this.wallet.getAddress(), this.paymentContract, maxFee, nonce],
functionData: new FunctionData(
FunctionSelector.fromSignature('transfer_public((Field),(Field),Field,Field)'),
false,
),
to: this.asset,
},
);

return Promise.resolve([
this.wallet.setPublicAuthWit(messageHash, true).request(),
Expand Down
16 changes: 11 additions & 5 deletions yarn-project/aztec.js/src/utils/authwit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,23 @@ import { pedersenHash } from '@aztec/foundation/crypto';
// docs:start:authwit_computeAuthWitMessageHash
/**
* Compute an authentication witness message hash from a caller and a request
* H(target: AztecAddress, H(caller: AztecAddress, selector: Field, args_hash: Field))
* H(target: AztecAddress, chainId: Field, version: Field, 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)))
* H(token, 1, 1, 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 chainId - The chain id for the message
* @param version - The version for the message
* @param action - The request to be made (function call)
* @returns The message hash for the witness
*/
export const computeAuthWitMessageHash = (caller: AztecAddress, action: FunctionCall) => {
export const computeAuthWitMessageHash = (caller: AztecAddress, chainId: Fr, version: Fr, action: FunctionCall) => {
return computeOuterAuthWitHash(
action.to.toField(),
chainId,
version,
computeInnerAuthWitHash([
caller.toField(),
action.functionData.selector.toField(),
Expand Down Expand Up @@ -51,12 +55,14 @@ export const computeInnerAuthWitHash = (args: Fr[]) => {
* 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 chainId - The chain id that can "consume" the authwit
* @param version - The version 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) => {
export const computeOuterAuthWitHash = (consumer: AztecAddress, chainId: Fr, version: Fr, innerHash: Fr) => {
return pedersenHash(
[consumer.toField(), innerHash].map(fr => fr.toBuffer()),
[consumer.toField(), chainId, version, innerHash].map(fr => fr.toBuffer()),
GeneratorIndex.AUTHWIT_OUTER,
);
};
40 changes: 37 additions & 3 deletions yarn-project/aztec.js/src/wallet/account_wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ export class AccountWallet extends BaseWallet {
return this.account.createTxExecutionRequest(execs, fee);
}

getChainId(): Fr {
return this.account.getChainId();
}

getVersion(): Fr {
return this.account.getVersion();
}

/**
* Computes an authentication witness from either a message or a caller and an action.
* If a message is provided, it will create a witness for the message directly.
Expand All @@ -35,6 +43,10 @@ export class AccountWallet extends BaseWallet {
caller: AztecAddress;
/** The action to approve */
action: ContractFunctionInteraction | FunctionCall;
/** The chain id to approve */
chainId?: Fr;
/** The version to approve */
version?: Fr;
},
): Promise<AuthWitness> {
const messageHash = this.getMessageHash(messageHashOrIntent);
Expand All @@ -59,6 +71,10 @@ export class AccountWallet extends BaseWallet {
caller: AztecAddress;
/** The action to approve */
action: ContractFunctionInteraction | FunctionCall;
/** The chain id to approve */
chainId?: Fr;
/** The version to approve */
version?: Fr;
},
authorized: boolean,
): ContractFunctionInteraction {
Expand All @@ -84,16 +100,26 @@ export class AccountWallet extends BaseWallet {
caller: AztecAddress;
/** The action to approve */
action: ContractFunctionInteraction | FunctionCall;
/** The chain id to approve */
chainId?: Fr;
/** The version to approve */
version?: Fr;
},
): Fr {
if (Buffer.isBuffer(messageHashOrIntent)) {
return Fr.fromBuffer(messageHashOrIntent);
} else if (messageHashOrIntent instanceof Fr) {
return messageHashOrIntent;
} else if (messageHashOrIntent.action instanceof ContractFunctionInteraction) {
return computeAuthWitMessageHash(messageHashOrIntent.caller, messageHashOrIntent.action.request());
} else {
return computeAuthWitMessageHash(
messageHashOrIntent.caller,
messageHashOrIntent.chainId || this.getChainId(),
messageHashOrIntent.version || this.getVersion(),
messageHashOrIntent.action instanceof ContractFunctionInteraction
? messageHashOrIntent.action.request()
: messageHashOrIntent.action,
);
}
return computeAuthWitMessageHash(messageHashOrIntent.caller, messageHashOrIntent.action);
}

/**
Expand All @@ -113,6 +139,10 @@ export class AccountWallet extends BaseWallet {
caller: AztecAddress;
/** The action to approve */
action: ContractFunctionInteraction | FunctionCall;
/** The chain id to approve */
chainId?: Fr;
/** The version to approve */
version?: Fr;
},
): Promise<{
/** boolean flag indicating if the authwit is valid in private context */
Expand Down Expand Up @@ -148,6 +178,10 @@ export class AccountWallet extends BaseWallet {
caller: AztecAddress;
/** The action to approve */
action: ContractFunctionInteraction | FunctionCall;
/** The chain id to approve */
chainId?: Fr;
/** The version to approve */
version?: Fr;
},
): ContractFunctionInteraction {
const message = this.getMessageHash(messageHashOrIntent);
Expand Down
8 changes: 8 additions & 0 deletions yarn-project/aztec.js/src/wallet/base_wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ export abstract class BaseWallet implements Wallet {

abstract getCompleteAddress(): CompleteAddress;

abstract getChainId(): Fr;

abstract getVersion(): Fr;

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

abstract createAuthWit(
Expand All @@ -42,6 +46,10 @@ export abstract class BaseWallet implements Wallet {
caller: AztecAddress;
/** The action to approve */
action: ContractFunctionInteraction | FunctionCall;
/** The chain id to approve */
chainId?: Fr;
/** The version to approve */
version?: Fr;
},
): Promise<AuthWitness>;

Expand Down
Loading
Loading