-
Notifications
You must be signed in to change notification settings - Fork 292
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes #5607
- Loading branch information
Showing
10 changed files
with
257 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"`, | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters