@aztec/key-store 0.0.1-commit.b655e406 → 0.0.1-commit.b9865e97

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dest/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export * from './key_store.js';
2
- //# sourceMappingURL=index.d.ts.map
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLGdCQUFnQixDQUFDIn0=
@@ -1,4 +1,5 @@
1
- import { Fr, GrumpkinScalar } from '@aztec/foundation/fields';
1
+ import { Fr } from '@aztec/foundation/curves/bn254';
2
+ import { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin';
2
3
  import { type Bufferable } from '@aztec/foundation/serialize';
3
4
  import type { AztecAsyncKVStore } from '@aztec/kv-store';
4
5
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
@@ -29,6 +30,8 @@ export declare class KeyStore {
29
30
  * @returns A Promise that resolves to an array of account addresses.
30
31
  */
31
32
  getAccounts(): Promise<AztecAddress[]>;
33
+ /** Checks whether an account is registered in the key store. */
34
+ hasAccount(account: AztecAddress): Promise<boolean>;
32
35
  /**
33
36
  * Gets the key validation request for a given master public key hash and contract address.
34
37
  * @throws If the account corresponding to the master public key hash does not exist in the key store.
@@ -81,13 +84,20 @@ export declare class KeyStore {
81
84
  */
82
85
  getAppOutgoingViewingSecretKey(account: AztecAddress, app: AztecAddress): Promise<Fr>;
83
86
  /**
84
- * Retrieves the sk_m corresponding to the pk_m.
85
- * @throws If the provided public key is not associated with any of the registered accounts.
86
- * @param pkM - The master public key to get secret key for.
87
+ * Retrieves the sk_m corresponding to the given pk_m hash.
88
+ * @throws If the provided hash is not associated with any of the registered accounts.
89
+ * @param pkMHash - The master public key hash to get secret key for.
87
90
  * @returns A Promise that resolves to sk_m.
88
91
  * @dev Used when feeding the sk_m to the kernel circuit for keys verification.
89
92
  */
90
- getMasterSecretKey(pkM: PublicKey): Promise<GrumpkinScalar>;
93
+ getMasterSecretKey(pkMHash: Fr): Promise<GrumpkinScalar>;
94
+ /**
95
+ * Checks whether a given account has a key matching the provided master public key hash.
96
+ * @param account - The account address to check.
97
+ * @param pkMHash - The master public key hash to look for.
98
+ * @returns True if the account has a key with the given hash.
99
+ */
100
+ accountHasKey(account: AztecAddress, pkMHash: Fr): Promise<boolean>;
91
101
  /**
92
102
  * Gets the key prefix and account address for a given value.
93
103
  * @returns A tuple containing the key prefix and account address.
@@ -96,4 +106,4 @@ export declare class KeyStore {
96
106
  */
97
107
  getKeyPrefixAndAccount(value: Bufferable): Promise<[KeyPrefix, AztecAddress]>;
98
108
  }
99
- //# sourceMappingURL=key_store.d.ts.map
109
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoia2V5X3N0b3JlLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMva2V5X3N0b3JlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUVBLE9BQU8sRUFBRSxFQUFFLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUNwRCxPQUFPLEVBQUUsY0FBYyxFQUFTLE1BQU0sbUNBQW1DLENBQUM7QUFFMUUsT0FBTyxFQUFFLEtBQUssVUFBVSxFQUFxQixNQUFNLDZCQUE2QixDQUFDO0FBQ2pGLE9BQU8sS0FBSyxFQUFFLGlCQUFpQixFQUFpQixNQUFNLGlCQUFpQixDQUFDO0FBQ3hFLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUMzRCxPQUFPLEVBQUUsZUFBZSxFQUFFLEtBQUssY0FBYyxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFDOUUsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDNUQsT0FBTyxFQUVMLEtBQUssU0FBUyxFQUNkLEtBQUssU0FBUyxFQUtmLE1BQU0sb0JBQW9CLENBQUM7QUFPNUI7O0dBRUc7QUFDSCxxQkFBYSxRQUFROztJQUNuQixnQkFBdUIsY0FBYyxLQUFLO0lBSTFDLFlBQVksUUFBUSxFQUFFLGlCQUFpQixFQUd0QztJQUVEOzs7T0FHRztJQUNJLGFBQWEsSUFBSSxPQUFPLENBQUMsZUFBZSxDQUFDLENBSS9DO0lBRUQ7Ozs7O09BS0c7SUFDVSxVQUFVLENBQUMsRUFBRSxFQUFFLEVBQUUsRUFBRSxjQUFjLEVBQUUsY0FBYyxHQUFHLE9BQU8sQ0FBQyxlQUFlLENBQUMsQ0ErQ3hGO0lBRUQ7OztPQUdHO0lBQ1UsV0FBVyxJQUFJLE9BQU8sQ0FBQyxZQUFZLEVBQUUsQ0FBQyxDQUtsRDtJQUVELGdFQUFnRTtJQUNuRCxVQUFVLENBQUMsT0FBTyxFQUFFLFlBQVksR0FBRyxPQUFPLENBQUMsT0FBTyxDQUFDLENBRS9EO0lBRUQ7Ozs7OztPQU1HO0lBQ0ksdUJBQXVCLENBQUMsT0FBTyxFQUFFLEVBQUUsRUFBRSxlQUFlLEVBQUUsWUFBWSxHQUFHLE9BQU8sQ0FBQyxvQkFBb0IsQ0FBQyxDQTBDeEc7SUFFRDs7Ozs7T0FLRztJQUNVLDJCQUEyQixDQUFDLE9BQU8sRUFBRSxZQUFZLEdBQUcsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQVFsRjtJQUVEOzs7OztPQUtHO0lBQ1UsaUNBQWlDLENBQUMsT0FBTyxFQUFFLFlBQVksR0FBRyxPQUFPLENBQUMsU0FBUyxDQUFDLENBUXhGO0lBRUQ7Ozs7O09BS0c7SUFDVSxpQ0FBaUMsQ0FBQyxPQUFPLEVBQUUsWUFBWSxHQUFHLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FReEY7SUFFRDs7Ozs7T0FLRztJQUNVLHlCQUF5QixDQUFDLE9BQU8sRUFBRSxZQUFZLEdBQUcsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQVFoRjtJQUVEOzs7OztPQUtHO0lBQ1UsaUNBQWlDLENBQUMsT0FBTyxFQUFFLFlBQVksR0FBRyxPQUFPLENBQUMsY0FBYyxDQUFDLENBUTdGO0lBRUQ7Ozs7OztPQU1HO0lBQ1UsOEJBQThCLENBQUMsT0FBTyxFQUFFLFlBQVksRUFBRSxHQUFHLEVBQUUsWUFBWSxHQUFHLE9BQU8sQ0FBQyxFQUFFLENBQUMsQ0Fhakc7SUFFRDs7Ozs7O09BTUc7SUFDSSxrQkFBa0IsQ0FBQyxPQUFPLEVBQUUsRUFBRSxHQUFHLE9BQU8sQ0FBQyxjQUFjLENBQUMsQ0E2QjlEO0lBRUQ7Ozs7O09BS0c7SUFDSSxhQUFhLENBQUMsT0FBTyxFQUFFLFlBQVksRUFBRSxPQUFPLEVBQUUsRUFBRSxHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUMsQ0FXekU7SUFFRDs7Ozs7T0FLRztJQUNVLHNCQUFzQixDQUFDLEtBQUssRUFBRSxVQUFVLEdBQUcsT0FBTyxDQUFDLENBQUMsU0FBUyxFQUFFLFlBQVksQ0FBQyxDQUFDLENBY3pGO0NBQ0YifQ==
@@ -1 +1 @@
1
- {"version":3,"file":"key_store.d.ts","sourceRoot":"","sources":["../src/key_store.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,EAAE,EAAE,cAAc,EAAS,MAAM,0BAA0B,CAAC;AAErE,OAAO,EAAE,KAAK,UAAU,EAAqB,MAAM,6BAA6B,CAAC;AACjF,OAAO,KAAK,EAAE,iBAAiB,EAAiB,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,SAAS,EAIf,MAAM,oBAAoB,CAAC;AAE5B;;GAEG;AACH,qBAAa,QAAQ;;IACnB,gBAAuB,cAAc,KAAK;gBAG9B,QAAQ,EAAE,iBAAiB;IAIvC;;;OAGG;IACI,aAAa,IAAI,OAAO,CAAC,eAAe,CAAC;IAMhD;;;;;OAKG;IACU,UAAU,CAAC,EAAE,EAAE,EAAE,EAAE,cAAc,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAsCzF;;;OAGG;IACU,WAAW,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAOnD;;;;;;OAMG;IACU,uBAAuB,CAAC,OAAO,EAAE,EAAE,EAAE,eAAe,EAAE,YAAY,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAuC/G;;;;;OAKG;IACU,2BAA2B,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC;IAUnF;;;;;OAKG;IACU,iCAAiC,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC;IAUzF;;;;;OAKG;IACU,iCAAiC,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC;IAUzF;;;;;OAKG;IACU,yBAAyB,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC;IAUjF;;;;;OAKG;IACU,iCAAiC,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,cAAc,CAAC;IAU9F;;;;;;OAMG;IACU,8BAA8B,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,EAAE,CAAC;IAelG;;;;;;OAMG;IACU,kBAAkB,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,cAAc,CAAC;IAmBxE;;;;;OAKG;IACU,sBAAsB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;CAe3F"}
1
+ {"version":3,"file":"key_store.d.ts","sourceRoot":"","sources":["../src/key_store.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;AACpD,OAAO,EAAE,cAAc,EAAS,MAAM,mCAAmC,CAAC;AAE1E,OAAO,EAAE,KAAK,UAAU,EAAqB,MAAM,6BAA6B,CAAC;AACjF,OAAO,KAAK,EAAE,iBAAiB,EAAiB,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,SAAS,EAKf,MAAM,oBAAoB,CAAC;AAO5B;;GAEG;AACH,qBAAa,QAAQ;;IACnB,gBAAuB,cAAc,KAAK;IAI1C,YAAY,QAAQ,EAAE,iBAAiB,EAGtC;IAED;;;OAGG;IACI,aAAa,IAAI,OAAO,CAAC,eAAe,CAAC,CAI/C;IAED;;;;;OAKG;IACU,UAAU,CAAC,EAAE,EAAE,EAAE,EAAE,cAAc,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC,CA+CxF;IAED;;;OAGG;IACU,WAAW,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC,CAKlD;IAED,gEAAgE;IACnD,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,CAE/D;IAED;;;;;;OAMG;IACI,uBAAuB,CAAC,OAAO,EAAE,EAAE,EAAE,eAAe,EAAE,YAAY,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA0CxG;IAED;;;;;OAKG;IACU,2BAA2B,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC,CAQlF;IAED;;;;;OAKG;IACU,iCAAiC,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC,CAQxF;IAED;;;;;OAKG;IACU,iCAAiC,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC,CAQxF;IAED;;;;;OAKG;IACU,yBAAyB,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC,CAQhF;IAED;;;;;OAKG;IACU,iCAAiC,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,cAAc,CAAC,CAQ7F;IAED;;;;;;OAMG;IACU,8BAA8B,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,EAAE,CAAC,CAajG;IAED;;;;;;OAMG;IACI,kBAAkB,CAAC,OAAO,EAAE,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CA6B9D;IAED;;;;;OAKG;IACI,aAAa,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAWzE;IAED;;;;;OAKG;IACU,sBAAsB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,CAczF;CACF"}
package/dest/key_store.js CHANGED
@@ -1,18 +1,24 @@
1
- import { GeneratorIndex } from '@aztec/constants';
2
- import { poseidon2HashWithSeparator } from '@aztec/foundation/crypto';
3
- import { Fr, GrumpkinScalar, Point } from '@aztec/foundation/fields';
1
+ import { DomainSeparator } from '@aztec/constants';
2
+ import { poseidon2HashWithSeparator } from '@aztec/foundation/crypto/poseidon';
3
+ import { Fr } from '@aztec/foundation/curves/bn254';
4
+ import { GrumpkinScalar, Point } from '@aztec/foundation/curves/grumpkin';
4
5
  import { toArray } from '@aztec/foundation/iterable';
5
6
  import { serializeToBuffer } from '@aztec/foundation/serialize';
6
7
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
7
8
  import { CompleteAddress } from '@aztec/stdlib/contract';
8
9
  import { KeyValidationRequest } from '@aztec/stdlib/kernel';
9
- import { KEY_PREFIXES, computeAppSecretKey, deriveKeys, derivePublicKeyFromSecretKey } from '@aztec/stdlib/keys';
10
+ import { KEY_PREFIXES, computeAppSecretKey, deriveKeys, derivePublicKeyFromSecretKey, hashPublicKey } from '@aztec/stdlib/keys';
11
+ /** Maps a key prefix to the storage suffix for the corresponding master secret key. */ function secretKeyStorageSuffix(prefix) {
12
+ return prefix === 'n' ? 'nhk_m' : `${prefix}sk_m`;
13
+ }
10
14
  /**
11
15
  * Used for managing keys. Can hold keys of multiple accounts.
12
16
  */ export class KeyStore {
13
17
  static SCHEMA_VERSION = 1;
18
+ #db;
14
19
  #keys;
15
20
  constructor(database){
21
+ this.#db = database;
16
22
  this.#keys = database.openMap('key_store');
17
23
  }
18
24
  /**
@@ -29,28 +35,33 @@ import { KEY_PREFIXES, computeAppSecretKey, deriveKeys, derivePublicKeyFromSecre
29
35
  * @param partialAddress - The partial address of the account.
30
36
  * @returns The account's complete address.
31
37
  */ async addAccount(sk, partialAddress) {
32
- const { masterNullifierSecretKey, masterIncomingViewingSecretKey, masterOutgoingViewingSecretKey, masterTaggingSecretKey, publicKeys } = await deriveKeys(sk);
33
- const completeAddress = await CompleteAddress.fromSecretKeyAndPartialAddress(sk, partialAddress);
38
+ const { masterNullifierHidingKey, masterIncomingViewingSecretKey, masterOutgoingViewingSecretKey, masterTaggingSecretKey, masterNullifierPublicKey, masterOutgoingViewingPublicKey, masterTaggingPublicKey, publicKeys } = await deriveKeys(sk);
39
+ const completeAddress = await CompleteAddress.fromPublicKeysAndPartialAddress(publicKeys, partialAddress);
34
40
  const { address: account } = completeAddress;
35
- // Naming of keys is as follows ${account}-${n/iv/ov/t}${sk/pk}_m
36
- await this.#keys.set(`${account.toString()}-ivsk_m`, masterIncomingViewingSecretKey.toBuffer());
37
- await this.#keys.set(`${account.toString()}-ovsk_m`, masterOutgoingViewingSecretKey.toBuffer());
38
- await this.#keys.set(`${account.toString()}-tsk_m`, masterTaggingSecretKey.toBuffer());
39
- await this.#keys.set(`${account.toString()}-nsk_m`, masterNullifierSecretKey.toBuffer());
40
- await this.#keys.set(`${account.toString()}-npk_m`, publicKeys.masterNullifierPublicKey.toBuffer());
41
- await this.#keys.set(`${account.toString()}-ivpk_m`, publicKeys.masterIncomingViewingPublicKey.toBuffer());
42
- await this.#keys.set(`${account.toString()}-ovpk_m`, publicKeys.masterOutgoingViewingPublicKey.toBuffer());
43
- await this.#keys.set(`${account.toString()}-tpk_m`, publicKeys.masterTaggingPublicKey.toBuffer());
44
- // We store pk_m_hash under `account-{n/iv/ov/t}pk_m_hash` key to be able to obtain address and key prefix
45
- // using the #getKeyPrefixAndAccount function later on
46
- const masterNullifierPublicKeyHash = await publicKeys.masterNullifierPublicKey.hash();
47
- await this.#keys.set(`${account.toString()}-npk_m_hash`, masterNullifierPublicKeyHash.toBuffer());
48
- const masterIncomingViewingPublicKeyHash = await publicKeys.masterIncomingViewingPublicKey.hash();
49
- await this.#keys.set(`${account.toString()}-ivpk_m_hash`, masterIncomingViewingPublicKeyHash.toBuffer());
50
- const masterOutgoingViewingPublicKeyHash = await publicKeys.masterOutgoingViewingPublicKey.hash();
51
- await this.#keys.set(`${account.toString()}-ovpk_m_hash`, masterOutgoingViewingPublicKeyHash.toBuffer());
52
- const masterTaggingPublicKeyHash = await publicKeys.masterTaggingPublicKey.hash();
53
- await this.#keys.set(`${account.toString()}-tpk_m_hash`, masterTaggingPublicKeyHash.toBuffer());
41
+ // The kernel cannot check that nhpk/ovpk/tpk are on-curve or non-infinity, so the PXE/key-store
42
+ // must guarantee it before persistence. By design, the above derivation produces points that are on
43
+ // the curve and not at infinity.
44
+ // The npk/ovpk/tpk hashes are already in publicKeys; ivpk_m_hash is computed for indexing.
45
+ const masterIncomingViewingPublicKeyHash = await hashPublicKey(publicKeys.ivpkM);
46
+ // The Message Signing and Fallback Keys don't have a derivation path yet, so we just use the default values for their hashes.
47
+ // So we avoid storing them persistently. The default hash is still required for address derivation
48
+ await this.#db.transactionAsync(async ()=>{
49
+ // Naming of keys is as follows ${account}-${n/iv/ov/t}${sk/pk}_m
50
+ await this.#keys.set(`${account.toString()}-ivsk_m`, masterIncomingViewingSecretKey.toBuffer());
51
+ await this.#keys.set(`${account.toString()}-ovsk_m`, masterOutgoingViewingSecretKey.toBuffer());
52
+ await this.#keys.set(`${account.toString()}-tsk_m`, masterTaggingSecretKey.toBuffer());
53
+ await this.#keys.set(`${account.toString()}-nhk_m`, masterNullifierHidingKey.toBuffer());
54
+ await this.#keys.set(`${account.toString()}-npk_m`, masterNullifierPublicKey.toBuffer());
55
+ await this.#keys.set(`${account.toString()}-ivpk_m`, publicKeys.ivpkM.toBuffer());
56
+ await this.#keys.set(`${account.toString()}-ovpk_m`, masterOutgoingViewingPublicKey.toBuffer());
57
+ await this.#keys.set(`${account.toString()}-tpk_m`, masterTaggingPublicKey.toBuffer());
58
+ // We store pk_m_hash under `account-{n/iv/ov/t}pk_m_hash` key to be able to obtain address and key prefix
59
+ // using the #getKeyPrefixAndAccount function later on
60
+ await this.#keys.set(`${account.toString()}-npk_m_hash`, publicKeys.npkMHash.toBuffer());
61
+ await this.#keys.set(`${account.toString()}-ivpk_m_hash`, masterIncomingViewingPublicKeyHash.toBuffer());
62
+ await this.#keys.set(`${account.toString()}-ovpk_m_hash`, publicKeys.ovpkMHash.toBuffer());
63
+ await this.#keys.set(`${account.toString()}-tpk_m_hash`, publicKeys.tpkMHash.toBuffer());
64
+ });
54
65
  // At last, we return the newly derived account address
55
66
  return completeAddress;
56
67
  }
@@ -63,38 +74,45 @@ import { KEY_PREFIXES, computeAppSecretKey, deriveKeys, derivePublicKeyFromSecre
63
74
  const accounts = allMapKeys.filter((key)=>key.endsWith('-ivsk_m')).map((key)=>key.split('-')[0]);
64
75
  return accounts.map((account)=>AztecAddress.fromString(account));
65
76
  }
77
+ /** Checks whether an account is registered in the key store. */ async hasAccount(account) {
78
+ return !!await this.#keys.getAsync(`${account.toString()}-ivsk_m`);
79
+ }
66
80
  /**
67
81
  * Gets the key validation request for a given master public key hash and contract address.
68
82
  * @throws If the account corresponding to the master public key hash does not exist in the key store.
69
83
  * @param pkMHash - The master public key hash.
70
84
  * @param contractAddress - The contract address to silo the secret key in the key validation request with.
71
85
  * @returns The key validation request.
72
- */ async getKeyValidationRequest(pkMHash, contractAddress) {
73
- const [keyPrefix, account] = await this.getKeyPrefixAndAccount(pkMHash);
74
- // Now we find the master public key for the account
75
- const pkMBuffer = await this.#keys.getAsync(`${account.toString()}-${keyPrefix}pk_m`);
76
- if (!pkMBuffer) {
77
- throw new Error(`Could not find ${keyPrefix}pk_m for account ${account.toString()} whose address was successfully obtained with ${keyPrefix}pk_m_hash ${pkMHash.toString()}.`);
78
- }
79
- const pkM = Point.fromBuffer(pkMBuffer);
80
- const computedPkMHash = await pkM.hash();
81
- if (!computedPkMHash.equals(pkMHash)) {
82
- throw new Error(`Could not find ${keyPrefix}pkM for ${keyPrefix}pk_m_hash ${pkMHash.toString()}.`);
83
- }
84
- // Now we find the secret key for the public key
85
- const skMBuffer = await this.#keys.getAsync(`${account.toString()}-${keyPrefix}sk_m`);
86
- if (!skMBuffer) {
87
- throw new Error(`Could not find ${keyPrefix}sk_m for account ${account.toString()} whose address was successfully obtained with ${keyPrefix}pk_m_hash ${pkMHash.toString()}.`);
88
- }
89
- const skM = GrumpkinScalar.fromBuffer(skMBuffer);
90
- // We sanity check that it's possible to derive the public key from the secret key
91
- const derivedPkM = await derivePublicKeyFromSecretKey(skM);
92
- if (!derivedPkM.equals(pkM)) {
93
- throw new Error(`Could not derive ${keyPrefix}pkM from ${keyPrefix}skM.`);
94
- }
95
- // At last we silo the secret key and return the key validation request
96
- const skApp = await computeAppSecretKey(skM, contractAddress, keyPrefix);
97
- return new KeyValidationRequest(pkM, skApp);
86
+ */ getKeyValidationRequest(pkMHash, contractAddress) {
87
+ return this.#db.transactionAsync(async ()=>{
88
+ const [keyPrefix, account] = await this.getKeyPrefixAndAccount(pkMHash);
89
+ // Load the stored master public key point. The returned KVR carries only the hash, but we
90
+ // use the point here as a witness for two integrity checks below: (1) it matches the supplied
91
+ // hash, and (2) it matches the value derived from the stored secret key.
92
+ const pkMBuffer = await this.#keys.getAsync(`${account.toString()}-${keyPrefix}pk_m`);
93
+ if (!pkMBuffer) {
94
+ throw new Error(`Could not find ${keyPrefix}pk_m for account ${account.toString()} whose address was successfully obtained with ${keyPrefix}pk_m_hash ${pkMHash.toString()}.`);
95
+ }
96
+ const pkM = Point.fromBuffer(pkMBuffer);
97
+ // Now we find the secret key for the public key
98
+ const skStorageSuffix = secretKeyStorageSuffix(keyPrefix);
99
+ const skMBuffer = await this.#keys.getAsync(`${account.toString()}-${skStorageSuffix}`);
100
+ if (!skMBuffer) {
101
+ throw new Error(`Could not find ${skStorageSuffix} for account ${account.toString()} whose address was successfully obtained with ${keyPrefix}pk_m_hash ${pkMHash.toString()}.`);
102
+ }
103
+ const skM = GrumpkinScalar.fromBuffer(skMBuffer);
104
+ // The remaining awaits are non-DB computations. They are safe because no further IDB operations follow them.
105
+ const computedPkMHash = await hashPublicKey(pkM);
106
+ if (!computedPkMHash.equals(pkMHash)) {
107
+ throw new Error(`Could not find ${keyPrefix}pkM for ${keyPrefix}pk_m_hash ${pkMHash.toString()}.`);
108
+ }
109
+ const derivedPkM = await derivePublicKeyFromSecretKey(skM);
110
+ if (!derivedPkM.equals(pkM)) {
111
+ throw new Error(`Could not derive ${keyPrefix}pkM from ${keyPrefix}skM.`);
112
+ }
113
+ const skApp = await computeAppSecretKey(skM, contractAddress, keyPrefix);
114
+ return new KeyValidationRequest(pkMHash, skApp);
115
+ });
98
116
  }
99
117
  /**
100
118
  * Gets the master nullifier public key for a given account.
@@ -172,26 +190,52 @@ import { KEY_PREFIXES, computeAppSecretKey, deriveKeys, derivePublicKeyFromSecre
172
190
  masterOutgoingViewingSecretKey.hi,
173
191
  masterOutgoingViewingSecretKey.lo,
174
192
  app
175
- ], GeneratorIndex.OVSK_M);
193
+ ], DomainSeparator.OVSK_M);
176
194
  }
177
195
  /**
178
- * Retrieves the sk_m corresponding to the pk_m.
179
- * @throws If the provided public key is not associated with any of the registered accounts.
180
- * @param pkM - The master public key to get secret key for.
196
+ * Retrieves the sk_m corresponding to the given pk_m hash.
197
+ * @throws If the provided hash is not associated with any of the registered accounts.
198
+ * @param pkMHash - The master public key hash to get secret key for.
181
199
  * @returns A Promise that resolves to sk_m.
182
200
  * @dev Used when feeding the sk_m to the kernel circuit for keys verification.
183
- */ async getMasterSecretKey(pkM) {
184
- const [keyPrefix, account] = await this.getKeyPrefixAndAccount(pkM);
185
- const secretKeyBuffer = await this.#keys.getAsync(`${account.toString()}-${keyPrefix}sk_m`);
186
- if (!secretKeyBuffer) {
187
- throw new Error(`Could not find ${keyPrefix}sk_m for ${keyPrefix}pk_m ${pkM.toString()}. This should not happen.`);
188
- }
189
- const skM = GrumpkinScalar.fromBuffer(secretKeyBuffer);
190
- const derivedpkM = await derivePublicKeyFromSecretKey(skM);
191
- if (!derivedpkM.equals(pkM)) {
192
- throw new Error(`Could not find ${keyPrefix}skM for ${keyPrefix}pkM ${pkM.toString()} in secret keys buffer.`);
193
- }
194
- return Promise.resolve(skM);
201
+ */ getMasterSecretKey(pkMHash) {
202
+ return this.#db.transactionAsync(async ()=>{
203
+ const [keyPrefix, account] = await this.getKeyPrefixAndAccount(pkMHash);
204
+ const skStorageSuffix = secretKeyStorageSuffix(keyPrefix);
205
+ const secretKeyBuffer = await this.#keys.getAsync(`${account.toString()}-${skStorageSuffix}`);
206
+ if (!secretKeyBuffer) {
207
+ throw new Error(`Could not find ${skStorageSuffix} for ${keyPrefix}pk_m_hash ${pkMHash.toString()}. This should not happen.`);
208
+ }
209
+ const skM = GrumpkinScalar.fromBuffer(secretKeyBuffer);
210
+ // Non-DB computation safe because no further IDB operations follow.
211
+ // Integrity check: confirm the stored secret key still derives the requested hash. The check
212
+ // is hash-based rather than point-equal because the on-disk identifier is `pk_m_hash`;
213
+ // cryptographic collision resistance of `hashPublicKey` makes this equivalent to a
214
+ // direct point comparison in practice.
215
+ const derivedPkM = await derivePublicKeyFromSecretKey(skM);
216
+ const derivedPkMHash = await hashPublicKey(derivedPkM);
217
+ if (!derivedPkMHash.equals(pkMHash)) {
218
+ throw new Error(`Could not find ${skStorageSuffix} for ${keyPrefix}pk_m_hash ${pkMHash.toString()} in secret keys buffer.`);
219
+ }
220
+ return skM;
221
+ });
222
+ }
223
+ /**
224
+ * Checks whether a given account has a key matching the provided master public key hash.
225
+ * @param account - The account address to check.
226
+ * @param pkMHash - The master public key hash to look for.
227
+ * @returns True if the account has a key with the given hash.
228
+ */ accountHasKey(account, pkMHash) {
229
+ return this.#db.transactionAsync(async ()=>{
230
+ const pkMHashBuffer = serializeToBuffer(pkMHash);
231
+ for (const prefix of KEY_PREFIXES){
232
+ const stored = await this.#keys.getAsync(`${account.toString()}-${prefix}pk_m_hash`);
233
+ if (stored && Buffer.from(stored).equals(pkMHashBuffer)) {
234
+ return true;
235
+ }
236
+ }
237
+ return false;
238
+ });
195
239
  }
196
240
  /**
197
241
  * Gets the key prefix and account address for a given value.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/key-store",
3
- "version": "0.0.1-commit.b655e406",
3
+ "version": "0.0.1-commit.b9865e97",
4
4
  "type": "module",
5
5
  "exports": "./dest/index.js",
6
6
  "typedocOptions": {
@@ -11,8 +11,8 @@
11
11
  "tsconfig": "./tsconfig.json"
12
12
  },
13
13
  "scripts": {
14
- "build": "yarn clean && tsc -b",
15
- "build:dev": "tsc -b --watch",
14
+ "build": "yarn clean && ../scripts/tsc.sh",
15
+ "build:dev": "../scripts/tsc.sh --watch",
16
16
  "clean": "rm -rf ./dest .tsbuildinfo",
17
17
  "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests --maxWorkers=${JEST_MAX_WORKERS:-8}"
18
18
  },
@@ -57,16 +57,17 @@
57
57
  ]
58
58
  },
59
59
  "dependencies": {
60
- "@aztec/constants": "0.0.1-commit.b655e406",
61
- "@aztec/foundation": "0.0.1-commit.b655e406",
62
- "@aztec/kv-store": "0.0.1-commit.b655e406",
63
- "@aztec/stdlib": "0.0.1-commit.b655e406",
60
+ "@aztec/constants": "0.0.1-commit.b9865e97",
61
+ "@aztec/foundation": "0.0.1-commit.b9865e97",
62
+ "@aztec/kv-store": "0.0.1-commit.b9865e97",
63
+ "@aztec/stdlib": "0.0.1-commit.b9865e97",
64
64
  "tslib": "^2.4.0"
65
65
  },
66
66
  "devDependencies": {
67
67
  "@jest/globals": "^30.0.0",
68
68
  "@types/jest": "^30.0.0",
69
69
  "@types/node": "^22.15.17",
70
+ "@typescript/native-preview": "7.0.0-dev.20260113.1",
70
71
  "jest": "^30.0.0",
71
72
  "ts-node": "^10.9.1",
72
73
  "typescript": "^5.3.3"
package/src/key_store.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { GeneratorIndex } from '@aztec/constants';
2
- import { poseidon2HashWithSeparator } from '@aztec/foundation/crypto';
3
- import { Fr, GrumpkinScalar, Point } from '@aztec/foundation/fields';
1
+ import { DomainSeparator } from '@aztec/constants';
2
+ import { poseidon2HashWithSeparator } from '@aztec/foundation/crypto/poseidon';
3
+ import { Fr } from '@aztec/foundation/curves/bn254';
4
+ import { GrumpkinScalar, Point } from '@aztec/foundation/curves/grumpkin';
4
5
  import { toArray } from '@aztec/foundation/iterable';
5
6
  import { type Bufferable, serializeToBuffer } from '@aztec/foundation/serialize';
6
7
  import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store';
@@ -14,16 +15,24 @@ import {
14
15
  computeAppSecretKey,
15
16
  deriveKeys,
16
17
  derivePublicKeyFromSecretKey,
18
+ hashPublicKey,
17
19
  } from '@aztec/stdlib/keys';
18
20
 
21
+ /** Maps a key prefix to the storage suffix for the corresponding master secret key. */
22
+ function secretKeyStorageSuffix(prefix: KeyPrefix): string {
23
+ return prefix === 'n' ? 'nhk_m' : `${prefix}sk_m`;
24
+ }
25
+
19
26
  /**
20
27
  * Used for managing keys. Can hold keys of multiple accounts.
21
28
  */
22
29
  export class KeyStore {
23
30
  public static readonly SCHEMA_VERSION = 1;
31
+ #db: AztecAsyncKVStore;
24
32
  #keys: AztecAsyncMap<string, Buffer>;
25
33
 
26
34
  constructor(database: AztecAsyncKVStore) {
35
+ this.#db = database;
27
36
  this.#keys = database.openMap('key_store');
28
37
  }
29
38
 
@@ -45,37 +54,48 @@ export class KeyStore {
45
54
  */
46
55
  public async addAccount(sk: Fr, partialAddress: PartialAddress): Promise<CompleteAddress> {
47
56
  const {
48
- masterNullifierSecretKey,
57
+ masterNullifierHidingKey,
49
58
  masterIncomingViewingSecretKey,
50
59
  masterOutgoingViewingSecretKey,
51
60
  masterTaggingSecretKey,
61
+ masterNullifierPublicKey,
62
+ masterOutgoingViewingPublicKey,
63
+ masterTaggingPublicKey,
52
64
  publicKeys,
53
65
  } = await deriveKeys(sk);
54
66
 
55
- const completeAddress = await CompleteAddress.fromSecretKeyAndPartialAddress(sk, partialAddress);
67
+ const completeAddress = await CompleteAddress.fromPublicKeysAndPartialAddress(publicKeys, partialAddress);
56
68
  const { address: account } = completeAddress;
57
69
 
58
- // Naming of keys is as follows ${account}-${n/iv/ov/t}${sk/pk}_m
59
- await this.#keys.set(`${account.toString()}-ivsk_m`, masterIncomingViewingSecretKey.toBuffer());
60
- await this.#keys.set(`${account.toString()}-ovsk_m`, masterOutgoingViewingSecretKey.toBuffer());
61
- await this.#keys.set(`${account.toString()}-tsk_m`, masterTaggingSecretKey.toBuffer());
62
- await this.#keys.set(`${account.toString()}-nsk_m`, masterNullifierSecretKey.toBuffer());
63
-
64
- await this.#keys.set(`${account.toString()}-npk_m`, publicKeys.masterNullifierPublicKey.toBuffer());
65
- await this.#keys.set(`${account.toString()}-ivpk_m`, publicKeys.masterIncomingViewingPublicKey.toBuffer());
66
- await this.#keys.set(`${account.toString()}-ovpk_m`, publicKeys.masterOutgoingViewingPublicKey.toBuffer());
67
- await this.#keys.set(`${account.toString()}-tpk_m`, publicKeys.masterTaggingPublicKey.toBuffer());
68
-
69
- // We store pk_m_hash under `account-{n/iv/ov/t}pk_m_hash` key to be able to obtain address and key prefix
70
- // using the #getKeyPrefixAndAccount function later on
71
- const masterNullifierPublicKeyHash = await publicKeys.masterNullifierPublicKey.hash();
72
- await this.#keys.set(`${account.toString()}-npk_m_hash`, masterNullifierPublicKeyHash.toBuffer());
73
- const masterIncomingViewingPublicKeyHash = await publicKeys.masterIncomingViewingPublicKey.hash();
74
- await this.#keys.set(`${account.toString()}-ivpk_m_hash`, masterIncomingViewingPublicKeyHash.toBuffer());
75
- const masterOutgoingViewingPublicKeyHash = await publicKeys.masterOutgoingViewingPublicKey.hash();
76
- await this.#keys.set(`${account.toString()}-ovpk_m_hash`, masterOutgoingViewingPublicKeyHash.toBuffer());
77
- const masterTaggingPublicKeyHash = await publicKeys.masterTaggingPublicKey.hash();
78
- await this.#keys.set(`${account.toString()}-tpk_m_hash`, masterTaggingPublicKeyHash.toBuffer());
70
+ // The kernel cannot check that nhpk/ovpk/tpk are on-curve or non-infinity, so the PXE/key-store
71
+ // must guarantee it before persistence. By design, the above derivation produces points that are on
72
+ // the curve and not at infinity.
73
+
74
+ // The npk/ovpk/tpk hashes are already in publicKeys; ivpk_m_hash is computed for indexing.
75
+ const masterIncomingViewingPublicKeyHash = await hashPublicKey(publicKeys.ivpkM);
76
+
77
+ // The Message Signing and Fallback Keys don't have a derivation path yet, so we just use the default values for their hashes.
78
+ // So we avoid storing them persistently. The default hash is still required for address derivation
79
+
80
+ await this.#db.transactionAsync(async () => {
81
+ // Naming of keys is as follows ${account}-${n/iv/ov/t}${sk/pk}_m
82
+ await this.#keys.set(`${account.toString()}-ivsk_m`, masterIncomingViewingSecretKey.toBuffer());
83
+ await this.#keys.set(`${account.toString()}-ovsk_m`, masterOutgoingViewingSecretKey.toBuffer());
84
+ await this.#keys.set(`${account.toString()}-tsk_m`, masterTaggingSecretKey.toBuffer());
85
+ await this.#keys.set(`${account.toString()}-nhk_m`, masterNullifierHidingKey.toBuffer());
86
+
87
+ await this.#keys.set(`${account.toString()}-npk_m`, masterNullifierPublicKey.toBuffer());
88
+ await this.#keys.set(`${account.toString()}-ivpk_m`, publicKeys.ivpkM.toBuffer());
89
+ await this.#keys.set(`${account.toString()}-ovpk_m`, masterOutgoingViewingPublicKey.toBuffer());
90
+ await this.#keys.set(`${account.toString()}-tpk_m`, masterTaggingPublicKey.toBuffer());
91
+
92
+ // We store pk_m_hash under `account-{n/iv/ov/t}pk_m_hash` key to be able to obtain address and key prefix
93
+ // using the #getKeyPrefixAndAccount function later on
94
+ await this.#keys.set(`${account.toString()}-npk_m_hash`, publicKeys.npkMHash.toBuffer());
95
+ await this.#keys.set(`${account.toString()}-ivpk_m_hash`, masterIncomingViewingPublicKeyHash.toBuffer());
96
+ await this.#keys.set(`${account.toString()}-ovpk_m_hash`, publicKeys.ovpkMHash.toBuffer());
97
+ await this.#keys.set(`${account.toString()}-tpk_m_hash`, publicKeys.tpkMHash.toBuffer());
98
+ });
79
99
 
80
100
  // At last, we return the newly derived account address
81
101
  return completeAddress;
@@ -92,6 +112,11 @@ export class KeyStore {
92
112
  return accounts.map(account => AztecAddress.fromString(account));
93
113
  }
94
114
 
115
+ /** Checks whether an account is registered in the key store. */
116
+ public async hasAccount(account: AztecAddress): Promise<boolean> {
117
+ return !!(await this.#keys.getAsync(`${account.toString()}-ivsk_m`));
118
+ }
119
+
95
120
  /**
96
121
  * Gets the key validation request for a given master public key hash and contract address.
97
122
  * @throws If the account corresponding to the master public key hash does not exist in the key store.
@@ -99,43 +124,48 @@ export class KeyStore {
99
124
  * @param contractAddress - The contract address to silo the secret key in the key validation request with.
100
125
  * @returns The key validation request.
101
126
  */
102
- public async getKeyValidationRequest(pkMHash: Fr, contractAddress: AztecAddress): Promise<KeyValidationRequest> {
103
- const [keyPrefix, account] = await this.getKeyPrefixAndAccount(pkMHash);
127
+ public getKeyValidationRequest(pkMHash: Fr, contractAddress: AztecAddress): Promise<KeyValidationRequest> {
128
+ return this.#db.transactionAsync(async () => {
129
+ const [keyPrefix, account] = await this.getKeyPrefixAndAccount(pkMHash);
104
130
 
105
- // Now we find the master public key for the account
106
- const pkMBuffer = await this.#keys.getAsync(`${account.toString()}-${keyPrefix}pk_m`);
107
- if (!pkMBuffer) {
108
- throw new Error(
109
- `Could not find ${keyPrefix}pk_m for account ${account.toString()} whose address was successfully obtained with ${keyPrefix}pk_m_hash ${pkMHash.toString()}.`,
110
- );
111
- }
131
+ // Load the stored master public key point. The returned KVR carries only the hash, but we
132
+ // use the point here as a witness for two integrity checks below: (1) it matches the supplied
133
+ // hash, and (2) it matches the value derived from the stored secret key.
134
+ const pkMBuffer = await this.#keys.getAsync(`${account.toString()}-${keyPrefix}pk_m`);
135
+ if (!pkMBuffer) {
136
+ throw new Error(
137
+ `Could not find ${keyPrefix}pk_m for account ${account.toString()} whose address was successfully obtained with ${keyPrefix}pk_m_hash ${pkMHash.toString()}.`,
138
+ );
139
+ }
112
140
 
113
- const pkM = Point.fromBuffer(pkMBuffer);
114
- const computedPkMHash = await pkM.hash();
115
- if (!computedPkMHash.equals(pkMHash)) {
116
- throw new Error(`Could not find ${keyPrefix}pkM for ${keyPrefix}pk_m_hash ${pkMHash.toString()}.`);
117
- }
141
+ const pkM = Point.fromBuffer(pkMBuffer);
118
142
 
119
- // Now we find the secret key for the public key
120
- const skMBuffer = await this.#keys.getAsync(`${account.toString()}-${keyPrefix}sk_m`);
121
- if (!skMBuffer) {
122
- throw new Error(
123
- `Could not find ${keyPrefix}sk_m for account ${account.toString()} whose address was successfully obtained with ${keyPrefix}pk_m_hash ${pkMHash.toString()}.`,
124
- );
125
- }
143
+ // Now we find the secret key for the public key
144
+ const skStorageSuffix = secretKeyStorageSuffix(keyPrefix);
145
+ const skMBuffer = await this.#keys.getAsync(`${account.toString()}-${skStorageSuffix}`);
146
+ if (!skMBuffer) {
147
+ throw new Error(
148
+ `Could not find ${skStorageSuffix} for account ${account.toString()} whose address was successfully obtained with ${keyPrefix}pk_m_hash ${pkMHash.toString()}.`,
149
+ );
150
+ }
126
151
 
127
- const skM = GrumpkinScalar.fromBuffer(skMBuffer);
152
+ const skM = GrumpkinScalar.fromBuffer(skMBuffer);
128
153
 
129
- // We sanity check that it's possible to derive the public key from the secret key
130
- const derivedPkM = await derivePublicKeyFromSecretKey(skM);
131
- if (!derivedPkM.equals(pkM)) {
132
- throw new Error(`Could not derive ${keyPrefix}pkM from ${keyPrefix}skM.`);
133
- }
154
+ // The remaining awaits are non-DB computations. They are safe because no further IDB operations follow them.
155
+ const computedPkMHash = await hashPublicKey(pkM);
156
+ if (!computedPkMHash.equals(pkMHash)) {
157
+ throw new Error(`Could not find ${keyPrefix}pkM for ${keyPrefix}pk_m_hash ${pkMHash.toString()}.`);
158
+ }
134
159
 
135
- // At last we silo the secret key and return the key validation request
136
- const skApp = await computeAppSecretKey(skM, contractAddress, keyPrefix!);
160
+ const derivedPkM = await derivePublicKeyFromSecretKey(skM);
161
+ if (!derivedPkM.equals(pkM)) {
162
+ throw new Error(`Could not derive ${keyPrefix}pkM from ${keyPrefix}skM.`);
163
+ }
137
164
 
138
- return new KeyValidationRequest(pkM, skApp);
165
+ const skApp = await computeAppSecretKey(skM, contractAddress, keyPrefix!);
166
+
167
+ return new KeyValidationRequest(pkMHash, skApp);
168
+ });
139
169
  }
140
170
 
141
171
  /**
@@ -236,34 +266,65 @@ export class KeyStore {
236
266
 
237
267
  return poseidon2HashWithSeparator(
238
268
  [masterOutgoingViewingSecretKey.hi, masterOutgoingViewingSecretKey.lo, app],
239
- GeneratorIndex.OVSK_M,
269
+ DomainSeparator.OVSK_M,
240
270
  );
241
271
  }
242
272
 
243
273
  /**
244
- * Retrieves the sk_m corresponding to the pk_m.
245
- * @throws If the provided public key is not associated with any of the registered accounts.
246
- * @param pkM - The master public key to get secret key for.
274
+ * Retrieves the sk_m corresponding to the given pk_m hash.
275
+ * @throws If the provided hash is not associated with any of the registered accounts.
276
+ * @param pkMHash - The master public key hash to get secret key for.
247
277
  * @returns A Promise that resolves to sk_m.
248
278
  * @dev Used when feeding the sk_m to the kernel circuit for keys verification.
249
279
  */
250
- public async getMasterSecretKey(pkM: PublicKey): Promise<GrumpkinScalar> {
251
- const [keyPrefix, account] = await this.getKeyPrefixAndAccount(pkM);
280
+ public getMasterSecretKey(pkMHash: Fr): Promise<GrumpkinScalar> {
281
+ return this.#db.transactionAsync(async () => {
282
+ const [keyPrefix, account] = await this.getKeyPrefixAndAccount(pkMHash);
252
283
 
253
- const secretKeyBuffer = await this.#keys.getAsync(`${account.toString()}-${keyPrefix}sk_m`);
254
- if (!secretKeyBuffer) {
255
- throw new Error(
256
- `Could not find ${keyPrefix}sk_m for ${keyPrefix}pk_m ${pkM.toString()}. This should not happen.`,
257
- );
258
- }
284
+ const skStorageSuffix = secretKeyStorageSuffix(keyPrefix);
285
+ const secretKeyBuffer = await this.#keys.getAsync(`${account.toString()}-${skStorageSuffix}`);
286
+ if (!secretKeyBuffer) {
287
+ throw new Error(
288
+ `Could not find ${skStorageSuffix} for ${keyPrefix}pk_m_hash ${pkMHash.toString()}. This should not happen.`,
289
+ );
290
+ }
259
291
 
260
- const skM = GrumpkinScalar.fromBuffer(secretKeyBuffer);
261
- const derivedpkM = await derivePublicKeyFromSecretKey(skM);
262
- if (!derivedpkM.equals(pkM)) {
263
- throw new Error(`Could not find ${keyPrefix}skM for ${keyPrefix}pkM ${pkM.toString()} in secret keys buffer.`);
264
- }
292
+ const skM = GrumpkinScalar.fromBuffer(secretKeyBuffer);
265
293
 
266
- return Promise.resolve(skM);
294
+ // Non-DB computation — safe because no further IDB operations follow.
295
+ // Integrity check: confirm the stored secret key still derives the requested hash. The check
296
+ // is hash-based rather than point-equal because the on-disk identifier is `pk_m_hash`;
297
+ // cryptographic collision resistance of `hashPublicKey` makes this equivalent to a
298
+ // direct point comparison in practice.
299
+ const derivedPkM = await derivePublicKeyFromSecretKey(skM);
300
+ const derivedPkMHash = await hashPublicKey(derivedPkM);
301
+ if (!derivedPkMHash.equals(pkMHash)) {
302
+ throw new Error(
303
+ `Could not find ${skStorageSuffix} for ${keyPrefix}pk_m_hash ${pkMHash.toString()} in secret keys buffer.`,
304
+ );
305
+ }
306
+
307
+ return skM;
308
+ });
309
+ }
310
+
311
+ /**
312
+ * Checks whether a given account has a key matching the provided master public key hash.
313
+ * @param account - The account address to check.
314
+ * @param pkMHash - The master public key hash to look for.
315
+ * @returns True if the account has a key with the given hash.
316
+ */
317
+ public accountHasKey(account: AztecAddress, pkMHash: Fr): Promise<boolean> {
318
+ return this.#db.transactionAsync(async () => {
319
+ const pkMHashBuffer = serializeToBuffer(pkMHash);
320
+ for (const prefix of KEY_PREFIXES) {
321
+ const stored = await this.#keys.getAsync(`${account.toString()}-${prefix}pk_m_hash`);
322
+ if (stored && Buffer.from(stored).equals(pkMHashBuffer)) {
323
+ return true;
324
+ }
325
+ }
326
+ return false;
327
+ });
267
328
  }
268
329
 
269
330
  /**