|
| 1 | +use crate::{ |
| 2 | + authwit::{authorization_interface::AuthorizationInterface, AuthorizationSelector}, |
| 3 | + hash::hash_args, |
| 4 | + macros::authorization::authorization, |
| 5 | + oracle::{execution_cache::load, offchain_effect::emit_offchain_effect}, |
| 6 | +}; |
| 7 | +use crate::protocol::{ |
| 8 | + abis::function_selector::FunctionSelector, |
| 9 | + address::AztecAddress, |
| 10 | + constants::{DOM_SEP__AUTHWIT_INNER, DOM_SEP__AUTHWIT_NULLIFIER, DOM_SEP__AUTHWIT_OUTER}, |
| 11 | + hash::poseidon2_hash_with_separator, |
| 12 | + traits::{Serialize, ToField}, |
| 13 | +}; |
| 14 | + |
| 15 | +/// Authentication witness helper library |
| 16 | +/// |
| 17 | +/// Authentication Witness is a scheme for authenticating actions on Aztec, so users can allow third-parties (e.g. |
| 18 | +/// protocols or other users) to execute an action on their behalf. |
| 19 | +/// |
| 20 | +/// This library provides helper functions to manage such witnesses. The authentication witness, is some "witness" |
| 21 | +/// (data) that authenticates a `message_hash`. The simplest example of an authentication witness, is a signature. The |
| 22 | +/// signature is the "evidence", that the signer has seen the message, agrees with it, and has allowed it. It does not |
| 23 | +/// need to be a signature. It could be any kind of "proof" that the message is allowed. Another proof could be knowing |
| 24 | +/// some kind of secret, or having some kind of "token" that allows the message. |
| 25 | +/// |
| 26 | +/// The `message_hash` is a hash of the following structure: hash(consumer, chain_id, version, inner_hash) |
| 27 | +/// - consumer: the address of the contract that is "consuming" the message, |
| 28 | +/// - chain_id: the chain id of the chain that the message is being consumed on, |
| 29 | +/// - version: the version of the chain that the message is being consumed on, |
| 30 | +/// - inner_hash: the hash of the "inner" message that is being consumed, this is the "actual" message or action. |
| 31 | +/// |
| 32 | +/// While the `inner_hash` could be anything, such as showing you signed a specific message, it will often be a hash of |
| 33 | +/// the "action" to approve, along with who made the call. As part of this library, we provide a few helper functions |
| 34 | +/// to deal with such messages. |
| 35 | +/// |
| 36 | +/// For example, we provide helper function that is used for checking that the message is an encoding of the current |
| 37 | +/// call. This can be used to let some contract "allow" another contract to act on its behalf, as long as it can show |
| 38 | +/// that it is acting on behalf of the contract. |
| 39 | +/// |
| 40 | +/// If we take a case of allowing a contract to transfer tokens on behalf of an account, the `inner_hash` can be |
| 41 | +/// derived as: inner_hash = hash(caller, "transfer", hash(to, amount)) |
| 42 | +/// |
| 43 | +/// Where the `caller` would be the address of the contract that is trying to transfer the tokens, and `to` and |
| 44 | +/// `amount` the arguments for the transfer. |
| 45 | +/// |
| 46 | +/// Note that we have both a `caller` and a `consumer`, the `consumer` will be the contract that is consuming the |
| 47 | +/// message, in the case of the transfer, it would be the `Token` contract itself, while the caller, will be the actor |
| 48 | +/// that is allowed to transfer the tokens. |
| 49 | +/// |
| 50 | +/// The authentication mechanism works differently in public and private contexts. In private, we recall that |
| 51 | +/// everything is executed on the user's device, so we can use `oracles` to "ask" the user (not contract) for |
| 52 | +/// information. In public we cannot do this, since it is executed by the sequencer (someone else). Therefore we can |
| 53 | +/// instead use a "registry" to store the messages that we have approved. See `authwit::private` and `authwit::public` |
| 54 | +/// for the corresponding flow-specific helpers; the symbols defined here are address-independent crypto utilities |
| 55 | +/// shared by both paths. |
| 56 | +/// |
| 57 | +/// --- FAQ --- |
| 58 | +/// Q: Why are we using a success flag of `poseidon2_hash_bytes("IS_VALID()")` instead of just returning a boolean? |
| 59 | +/// A: We want to make sure that we don't accidentally return `true` if there is a collision in the function |
| 60 | +/// selector. By returning a hash of `IS_VALID()`, it becomes very unlikely that there is both a collision and we |
| 61 | +/// return a success flag. |
| 62 | +/// |
| 63 | +/// Q: Why are we using static calls? |
| 64 | +/// A: We are using static calls to ensure that the account contract cannot re-enter. If it was a normal call, it |
| 65 | +/// could make a new call and do a re-entry attack. Using a static ensures that it cannot update any state. |
| 66 | +/// |
| 67 | +/// Q: Would it not be cheaper to use a nullifier instead of updating state in public? |
| 68 | +/// A: At a quick glance, a public state update + nullifier is 96 bytes, but two state updates are 128, so it would |
| 69 | +/// be cheaper to use a nullifier, if this is the way it would always be done. However, if both the approval and the |
| 70 | +/// consumption is done in the same transaction, then we will be able to squash the updates (only final tx state diff |
| 71 | +/// is posted to DA), and now it is cheaper. |
| 72 | +/// |
| 73 | +/// Q: Why is the chain id and the version part of the message hash? |
| 74 | +/// A: The chain id and the version is part of the message hash to ensure that the message is only valid on a |
| 75 | +/// specific chain to avoid a case where the same message could be used across multiple chains. |
| 76 | + |
| 77 | +pub global IS_VALID_SELECTOR: Field = 0x47dacd73; // 4 last bytes of |
| 78 | +// poseidon2_hash_bytes("IS_VALID()") |
| 79 | + |
| 80 | +/// A struct that represents a contract call the user can authorize. It's associated identifier is generated by |
| 81 | +/// serializing and hashing it. The user is expected to sign this hash to signal the contract call can be performed on |
| 82 | +/// their behalf |
| 83 | +#[authorization] |
| 84 | +pub struct CallAuthorization { |
| 85 | + pub msg_sender: AztecAddress, |
| 86 | + pub selector: FunctionSelector, |
| 87 | + pub args_hash: Field, |
| 88 | +} |
| 89 | + |
| 90 | +/// A struct that represents a request to authorize a call, which is used to emit an offchain effect so the user/wallet |
| 91 | +/// can understand what they are being asked to sign. It is generated from a CallAuthorization by adding metadata to |
| 92 | +/// it, such as the selector for the authorization, the inner hash, and the actual arguments that are being passed to |
| 93 | +/// the function call. |
| 94 | +#[derive(Serialize)] |
| 95 | +pub struct CallAuthorizationRequest { |
| 96 | + pub selector: AuthorizationSelector, |
| 97 | + pub inner_hash: Field, |
| 98 | + pub on_behalf_of: AztecAddress, |
| 99 | + pub msg_sender: AztecAddress, |
| 100 | + pub fn_selector: FunctionSelector, |
| 101 | + pub args_hash: Field, |
| 102 | +} |
| 103 | + |
| 104 | +pub(crate) unconstrained fn emit_authorization_as_offchain_effect<let N: u32>( |
| 105 | + authorization: CallAuthorization, |
| 106 | + inner_hash: Field, |
| 107 | + on_behalf_of: AztecAddress, |
| 108 | +) { |
| 109 | + let args: [Field; N] = load(authorization.args_hash); |
| 110 | + let authorization_request = CallAuthorizationRequest { |
| 111 | + selector: authorization.get_authorization_selector(), |
| 112 | + inner_hash: inner_hash, |
| 113 | + on_behalf_of: on_behalf_of, |
| 114 | + msg_sender: authorization.msg_sender, |
| 115 | + fn_selector: authorization.selector, |
| 116 | + args_hash: authorization.args_hash, |
| 117 | + }; |
| 118 | + emit_offchain_effect(authorization_request.serialize().concat(args)) |
| 119 | +} |
| 120 | + |
| 121 | +/// Compute the `message_hash` from a function call to be used by an authentication witness |
| 122 | +/// |
| 123 | +/// Useful for when you need a non-account contract to approve during execution. For example if you need a contract to |
| 124 | +/// make a call to nested contract, e.g., contract A wants to exit token T to L1 using bridge B, so it needs to allow B |
| 125 | +/// to transfer T on its behalf. |
| 126 | +/// |
| 127 | +/// @param caller The address of the contract that is calling the function, in the example above, this would be B |
| 128 | +/// @param consumer The address of the contract that is consuming the message, in the example above, this would be T |
| 129 | +/// @param chain_id The chain id of the chain that the message is being consumed on @param version The version of the |
| 130 | +/// chain that the message is being consumed on @param selector The function selector of the function that is being |
| 131 | +/// called @param args The arguments of the function that is being called |
| 132 | +pub fn compute_authwit_message_hash_from_call<let N: u32>( |
| 133 | + caller: AztecAddress, |
| 134 | + consumer: AztecAddress, |
| 135 | + chain_id: Field, |
| 136 | + version: Field, |
| 137 | + selector: FunctionSelector, |
| 138 | + args: [Field; N], |
| 139 | +) -> Field { |
| 140 | + let args_hash = hash_args(args); |
| 141 | + let inner_hash = compute_inner_authwit_hash([caller.to_field(), selector.to_field(), args_hash]); |
| 142 | + compute_authwit_message_hash(consumer, chain_id, version, inner_hash) |
| 143 | +} |
| 144 | + |
| 145 | +/// Computes the `inner_hash` of the authentication witness |
| 146 | +/// |
| 147 | +/// This is used internally, but also useful in cases where you want to compute the `inner_hash` for a specific message |
| 148 | +/// that is not necessarily a call, but just some "bytes" or text. |
| 149 | +/// |
| 150 | +/// @param args The arguments to hash |
| 151 | +pub fn compute_inner_authwit_hash<let N: u32>(args: [Field; N]) -> Field { |
| 152 | + poseidon2_hash_with_separator(args, DOM_SEP__AUTHWIT_INNER) |
| 153 | +} |
| 154 | + |
| 155 | +/// Computes the `authwit_nullifier` for a specific `on_behalf_of` and `inner_hash` |
| 156 | +/// |
| 157 | +/// Using the `on_behalf_of` and the `inner_hash` to ensure that the nullifier is siloed for a specific `on_behalf_of`. |
| 158 | +/// |
| 159 | +/// @param on_behalf_of The address that has authorized the `inner_hash` @param inner_hash The hash of the message to |
| 160 | +/// authorize |
| 161 | +pub fn compute_authwit_nullifier(on_behalf_of: AztecAddress, inner_hash: Field) -> Field { |
| 162 | + poseidon2_hash_with_separator( |
| 163 | + [on_behalf_of.to_field(), inner_hash], |
| 164 | + DOM_SEP__AUTHWIT_NULLIFIER, |
| 165 | + ) |
| 166 | +} |
| 167 | + |
| 168 | +/// Computes the `message_hash` for the authentication witness |
| 169 | +/// |
| 170 | +/// @param consumer The address of the contract that is consuming the message @param chain_id The chain id of the chain |
| 171 | +/// that the message is being consumed on @param version The version of the chain that the message is being consumed on |
| 172 | +/// @param inner_hash The hash of the "inner" message that is being consumed |
| 173 | +pub fn compute_authwit_message_hash( |
| 174 | + consumer: AztecAddress, |
| 175 | + chain_id: Field, |
| 176 | + version: Field, |
| 177 | + inner_hash: Field, |
| 178 | +) -> Field { |
| 179 | + poseidon2_hash_with_separator( |
| 180 | + [consumer.to_field(), chain_id, version, inner_hash], |
| 181 | + DOM_SEP__AUTHWIT_OUTER, |
| 182 | + ) |
| 183 | +} |
0 commit comments