Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new key store #5653

Merged
merged 7 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading