Skip to content

Commit

Permalink
feat(AuthWit): chain_id and version in hash (#5331)
Browse files Browse the repository at this point in the history
Fixes #5074 by including the `chain_id` and `version` as part of the message hash that we are signing over in the authwits.
  • Loading branch information
LHerskind authored and sklppy88 committed Mar 22, 2024
1 parent 565f59b commit 784585a
Show file tree
Hide file tree
Showing 25 changed files with 381 additions and 81 deletions.
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

0 comments on commit 784585a

Please sign in to comment.