|
1 | 1 | import { type AuthWitness, type FunctionCall, type PXE, type TxExecutionRequest } from '@aztec/circuit-types'; |
2 | | -import { type AztecAddress, Fr } from '@aztec/circuits.js'; |
| 2 | +import { AztecAddress, CANONICAL_KEY_REGISTRY_ADDRESS, Fq, Fr, derivePublicKeyFromSecretKey } from '@aztec/circuits.js'; |
3 | 3 | import { type ABIParameterVisibility, type FunctionAbi, FunctionType } from '@aztec/foundation/abi'; |
4 | 4 |
|
5 | 5 | import { type AccountInterface } from '../account/interface.js'; |
@@ -165,6 +165,30 @@ export class AccountWallet extends BaseWallet { |
165 | 165 | return { isValidInPrivate, isValidInPublic }; |
166 | 166 | } |
167 | 167 |
|
| 168 | + /** |
| 169 | + * Rotates the account master nullifier key pair. |
| 170 | + * @param newNskM - The new master nullifier secret key we want to use. |
| 171 | + * @remarks - This function also calls the canonical key registry with the account's new derived master nullifier public key. |
| 172 | + * We are doing it this way to avoid user error, in the case that a user rotates their keys in the key registry, |
| 173 | + * but fails to do so in the key store. This leads to unspendable notes. |
| 174 | + * |
| 175 | + * This does not hinder our ability to spend notes tied to a previous master nullifier public key, provided we have the master nullifier secret key for it. |
| 176 | + */ |
| 177 | + public async rotateNullifierKeys(newNskM: Fq = Fq.random()): Promise<void> { |
| 178 | + // We rotate our secret key in the keystore first, because if the subsequent interaction fails, there are no bad side-effects. |
| 179 | + // If vice versa (the key registry is called first), but the call to the PXE fails, we will end up in a situation with unspendable notes, as we have not committed our |
| 180 | + // nullifier secret key to our wallet. |
| 181 | + await this.pxe.rotateNskM(this.getAddress(), newNskM); |
| 182 | + const interaction = new ContractFunctionInteraction( |
| 183 | + this, |
| 184 | + AztecAddress.fromBigInt(CANONICAL_KEY_REGISTRY_ADDRESS), |
| 185 | + this.getRotateNpkMAbi(), |
| 186 | + [this.getAddress(), derivePublicKeyFromSecretKey(newNskM), Fr.ZERO], |
| 187 | + ); |
| 188 | + |
| 189 | + await interaction.send().wait(); |
| 190 | + } |
| 191 | + |
168 | 192 | /** |
169 | 193 | * Returns a function interaction to cancel a message hash as authorized in this account. |
170 | 194 | * @param messageHashOrIntent - The message or the caller and action to authorize/revoke |
@@ -268,4 +292,39 @@ export class AccountWallet extends BaseWallet { |
268 | 292 | returnTypes: [{ kind: 'array', length: 2, type: { kind: 'boolean' } }], |
269 | 293 | }; |
270 | 294 | } |
| 295 | + |
| 296 | + private getRotateNpkMAbi(): FunctionAbi { |
| 297 | + return { |
| 298 | + name: 'rotate_npk_m', |
| 299 | + isInitializer: false, |
| 300 | + functionType: FunctionType.PUBLIC, |
| 301 | + isInternal: false, |
| 302 | + isStatic: false, |
| 303 | + parameters: [ |
| 304 | + { |
| 305 | + name: 'address', |
| 306 | + type: { |
| 307 | + fields: [{ name: 'inner', type: { kind: 'field' } }], |
| 308 | + kind: 'struct', |
| 309 | + path: 'authwit::aztec::protocol_types::address::aztec_address::AztecAddress', |
| 310 | + }, |
| 311 | + visibility: 'private' as ABIParameterVisibility, |
| 312 | + }, |
| 313 | + { |
| 314 | + name: 'new_npk_m', |
| 315 | + type: { |
| 316 | + fields: [ |
| 317 | + { name: 'x', type: { kind: 'field' } }, |
| 318 | + { name: 'y', type: { kind: 'field' } }, |
| 319 | + ], |
| 320 | + kind: 'struct', |
| 321 | + path: 'authwit::aztec::protocol_types::grumpkin_point::GrumpkinPoint', |
| 322 | + }, |
| 323 | + visibility: 'private' as ABIParameterVisibility, |
| 324 | + }, |
| 325 | + { name: 'nonce', type: { kind: 'field' }, visibility: 'private' as ABIParameterVisibility }, |
| 326 | + ], |
| 327 | + returnTypes: [], |
| 328 | + }; |
| 329 | + } |
271 | 330 | } |
0 commit comments