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(docs): add partial notes doc #8192

Merged
merged 7 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
"hasher",
"headstart",
"herskind",
"homomorphic",
"ierc",
"indexeddb",
"initialise",
Expand Down
49 changes: 43 additions & 6 deletions docs/docs/aztec/concepts/storage/partial_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,43 @@ description: Describes how partial notes are used in Aztec
tags: [notes, storage]
---

Partial notes are a concept that allow users to commit to an encrypted value, and allow a counterparty to update that value without knowing the specific details of the encrypted value. To do this, we can leverage the homomorphic properties of the pedersen commitment.

Partial notes are a concept that allow users to commit to an encrypted value, and allow a counterparty to update that value without knowing the specific details of the encrypted value.
To do this, we leverage the following properties of elliptic curve operations:
critesjosh marked this conversation as resolved.
Show resolved Hide resolved

1. `x_1 * G + x_2 * G` equals `(x_1 + x_2) * G` and
2. `f(x) = x * G` being a one-way function.

Property 1 allows us to be continually adding to a point on elliptic curve and property 2 allows us to pass the point to a public realm without revealing anything about the point preimage.

Before getting to partial notes let's recap what is the flow of standard notes.

## Note lifecycle recap
The standard note flow is as follows:
1. Create a note in your contract,
2. compute the note hash/commitment,
3. emit the note hash,
4. emit the note (note hash preimage) as an encrypted note log,
5. sequencer picks up the transaction, includes it in a block (note hash gets included in a note hash tree) and submits the block on-chain,
6. nodes following the network pick up the new block, update its internal state and if they have accounts attached they try decrypting all the encrypted note logs,
7. if a node succeeds in decrypting a log it stores the note in its database,
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
8. later on when we want to spend a note, a contract obtains it via oracle and stores a note hash read request within the function context (note hash read request contains a newly computed note hash),
9. based on the note and a nullifier secret key a nullifier is computed and emitted,
10. protocol circuits check that the note is a valid note by checking that the note hash read request corresponds to a real note in the note hash tree and that the new nullifier does not yet exist in the nullifier tree,
11. if the conditions in point 10. are satisfied the nullifier is inserted into the nullifier tree and the note is at the end of its life.

Now let's do the same for partial notes.

## Partial notes life cycle
1. Create a partial/unfinished note in a private function of your contract --> partial here means that the values within the note are not yet considered finalized (e.g. `amount` in a `TokenNote`),
2. compute a note hiding point of the partial note using a multi scalar multiplication on an elliptic curve. For `TokenNote` this would be done as `G_amt * amount0 + G_npk * npk_m_hash + G_rnd * randomness + G_slot * slot`,
critesjosh marked this conversation as resolved.
Show resolved Hide resolved
3. pass the note hiding point to a public function,
4. in a public function determine the value you want to add to the note (e.g. adding a value to an amount) and add it to the note hiding point (e.g. `NOTE_HIDING_POINT + G_amt * amount`),
5. get the note hash by finalizing the note hiding point (the note hash is the x coordinate of the point),
6. emit the note hash,
7. manually construct the note in your application and add it to your node (PXE) --> this currently has to be done manually and not automatically via encrypted note logs because we have not yet implemented partial notes delivery (tracked in [issue #8238](https://github.com/AztecProtocol/aztec-packages/issues/8238))
8. from this point on the partial note is equal normal note and hence the rest of the life cycle is the same.

## Use cases
Why is this useful?

Consider the case where a user wants to pay for a transaction fee, using a [fee-payment contract](../../../protocol-specs/gas-and-fees/index.md) and they want to do this privately. They can't be certain what the transaction fee will be because the state of the network will have progressed by the time the transaction is processed by the sequencer, and transaction fees are dynamic. So the user can commit to a value for the transaction fee, publicly post this commitment, the fee payer can update the public commitment, deducting the final cost of the transaction from the commitment and returning the unused value to the user.
Expand All @@ -23,7 +58,7 @@ And the fee payer is:

The idea of committing to a value and allowing a counterparty to update that value without knowing the specific details of the encrypted value is a powerful concept that can be used in many different applications. For example, this could be used for updating timestamp values in private, without revealing the exact timestamp, which could be useful for many defi applications.

## Private Fee Payment Example
### Private Fee Payment Example

Alice wants to use a fee-payment contract for fee abstraction, and wants to use private balances. That is, she wants to pay the FPC (fee-payment contract) some amount in an arbitrary token privately (e.g. bananas), and have the FPC pay the `transaction_fee`.
critesjosh marked this conversation as resolved.
Show resolved Hide resolved

Expand All @@ -47,11 +82,13 @@ $$
P_a' := \text{funded amount}*G_{amount} + \text{alice address}*G_{address} + \text{rand}_a*G_{randomness}
$$

Where did that $\text{rand}_a$ come from? Well from Alice of course. So we can't trust it is random. So we hash it with Alice's address and emit it as a nullifier.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mitch originally though this might be a problem because at the time we did not have nonces in publicly injected note hashes and for this reason if user would use the same randomness twice and the note would end up having the same amount you would inject 2 equal notes into a note hash tree and effectively lose one of those because you could spend only 1 of those (because nullifier would be the same).
A while ago Leila implemented nonces in the public note hashes so this is no longer a problem.


We also need to create a point for the owner of the FPC (whom we call Bob) to receive the transaction fee, which will also need randomness.

So we compute $\text{rand}_b := h(\text{rand}_a, \text{msg_sender})$, and emit that as a nullifier (to make sure it is unique) and as an unencrypted log (so Bob can recreate his refund note after the transaction).
So in the contract we compute $\text{rand}_b := h(\text{rand}_a, \text{msg_sender})$.

:::warning
We need to use different randomness for Bob's note here to avoid potential privacy leak (see [description](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/noir-contracts/contracts/token_contract/src/main.nr#L491) of `setup_refund` function)
:::

$$
P_b' := \text{bob address}*G_{address} + \text{rand}_b*G_{randomness}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ contract PrivateFPC {
// We emit fee payer randomness as nullifier to ensure FPC admin can reconstruct their fee note - note that
// protocol circuits will perform the siloing as was done above and hence the final nullifier will be correct
// fee payer randomness.
// TODO(#8238): Implement proper note delivery
context.push_nullifier(user_randomness);

Token::at(asset).setup_refund(
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/end-to-end/src/e2e_fees/private_refunds.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ describe('e2e_fees/private_refunds', () => {
// should be able to add the note to our PXE. Just calling `pxe.addNote(...)` is enough of a check that the note
// hash was emitted because the endpoint will compute the hash and then it will try to find it in the note hash
// tree. If the note hash is not found in the tree, an error is thrown.
// TODO(#8238): Implement proper note delivery
await t.aliceWallet.addNote(
new ExtendedNote(
aliceRefundNote,
Expand All @@ -112,6 +113,7 @@ describe('e2e_fees/private_refunds', () => {
const bobFeeNote = new Note([new Fr(transactionFee!), bobNpkMHash, bobRandomness]);

// 7. Once again we add the note to PXE which computes the note hash and checks that it is in the note hash tree.
// TODO(#8238): Implement proper note delivery
await t.bobWallet.addNote(
new ExtendedNote(
bobFeeNote,
Expand Down
Loading