diff --git a/docs/docs/getting_started/aztecnr-getting-started.md b/docs/docs/getting_started/aztecnr-getting-started.md index 8e2abeff7b7..6caa2634c64 100644 --- a/docs/docs/getting_started/aztecnr-getting-started.md +++ b/docs/docs/getting_started/aztecnr-getting-started.md @@ -92,7 +92,7 @@ Map is a private state variable that functions like a dictionary, relating Field `value_note` -Notes are fundamental to how Aztec manages privacy. A note is a privacy-preserving representation of an amount of tokens associated with an address, while encrypting the amount and owner. In this contract, we are using the `value_note` library. This is a type of note interface for storing a single Field, eg a balance - or, in our case, a counter. +Notes are fundamental to how Aztec manages privacy. A note is a privacy-preserving representation of an amount of tokens associated with a nullifier key (that can be owned by an owner), while encrypting the amount. In this contract, we are using the `value_note` library. This is a type of note interface for storing a single Field, eg a balance - or, in our case, a counter. We are also using `balance_utils` from this import, a useful library that allows us to utilize value notes as if they are simple balances. @@ -128,7 +128,7 @@ The `increment` function works very similarly to the `constructor`, but instead ## Prevent double spending -Because our counters are private, the network can't directly verify if a note was spent or not, which could lead to double-spending. To solve this, we use a nullifier - a unique identifier generated from each spent note and its owner. Although this isn't really an issue in this simple smart contract, Aztec injects a special function called `compute_note_hash_and_nullifier` to determine these values for any given note produced by this contract. +Because our counters are private, the network can't directly verify if a note was spent or not, which could lead to double-spending. To solve this, we use a nullifier - a unique identifier generated from each spent note and its nullifier key. Although this isn't really an issue in this simple smart contract, Aztec injects a special function called `compute_note_hash_and_nullifier` to determine these values for any given note produced by this contract. ## Getting a counter diff --git a/docs/docs/guides/js_apps/rotate_keys.md b/docs/docs/guides/js_apps/rotate_keys.md new file mode 100644 index 00000000000..3fca7522ac3 --- /dev/null +++ b/docs/docs/guides/js_apps/rotate_keys.md @@ -0,0 +1,41 @@ +--- +title: How to Rotate Nullifier Keys +--- + +This guide explains how to rotate nullifer secret and public keys using Aztec.js. To learn more about key rotation, read the [concepts section](../../aztec/concepts/accounts/keys.md#key-rotation). + +## Prerequisites + +You should have a wallet whose keys you want to rotate. You can learn how to create wallets from [this guide](./create_account.md). + +You should also have a PXE initialized. + +## Relevant imports + +You will need to import these from Aztec.js: + +#include_code imports yarn-project/end-to-end/src/e2e_key_rotation.test.ts typescript + +## Create nullifier secret and public key + +`newNskM` = new master nullifier secret key + +`newNpkM` = new master nullifier public key (type `PublicKey`) + +#include_code create_keys yarn-project/end-to-end/src/e2e_key_rotation.test.ts typescript + +## Rotate nullifier secret and public key + +Call `rotateMasterNullifierKey` on the PXE to rotate the secret key. + +#include_code rotateMasterNullifierKey yarn-project/end-to-end/src/e2e_key_rotation.test.ts typescript + +## Rotate public key + +Connect to the key registry contract with your wallet. + +#include_code keyRegistryWithB yarn-project/end-to-end/src/e2e_key_rotation.test.ts typescript + +Then `rotate_npk_m` on the key registry contract to rotate the public key: + +#include_code rotate_npk_m yarn-project/end-to-end/src/e2e_key_rotation.test.ts typescript diff --git a/docs/docs/guides/js_apps/test_contracts.md b/docs/docs/guides/js_apps/test_contracts.md index 53a7272335a..c2ef8cf6c0b 100644 --- a/docs/docs/guides/js_apps/test_contracts.md +++ b/docs/docs/guides/js_apps/test_contracts.md @@ -152,7 +152,7 @@ Private state in the Aztec Network is represented via sets of [private notes](/a #include_code value-note-def noir-projects/aztec-nr/value-note/src/value_note.nr rust -We can query the Private eXecution Environment (PXE) for all notes encrypted for a given user in a contract slot. For this example, we'll get all notes encrypted for the `owner` user that are stored on the token contract address and on the slot we calculated earlier. To calculate the actual balance, we extract the `value` of each note, which is the first element, and sum them up. +We can query the Private eXecution Environment (PXE) for all notes encrypted to a given owner in a contract slot. For this example, we'll get all notes encrypted for the `owner` user that are stored on the token contract address and on the slot we calculated earlier. To calculate the actual balance, we extract the `value` of each note, which is the first element, and sum them up. #include_code private-storage /yarn-project/end-to-end/src/guides/dapp_testing.test.ts typescript diff --git a/docs/docs/guides/smart_contracts/writing_contracts/common_patterns/key_rotation.md b/docs/docs/guides/smart_contracts/writing_contracts/common_patterns/key_rotation.md new file mode 100644 index 00000000000..9fb6fdca481 --- /dev/null +++ b/docs/docs/guides/smart_contracts/writing_contracts/common_patterns/key_rotation.md @@ -0,0 +1,27 @@ +--- +title: Key Rotation +--- + +## Prerequisite reading + +- [Keys](../../../../aztec/concepts/accounts/keys.md) + +## Introduction + +It is possible for users to rotate their keys, which can be helpful if some of their keys are leaked. + +Because of this, notes are associated with their `nullifier key` rather than any sort of 'owner' address. + +It is still possible to nullify the notes with the old nullifier key even after the key rotation. + +## Things to consider + +- 'Owner' is arbitrary - as long as you know the nullifier secret, you can nullify a note +- Consider how key rotation can affect account contracts, eg you can add additional security checks for who or how the key rotation is called + +## Glossary + +- `npk_m_hash`: master nullifying public key hash +- `nsk_app`: app nullifying secret key - the app-specific NSK (learn more about app-scoped keys [here](../../../../aztec/concepts/accounts/keys.md#scoped-keys)) +- `nsk_hash`: nullifying secret key hash +- `ivpk_m`: incoming view public key (master) (learn more about IVPKs [here](../../../../aztec/concepts/accounts/keys.md#keys)) \ No newline at end of file diff --git a/docs/docs/guides/smart_contracts/writing_contracts/how_to_emit_event.md b/docs/docs/guides/smart_contracts/writing_contracts/how_to_emit_event.md index bd8f362bc8c..4e734d4b8c1 100644 --- a/docs/docs/guides/smart_contracts/writing_contracts/how_to_emit_event.md +++ b/docs/docs/guides/smart_contracts/writing_contracts/how_to_emit_event.md @@ -21,7 +21,7 @@ Encrypted events can only be emitted by private functions and are encrypted usin For this reason it is necessary to register a recipient in the Private Execution Environment (PXE) before encrypting the events for them. First we need to get a hold of recipient's [complete address](#complete-address). -Bellow are some ways how we could instantiate it after getting the information in a string form from a recipient: +Below are some ways how we could instantiate it after getting the information in a string form from a recipient: #include_code instantiate-complete-address /yarn-project/circuits.js/src/structs/complete_address.test.ts rust diff --git a/docs/docs/guides/smart_contracts/writing_contracts/how_to_prove_history.md b/docs/docs/guides/smart_contracts/writing_contracts/how_to_prove_history.md index c6fbd2c2e16..619cd33a8a0 100644 --- a/docs/docs/guides/smart_contracts/writing_contracts/how_to_prove_history.md +++ b/docs/docs/guides/smart_contracts/writing_contracts/how_to_prove_history.md @@ -20,7 +20,7 @@ The history library allows you to prove any of the following at a given block he Using this library, you can check that specific notes or nullifiers were part of Aztec network state at specific blocks. This can be useful for things such as: - Verifying a minimum timestamp from a private context -- Checking eligibility based on historical events (e.g. for an airdrop by proving that you owned a note) +- Checking eligibility based on historical events (e.g. for an airdrop by proving that you knew the nullifier key for a note) - Verifying historic ownership / relinquishing of assets - Proving existence of a value in public data tree at a given contract slot - Proving that a contract was deployed in a given block with some parameters diff --git a/docs/docs/guides/smart_contracts/writing_contracts/storage/notes.md b/docs/docs/guides/smart_contracts/writing_contracts/storage/notes.md index 41f93939383..097ca2e12a3 100644 --- a/docs/docs/guides/smart_contracts/writing_contracts/storage/notes.md +++ b/docs/docs/guides/smart_contracts/writing_contracts/storage/notes.md @@ -106,7 +106,7 @@ As a convenience, the outer [note/utils.nr](https://github.com/AztecProtocol/azt Serialization/deserialization of content is used to convert between the Note's variables and a generic array of Field elements. The Field type is understood and used by lower level crypographic libraries. This is analogous to the encoding/decoding between variables and bytes in solidity. -For example in ValueNote, the `serialize_content` function simply returns: the value, owner address (as a field) and the note randomness; as an array of Field elements. +For example in ValueNote, the `serialize_content` function simply returns: the value, nullifying public key hash (as a field) and the note randomness; as an array of Field elements. ### Value as a sum of Notes We recall that multiple notes are associated with a "slot" (or ID), and so the value of a numerical note (like ValueNote) is the sum of each note's value. diff --git a/docs/docs/migration_notes.md b/docs/docs/migration_notes.md index f607abad860..ee69840c376 100644 --- a/docs/docs/migration_notes.md +++ b/docs/docs/migration_notes.md @@ -57,8 +57,20 @@ struct TokenNote { } ``` +Creating a token note and adding it to storage now looks like this: + +```diff +- let mut note = ValueNote::new(new_value, owner); +- storage.a_private_value.insert(&mut note, true); ++ let owner_npk_m_hash = get_npk_m_hash(&mut context, owner); ++ let owner_ivpk_m = get_ivpk_m(&mut context, owner); ++ let mut note = ValueNote::new(new_value, owner_npk_m_hash); ++ storage.a_private_value.insert(&mut note, true, owner_ivpk_m); +``` + Computing the nullifier similarly changes to use this master nullifying public key hash. + ## 0.40.0 ### [Aztec.nr] Debug logging diff --git a/docs/docs/reference/smart_contract_reference/storage/private_state.md b/docs/docs/reference/smart_contract_reference/storage/private_state.md index bed799ff641..256543f3493 100644 --- a/docs/docs/reference/smart_contract_reference/storage/private_state.md +++ b/docs/docs/reference/smart_contract_reference/storage/private_state.md @@ -326,9 +326,9 @@ The first value of `.select` and `.sort` is the index of a field in a note type. #include_code state_vars-CardNote /noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr rust -The indices are: 0 for `points`, 1 for `secret`, and 2 for `owner`. +The indices are: 0 for `points`, 1 for `randomness`, and 2 for `npk_m_hash`. -In the example, `.select(2, account_address, Option::none())` matches the 2nd field of `CardNote`, which is `owner`, and returns the cards whose `owner` field equals `account_address`, equality is the comparator used because because including no comparator (the third argument) defaults to using the equality comparator. The current possible values of Comparator are specified in the Note Getter Options implementation linked above. +In the example, `.select(2, account_address, Option::none())` matches the 2nd field of `CardNote`, which is `npk_m_hash`, and returns the cards whose `npk_m_hash` field equals `account_npk_m_hash`, equality is the comparator used because because including no comparator (the third argument) defaults to using the equality comparator. The current possible values of Comparator are specified in the Note Getter Options implementation linked above. `.sort(0, SortOrder.DESC)` sorts the 0th field of `CardNote`, which is `points`, in descending order. diff --git a/docs/docs/tutorials/contract_tutorials/private_voting_contract.md b/docs/docs/tutorials/contract_tutorials/private_voting_contract.md index 5815d037c26..70709f44de4 100644 --- a/docs/docs/tutorials/contract_tutorials/private_voting_contract.md +++ b/docs/docs/tutorials/contract_tutorials/private_voting_contract.md @@ -44,7 +44,7 @@ Your file structure should look something like this: The file `main.nr` will soon turn into our smart contract! -We will need the Aztec library to create this contract. In your `Nargo.toml` you should see `[dependencies]` - paste this bellow it. +We will need the Aztec library to create this contract. In your `Nargo.toml` you should see `[dependencies]` - paste this below it. ```toml [dependencies] @@ -126,6 +126,10 @@ The first thing we do here is assert that the vote has not ended. The code after the assertion will only run if the assertion is true. In this snippet, we read the current vote tally at the voteId, add 1 to it, and write this new number to the voteId. The `Field` element allows us to use `+` to add to an integer. +:::danger +Note that due to [key rotation](../../aztec/concepts/accounts/keys.md#key-rotation), it would be possible for a user to rotate their nullifier secret key and be able to vote again. Refer to [common patterns](../../guides/smart_contracts/writing_contracts/common_patterns/key_rotation.md) for more information +::: + ## Getting the number of votes We will create a function that anyone can call that will return the number of votes at a given vote Id. Paste this in your contract: diff --git a/docs/docs/tutorials/contract_tutorials/token_contract.md b/docs/docs/tutorials/contract_tutorials/token_contract.md index cac8dc0a4d1..3df7b7a7db4 100644 --- a/docs/docs/tutorials/contract_tutorials/token_contract.md +++ b/docs/docs/tutorials/contract_tutorials/token_contract.md @@ -215,7 +215,7 @@ For more detail on execution contexts, see [Contract Communication](/aztec/conce We are also importing types from a `types.nr` file, which imports types from the `types` folder. You can view them [here](https://github.com/AztecProtocol/aztec-packages/tree/#include_aztec_version/noir-projects/noir-contracts/contracts/token_contract/src). -The main thing to note from this types folder is the `TransparentNote` definition. This defines how the contract moves value from the public domain into the private domain. It is similar to the `value_note` that we imported, but with some modifications namely, instead of a defined `owner`, it allows anyone that can produce the pre-image to the stored `secret_hash` to spend the note. +The main thing to note from this types folder is the `TransparentNote` definition. This defines how the contract moves value from the public domain into the private domain. It is similar to the `value_note` that we imported, but with some modifications namely, instead of a defined nullifier key, it allows anyone that can produce the pre-image to the stored `secret_hash` to spend the note. ### Note on private state @@ -233,7 +233,7 @@ Reading through the storage variables: - `admin` an Aztec address stored in public state. - `minters` is a mapping of Aztec addresses in public state. This will store whether an account is an approved minter on the contract. -- `balances` is a mapping of private balances. Private balances are stored in a `PrivateSet` of `ValueNote`s. The balance is the sum of all of an account's `ValueNote`s. +- `balances` is a mapping of private balances. Private balances are stored in a `PrivateSet` of `TokenNote`s. The balance is the sum of all of an account's `TokenNote`s. - `total_supply` is an unsigned integer (max 128 bit value) stored in public state and represents the total number of tokens minted. - `pending_shields` is a `PrivateSet` of `TransparentNote`s stored in private state. What is stored publicly is a set of commitments to `TransparentNote`s. - `public_balances` is a mapping of Aztec addresses in public state and represents the publicly viewable balances of accounts. @@ -337,7 +337,7 @@ Storage is referenced as `storage.variable`. #### `redeem_shield` -This private function enables an account to move tokens from a `TransparentNote` in the `pending_shields` mapping to any Aztec account as a `ValueNote` in private `balances`. +This private function enables an account to move tokens from a `TransparentNote` in the `pending_shields` mapping to a `TokenNote` in private `balances`. The `TokenNote` will be associated with a nullifier key, so any account that knows this key can spend this note. Going through the function logic, first the `secret_hash` is generated from the given secret. This ensures that only the entity possessing the secret can use it to redeem the note. Following this, a `TransparentNote` is retrieved from the set, using the provided amount and secret. The note is subsequently removed from the set, allowing it to be redeemed only once. The recipient's private balance is then increased using the `increment` helper function from the `value_note` [library](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/value-note/src/utils.nr). @@ -347,7 +347,7 @@ The function returns `1` to indicate successful execution. #### `unshield` -This private function enables un-shielding of private `ValueNote`s stored in `balances` to any Aztec account's `public_balance`. +This private function enables un-shielding of private `TokenNote`s stored in `balances` to any Aztec account's `public_balance`. After initializing storage, the function checks that the `msg_sender` is authorized to spend tokens. See [the Authorizing token spends section](#authorizing-token-spends) above for more detail--the only difference being that `assert_valid_message_for` is modified to work specifically in the private context. After the authorization check, the sender's private balance is decreased using the `decrement` helper function for the `value_note` library. Then it stages a public function call on this contract ([`_increase_public_balance`](#_increase_public_balance)) to be executed in the [public execution phase](#execution-contexts) of transaction execution. `_increase_public_balance` is marked as an `internal` function, so can only be called by this token contract. @@ -411,7 +411,7 @@ A getter function for checking the token `total_supply`. #### `balance_of_private` -A getter function for checking the private balance of the provided Aztec account. Note that the [Private Execution Environment (PXE)](https://github.com/AztecProtocol/aztec-packages/tree/#include_aztec_version/yarn-project/pxe) must have access to the `owner`s decryption keys in order to decrypt their notes. +A getter function for checking the private balance of the provided Aztec account. Note that the [Private Execution Environment (PXE)](https://github.com/AztecProtocol/aztec-packages/tree/#include_aztec_version/yarn-project/pxe) must have `ivsk_app` ([app-specific secret key](../../aztec/concepts/accounts/keys.md#scoped-keys)) in order to decrypt the notes. #include_code balance_of_private /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust diff --git a/yarn-project/end-to-end/src/e2e_key_rotation.test.ts b/yarn-project/end-to-end/src/e2e_key_rotation.test.ts index 06ec9e71473..ba7aad8612c 100644 --- a/yarn-project/end-to-end/src/e2e_key_rotation.test.ts +++ b/yarn-project/end-to-end/src/e2e_key_rotation.test.ts @@ -13,8 +13,11 @@ import { computeSecretHash, retryUntil, } from '@aztec/aztec.js'; +// docs:start:imports import { type PublicKey, derivePublicKeyFromSecretKey } from '@aztec/circuits.js'; -import { KeyRegistryContract, TestContract, TokenContract } from '@aztec/noir-contracts.js'; +import { KeyRegistryContract } from '@aztec/noir-contracts.js'; +// docs:end:imports +import { TestContract, TokenContract } from '@aztec/noir-contracts.js'; import { getCanonicalKeyRegistryAddress } from '@aztec/protocol-contracts/key-registry'; import { jest } from '@jest/globals'; @@ -56,10 +59,10 @@ describe('e2e_key_rotation', () => { } = await setup(1)); ({ pxe: pxeB, teardown: teardownB } = await setupPXEService(aztecNode, {}, undefined, true)); - + // docs:start:keyRegistryWithB [walletB] = await createAccounts(pxeB, 1); keyRegistryWithB = await KeyRegistryContract.at(getCanonicalKeyRegistryAddress(), walletB); - + // docs:end:keyRegistryWithB // We deploy test and token contracts testContract = await TestContract.deploy(walletA).send().deployed(); const tokenInstance = await deployTokenContract(initialBalance, walletA.getAddress(), pxeA); @@ -174,11 +177,16 @@ describe('e2e_key_rotation', () => { // 3. Rotates B key let newNpkM: PublicKey; { + // docs:start:create_keys const newNskM = Fq.random(); newNpkM = derivePublicKeyFromSecretKey(newNskM); + // docs:end:create_keys + // docs:start:rotateMasterNullifierKey await pxeB.rotateMasterNullifierKey(walletB.getAddress(), newNskM); - + // docs:end:rotateMasterNullifierKey + // docs:start:rotate_npk_m await keyRegistryWithB.methods.rotate_npk_m(walletB.getAddress(), newNpkM, 0).send().wait(); + // docs:end:rotate_npk_m await crossDelay(); }