Skip to content

Commit

Permalink
feat: new key store (#5653)
Browse files Browse the repository at this point in the history
Fixes #5607
  • Loading branch information
benesjan authored Apr 12, 2024
1 parent 202fc1b commit 3e44a58
Show file tree
Hide file tree
Showing 10 changed files with 257 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,6 @@ global NUM_BASE_PARITY_PER_ROOT_PARITY: u64 = 4;
* | MID | 8 < n ≤ 16 | 32 < hash_index ≤ 40 |
* | HIGH | 16 < n ≤ 48 | 40 < hash_index ≤ 48 |
* +-----------+-------------------------------+----------------------+
*
* Note: When modifying, modify `GeneratorIndexPacker` in packer.hpp accordingly.
*/
// Indices with size ≤ 8
global GENERATOR_INDEX__NOTE_HASH = 1;
Expand Down Expand Up @@ -238,3 +236,10 @@ global GENERATOR_INDEX__PUBLIC_CIRCUIT_PUBLIC_INPUTS = 43;
global GENERATOR_INDEX__FUNCTION_ARGS = 44;
global GENERATOR_INDEX__AUTHWIT_INNER = 45;
global GENERATOR_INDEX__AUTHWIT_OUTER = 46;
// Key related generators follow
global GENERATOR_INDEX__NSK_M = 47;
global GENERATOR_INDEX__IVSK_M = 48;
global GENERATOR_INDEX__OVSK_M = 49;
global GENERATOR_INDEX__TSK_M = 50;
global GENERATOR_INDEX__PUBLIC_KEYS_HASH = 51;
global GENERATOR_INDEX__CONTRACT_ADDRESS_V1 = 52;
1 change: 1 addition & 0 deletions yarn-project/circuit-types/src/keys/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './key_pair.js';
export * from './key_store.js';
export * from './new_key_store.js';
1 change: 1 addition & 0 deletions yarn-project/circuit-types/src/keys/key_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { type AztecAddress, type GrumpkinPrivateKey, type PublicKey } from '@azt
/**
* Represents a secure storage for managing keys.
* Provides functionality to create and retrieve accounts, private and public keys,
* TODO(#5627): 💣💣💣
*/
export interface KeyStore {
/**
Expand Down
52 changes: 52 additions & 0 deletions yarn-project/circuit-types/src/keys/new_key_store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { type AztecAddress, type Fr, type PartialAddress, type PublicKey } from '@aztec/circuits.js';

/**
* Represents a secure storage for managing keys.
*/
export interface NewKeyStore {
/**
* Creates a new account from a randomly generated secret key.
* @returns A promise that resolves to the newly created account's AztecAddress.
*/
createAccount(): Promise<AztecAddress>;

/**
* Adds an account to the key store from the provided secret key.
* @param sk - The secret key of the account.
* @param partialAddress - The partial address of the account.
* @returns The account's address.
*/
addAccount(sk: Fr, partialAddress: PartialAddress): Promise<AztecAddress>;

/**
* Gets the master nullifier public key for a given account.
* @throws If the account does not exist in the key store.
* @param account - The account address for which to retrieve the master nullifier public key.
* @returns The master nullifier public key for the account.
*/
getMasterNullifierPublicKey(account: AztecAddress): Promise<PublicKey>;

/**
* Gets the master incoming viewing public key for a given account.
* @throws If the account does not exist in the key store.
* @param account - The account address for which to retrieve the master incoming viewing public key.
* @returns The master incoming viewing public key for the account.
*/
getMasterIncomingViewingPublicKey(account: AztecAddress): Promise<PublicKey>;

/**
* Retrieves the master outgoing viewing key.
* @throws If the account does not exist in the key store.
* @param account - The account to retrieve the master outgoing viewing key for.
* @returns A Promise that resolves to the master outgoing viewing key.
*/
getMasterOutgoingViewingPublicKey(account: AztecAddress): Promise<PublicKey>;

/**
* Retrieves the master tagging key.
* @throws If the account does not exist in the key store.
* @param account - The account to retrieve the master tagging key for.
* @returns A Promise that resolves to the master tagging key.
*/
getMasterTaggingPublicKey(account: AztecAddress): Promise<PublicKey>;
}
6 changes: 6 additions & 0 deletions yarn-project/circuits.js/src/constants.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,10 @@ export enum GeneratorIndex {
FUNCTION_ARGS = 44,
AUTHWIT_INNER = 45,
AUTHWIT_OUTER = 46,
NSK_M = 47,
IVSK_M = 48,
OVSK_M = 49,
TSK_M = 50,
PUBLIC_KEYS_HASH = 51,
CONTRACT_ADDRESS_V1 = 52,
}
1 change: 1 addition & 0 deletions yarn-project/foundation/src/crypto/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { BarretenbergSync } from '@aztec/bb.js';
export * from './keccak/index.js';
export * from './random/index.js';
export * from './sha256/index.js';
export * from './sha512/index.js';
export * from './pedersen/index.js';
export * from './poseidon/index.js';

Expand Down
16 changes: 16 additions & 0 deletions yarn-project/foundation/src/crypto/sha512/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { default as hash } from 'hash.js';

import { GrumpkinScalar } from '../../fields/fields.js';
import { type Bufferable, serializeToBuffer } from '../../serialize/serialize.js';

export const sha512 = (data: Buffer) => Buffer.from(hash.sha512().update(data).digest());

/**
* @dev We don't truncate in this function (unlike in sha256ToField) because this function is used in situations where
* we don't care only about collision resistance but we need the output to be uniformly distributed as well. This is
* because we use it as a pseudo-random function.
*/
export const sha512ToGrumpkinScalar = (data: Bufferable[]) => {
const buffer = serializeToBuffer(data);
return GrumpkinScalar.fromBufferReduce(sha512(buffer));
};
42 changes: 42 additions & 0 deletions yarn-project/key-store/src/new_test_key_store.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Fr } from '@aztec/circuits.js';
import { Grumpkin } from '@aztec/circuits.js/barretenberg';
import { openTmpStore } from '@aztec/kv-store/utils';

import { NewTestKeyStore } from './new_test_key_store.js';

describe('NewTestKeyStore', () => {
it('Adds account and returns keys', async () => {
const db = openTmpStore();
const keyStore = new NewTestKeyStore(new Grumpkin(), db);

// Arbitrary fixed values
const sk = new Fr(8923n);
const partialAddress = new Fr(243523n);

const accountAddress = await keyStore.addAccount(sk, partialAddress);
expect(accountAddress.toString()).toMatchInlineSnapshot(
`"0x2e34847ad9019320ac89a6ec9b42fec90f94ef4162fdfdd7f5b7668e32d82655"`,
);

// TODO(#5714): The keys are currently the same here because separator is currently ignored in poseidon
const masterNullifierPublicKey = await keyStore.getMasterNullifierPublicKey(accountAddress);
expect(masterNullifierPublicKey.toString()).toMatchInlineSnapshot(
`"0x2ef5d15dd65d29546680ab72846fb071f41cb9f2a0212215e6c560e29df4ff650ce764818364b376be92dc2f49577fe440e64a16012584f7c4ee94f7edbc323a"`,
);

const masterIncomingViewingPublicKey = await keyStore.getMasterIncomingViewingPublicKey(accountAddress);
expect(masterIncomingViewingPublicKey.toString()).toMatchInlineSnapshot(
`"0x1c088f4e4a711f236a88b55da9ddf388de0bc00d56a5ceca96cea3a5cbe75bf32db0a333ba30c36b844d9fc6d2fb0de8d10e4371f0c5baebae452d90ff366798"`,
);

const masterOutgoingViewingPublicKey = await keyStore.getMasterOutgoingViewingPublicKey(accountAddress);
expect(masterOutgoingViewingPublicKey.toString()).toMatchInlineSnapshot(
`"0x232d0b445d097fbc2046012c3fc474f6a9beef97eda1d8d1f2487dbe501ee1e70e8db9a824531a14e8717dee54cbb7abfec29a88c550a49617258bd6fd858242"`,
);

const masterTaggingPublicKey = await keyStore.getMasterTaggingPublicKey(accountAddress);
expect(masterTaggingPublicKey.toString()).toMatchInlineSnapshot(
`"0x076429010fdebfa522b053267f654a4c5daf18589915d96f7e5001d63ea2033f27f915f254560c84450aa38e93c3162be52492d05b316e75f542e3b302117360"`,
);
});
});
130 changes: 130 additions & 0 deletions yarn-project/key-store/src/new_test_key_store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { type NewKeyStore, type PublicKey } from '@aztec/circuit-types';
import { AztecAddress, Fr, GeneratorIndex, type PartialAddress, Point } from '@aztec/circuits.js';
import { type Grumpkin } from '@aztec/circuits.js/barretenberg';
import { poseidon2Hash, sha512ToGrumpkinScalar } from '@aztec/foundation/crypto';
import { type AztecKVStore, type AztecMap } from '@aztec/kv-store';

/**
* TestKeyStore is an implementation of the KeyStore interface, used for managing key pairs in a testing environment.
* It should be utilized in testing scenarios where secure key management is not required, and ease-of-use is prioritized.
*/
export class NewTestKeyStore implements NewKeyStore {
#keys: AztecMap<string, Buffer>;

constructor(private curve: Grumpkin, database: AztecKVStore) {
this.#keys = database.openMap('key_store');
}

/**
* Creates a new account from a randomly generated secret key.
* @returns A promise that resolves to the newly created account's AztecAddress.
*/
public createAccount(): Promise<AztecAddress> {
const sk = Fr.random();
const partialAddress = Fr.random();
return this.addAccount(sk, partialAddress);
}

/**
* Adds an account to the key store from the provided secret key.
* @param sk - The secret key of the account.
* @param partialAddress - The partial address of the account.
* @returns The account's address.
*/
public async addAccount(sk: Fr, partialAddress: PartialAddress): Promise<AztecAddress> {
// First we derive master secret keys - we use sha512 here because this derivation will never take place
// in a circuit
const masterNullifierSecretKey = sha512ToGrumpkinScalar([sk, GeneratorIndex.NSK_M]);
const masterIncomingViewingSecretKey = sha512ToGrumpkinScalar([sk, GeneratorIndex.IVSK_M]);
const masterOutgoingViewingSecretKey = sha512ToGrumpkinScalar([sk, GeneratorIndex.OVSK_M]);
const masterTaggingSecretKey = sha512ToGrumpkinScalar([sk, GeneratorIndex.TSK_M]);

// Then we derive master public keys
const masterNullifierPublicKey = this.curve.mul(this.curve.generator(), masterNullifierSecretKey);
const masterIncomingViewingPublicKey = this.curve.mul(this.curve.generator(), masterIncomingViewingSecretKey);
const masterOutgoingViewingPublicKey = this.curve.mul(this.curve.generator(), masterOutgoingViewingSecretKey);
const masterTaggingPublicKey = this.curve.mul(this.curve.generator(), masterTaggingSecretKey);

// We hash the public keys to get the public keys hash
const publicKeysHash = poseidon2Hash(
[
masterNullifierPublicKey,
masterIncomingViewingPublicKey,
masterOutgoingViewingPublicKey,
masterTaggingPublicKey,
],
GeneratorIndex.PUBLIC_KEYS_HASH,
);

// We hash the partial address and the public keys hash to get the account address
// TODO(#5726): Should GeneratorIndex.CONTRACT_ADDRESS be removed given that we introduced CONTRACT_ADDRESS_V1?
// TODO(#5726): Move the following line to AztecAddress class?
const accountAddressFr = poseidon2Hash([partialAddress, publicKeysHash], GeneratorIndex.CONTRACT_ADDRESS_V1);
const accountAddress = AztecAddress.fromField(accountAddressFr);

// We store the keys in the database
await this.#keys.set(`${accountAddress.toString()}-npk_m`, masterNullifierPublicKey.toBuffer());
await this.#keys.set(`${accountAddress.toString()}-ivpk_m`, masterIncomingViewingPublicKey.toBuffer());
await this.#keys.set(`${accountAddress.toString()}-ovpk_m`, masterOutgoingViewingPublicKey.toBuffer());
await this.#keys.set(`${accountAddress.toString()}-tpk_m`, masterTaggingPublicKey.toBuffer());

// At last, we return the newly derived account address
return Promise.resolve(accountAddress);
}

/**
* Gets the master nullifier public key for a given account.
* @throws If the account does not exist in the key store.
* @param account - The account address for which to retrieve the master nullifier public key.
* @returns The master nullifier public key for the account.
*/
public getMasterNullifierPublicKey(account: AztecAddress): Promise<PublicKey> {
const masterNullifierPublicKeyBuffer = this.#keys.get(`${account.toString()}-npk_m`);
if (!masterNullifierPublicKeyBuffer) {
throw new Error(`Account ${account.toString()} does not exist.`);
}
return Promise.resolve(Point.fromBuffer(masterNullifierPublicKeyBuffer));
}

/**
* Gets the master incoming viewing public key for a given account.
* @throws If the account does not exist in the key store.
* @param account - The account address for which to retrieve the master incoming viewing public key.
* @returns The master incoming viewing public key for the account.
*/
public getMasterIncomingViewingPublicKey(account: AztecAddress): Promise<PublicKey> {
const masterIncomingViewingPublicKeyBuffer = this.#keys.get(`${account.toString()}-ivpk_m`);
if (!masterIncomingViewingPublicKeyBuffer) {
throw new Error(`Account ${account.toString()} does not exist.`);
}
return Promise.resolve(Point.fromBuffer(masterIncomingViewingPublicKeyBuffer));
}

/**
* Retrieves the master outgoing viewing public key.
* @throws If the account does not exist in the key store.
* @param account - The account to retrieve the master outgoing viewing key for.
* @returns A Promise that resolves to the master outgoing viewing key.
*/
public getMasterOutgoingViewingPublicKey(account: AztecAddress): Promise<PublicKey> {
const masterOutgoingViewingPublicKeyBuffer = this.#keys.get(`${account.toString()}-ovpk_m`);
if (!masterOutgoingViewingPublicKeyBuffer) {
throw new Error(`Account ${account.toString()} does not exist.`);
}
return Promise.resolve(Point.fromBuffer(masterOutgoingViewingPublicKeyBuffer));
}

/**
* Retrieves the master tagging public key.
* @throws If the account does not exist in the key store.
* @param account - The account to retrieve the master tagging key for.
* @returns A Promise that resolves to the master tagging key.
*/
public getMasterTaggingPublicKey(account: AztecAddress): Promise<PublicKey> {
const masterTaggingPublicKeyBuffer = this.#keys.get(`${account.toString()}-tpk_m`);
if (!masterTaggingPublicKeyBuffer) {
throw new Error(`Account ${account.toString()} does not exist.`);
}
return Promise.resolve(Point.fromBuffer(masterTaggingPublicKeyBuffer));
}
}
1 change: 1 addition & 0 deletions yarn-project/key-store/src/test_key_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { ConstantKeyPair } from './key_pair.js';
/**
* TestKeyStore is an implementation of the KeyStore interface, used for managing key pairs in a testing environment.
* It should be utilized in testing scenarios where secure key management is not required, and ease-of-use is prioritized.
* TODO(#5627): 💣💣💣
*/
export class TestKeyStore implements KeyStore {
#keys: AztecMap<string, Buffer>;
Expand Down

0 comments on commit 3e44a58

Please sign in to comment.