From b44e13ab58c3a483c9be9a2ccd84323f6c399593 Mon Sep 17 00:00:00 2001 From: LHerskind Date: Mon, 18 Dec 2023 13:38:20 +0000 Subject: [PATCH 1/5] docs: extend state --- yellow-paper/docs/state/index.md | 28 +++++++++++++++++++++++---- yellow-paper/docs/state/tree_impls.md | 4 ++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/yellow-paper/docs/state/index.md b/yellow-paper/docs/state/index.md index 9287f5c9007c..18639baf0ed8 100644 --- a/yellow-paper/docs/state/index.md +++ b/yellow-paper/docs/state/index.md @@ -4,12 +4,32 @@ sidebar_position: 10 --- # State +The global state is the set of data that makes up Aztec - it is persisted and only updates when new blocks are added to the chain. The state consist of multiple different categories of data with varying requirements. Common for all of the categories is that they need strong integrity guarantees and efficient membership proofs. Like for most other blockchains, this can be enforced by structuring the data as leafs in Merkle trees. However, unlike most other blockchains, our contract state cannot use a Merkle tree as a key-value store for each contracts data. The reason being that we have both private and public state. While public state could be stored in a key-value tree, private state cannot - as doing so would leak information whenever the private state is updated, even if encrypted. -Global state in the Aztec Network is represented by a set of Merkle trees: the [Note Hash tree](./note_hash_tree.md), [Nullifier tree](./nullifier_tree.md), and [Public Data tree](./public_data_tree.md) reflect the latest state of the chain, while the L1 to L2 message tree allows for [cross-chain communication](../contracts/#l2-outbox) and the [Archive](./archive.md) allows for historical state access. +To work around this, we use a two-tree approach for state that can be used privately. Namely we have one (or more) tree(s) where data is added to (sometimes called a data tree), and a second tree where we "nullify" or mark the data as deleted. This allows us to "update" a leaf by adding a new leaf to the date trees, and add the nullifier of the old leaf to the second tree (the nullifier tree). That way we can show that the new leaf is the "active" one, and that the old leaf is "deleted". -Merkle trees are either -- [append-only](./tree_impls.md#append-only-merkle-trees), for data where we only require inclusion proofs or -- [indexed](./tree_impls.md#indexed-merkle-trees) for storing data that requires proofs of non-membership. +When dealing with private data, only the hash of the data is stored in the leaf in our data tree and we must setup a derivation mechanism that ensure nullifiers can be computed deterministically from the pre-image (the data that was hashed). This way, no-one can tell what data is stored in the leaf (unless they already know it), and therefore won't be able to derive the nullifier and tell if the leaf is active or deleted. Whenever a user want to "delete" or "update" data, they can prove that they know the data and that it is still active (its nullifier is not in the nullifier tree). +Simultaneously, this ensures that a user cannot "double-spend" data by nullifying it again, because it would fail the non-membership proof (this relies on the nullifier being deterministic from the data for security). + +This ability to efficiently prove non-membership is one of the extra requirements we have for some parts of out state. To support the requirements most efficiently, we use two families of Merkle trees: +- The [Append-only Merkle tree](./tree_impls.md#append-only-merkle-trees), which supports efficient membership proofs, +- The [Indexed Merkle tree](./tree_impls.md#indexed-merkle-trees), which supports efficient membership and non-membership proofs but increase cost of adding leafs. + +:::warning **Discussion Point**: +"Indexed merkle tree" is not a very telling name, our normal merkle trees are indexed too. I propose we call them "successor merkle trees" instead since each leaf refer to their successor. The low-nullifiers are are also the predecessor of the nullifier you are inserting, so it seems nice that you prove that the nullifier you are inserting has a predecessor and that the predecessors successor would also be the successor of the nullifier you are inserting. +::: + +Another important aspect of state in Aztec is *when* it can be accessed. In most blockchains, state is accessed at the head of the chain, and the state is updated as new blocks are added. However, since private execution relies on proofs generated by the user this would be very impractical - one users transaction would invalidate everyone elses. Instead, private execution relies on historical state, where the user can prove that the state they are accessing is valid, and then emit nullifiers (as mentioned above) to ensure that the state accessed was up to date. The nullifiers being emitted addresses the issue of double-spending, so as long as users are spending each others notes there will be no collisions. + +Below is a short description of the state catagories (trees) and why they have the type they have. +- [**Note Hashes**](./note_hash_tree.md): A set of hashes (commitments) of the individual blobs of contract data (we call these blobs of data notes). New notes can be created and their hashes inserted through contract execution. We need to support efficient membership proofs as any read will require one to prove validity. The set is represented as an [Append-only Merkle tree](./tree_impls.md#append-only-merkle-trees), storing the note hashes as leafs. +- [**Nullifiers**](./nullifier_tree.md): A set of nullifiers for notes that have been spent. We need to support efficient non-membership proofs since we need to check that a note has not been spent before it can be used. The set is represented as an [Indexed Merkle tree](./tree_impls.md#indexed-merkle-trees). +- [**Public Data**](./public_data_tree.md): The key-value store for public contract state. We need to support both efficient membership and non-membership proofs! We require both, since the tree is "empty" from the start. Meaning that if the key is not already stored (non-membership), we need to insert it, and if it is already stored (membership) we need to just update the value. +- **Contracts**: The set of deployed contracts. We need to support efficient membership proofs as we need to check that a contract is deployed before we can interact with it. The set is represented as an [Append-only Merkle tree](./tree_impls.md#append-only-merkle-trees). +- **L1 to L2 Messages**: The set of messages sent from L1 to L2. The set itself only needs to support efficient membership proofs, so we can ensure that the message was correctly sent from L1. However, it utilizes the Nullifier tree from above to ensure that the message cannot be processed twice. The set is represented as an [Append-only Merkle tree](./tree_impls.md#append-only-merkle-trees). +- [**Archive**](./archive.md): The set of block headers that have been processed. We need to support efficient membership proofs as this is used in private execution to get the roots of the other trees. The set is represented as an [Append-only Merkle tree](./tree_impls.md#append-only-merkle-trees). + +To recall, the global state in Aztec is represented by a set of Merkle trees: the [Note Hash tree](./note_hash_tree.md), [Nullifier tree](./nullifier_tree.md), and [Public Data tree](./public_data_tree.md) reflect the latest state of the chain, while the L1 to L2 message tree allows for [cross-chain communication](../contracts/#l2-outbox) and the [Archive](./archive.md) allows for historical state access. ```mermaid classDiagram diff --git a/yellow-paper/docs/state/tree_impls.md b/yellow-paper/docs/state/tree_impls.md index 0a698eb2de4d..ce0b7cd91acd 100644 --- a/yellow-paper/docs/state/tree_impls.md +++ b/yellow-paper/docs/state/tree_impls.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 1 +--- + # Tree implementations Aztec relies on two Merkle tree implementations in the protocol: append-only and indexed Merkle trees. From bf420f06af040c057e6eb53c485931fafd631d45 Mon Sep 17 00:00:00 2001 From: Lasse Herskind <16536249+LHerskind@users.noreply.github.com> Date: Thu, 21 Dec 2023 16:45:37 +0100 Subject: [PATCH 2/5] Apply suggestions from code review Co-authored-by: esau <152162806+sklppy88@users.noreply.github.com> --- yellow-paper/docs/state/index.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yellow-paper/docs/state/index.md b/yellow-paper/docs/state/index.md index 18639baf0ed8..52e795db3605 100644 --- a/yellow-paper/docs/state/index.md +++ b/yellow-paper/docs/state/index.md @@ -4,19 +4,19 @@ sidebar_position: 10 --- # State -The global state is the set of data that makes up Aztec - it is persisted and only updates when new blocks are added to the chain. The state consist of multiple different categories of data with varying requirements. Common for all of the categories is that they need strong integrity guarantees and efficient membership proofs. Like for most other blockchains, this can be enforced by structuring the data as leafs in Merkle trees. However, unlike most other blockchains, our contract state cannot use a Merkle tree as a key-value store for each contracts data. The reason being that we have both private and public state. While public state could be stored in a key-value tree, private state cannot - as doing so would leak information whenever the private state is updated, even if encrypted. +The global state is the set of data that makes up Aztec - it is persisted and only updates when new blocks are added to the chain. The state consists of multiple different categories of data with varying requirements. Common for all of the categories is that they need strong integrity guarantees and efficient membership proofs. Like for most other blockchains, this can be enforced by structuring the data as leafs in Merkle trees. However, unlike most other blockchains, our contract state cannot use a Merkle tree as a key-value store for each contracts data. The reason being that we have both private and public state. While public state could be stored in a key-value tree, private state cannot - as doing so would leak information whenever the private state is updated, even if encrypted. To work around this, we use a two-tree approach for state that can be used privately. Namely we have one (or more) tree(s) where data is added to (sometimes called a data tree), and a second tree where we "nullify" or mark the data as deleted. This allows us to "update" a leaf by adding a new leaf to the date trees, and add the nullifier of the old leaf to the second tree (the nullifier tree). That way we can show that the new leaf is the "active" one, and that the old leaf is "deleted". -When dealing with private data, only the hash of the data is stored in the leaf in our data tree and we must setup a derivation mechanism that ensure nullifiers can be computed deterministically from the pre-image (the data that was hashed). This way, no-one can tell what data is stored in the leaf (unless they already know it), and therefore won't be able to derive the nullifier and tell if the leaf is active or deleted. Whenever a user want to "delete" or "update" data, they can prove that they know the data and that it is still active (its nullifier is not in the nullifier tree). +When dealing with private data, only the hash of the data is stored in the leaf in our data tree and we must setup a derivation mechanism that ensures nullifiers can be computed deterministically from the pre-image (the data that was hashed). This way, no-one can tell what data is stored in the leaf (unless they already know it), and therefore won't be able to derive the nullifier and tell if the leaf is active or deleted. Whenever a user wants to "delete" or "update" data, they can prove that they know the data and that it is still active (its nullifier is not in the nullifier tree). Simultaneously, this ensures that a user cannot "double-spend" data by nullifying it again, because it would fail the non-membership proof (this relies on the nullifier being deterministic from the data for security). -This ability to efficiently prove non-membership is one of the extra requirements we have for some parts of out state. To support the requirements most efficiently, we use two families of Merkle trees: +This ability to efficiently prove non-membership is one of the extra requirements we have for some parts of our state. To support the requirements most efficiently, we use two families of Merkle trees: - The [Append-only Merkle tree](./tree_impls.md#append-only-merkle-trees), which supports efficient membership proofs, - The [Indexed Merkle tree](./tree_impls.md#indexed-merkle-trees), which supports efficient membership and non-membership proofs but increase cost of adding leafs. :::warning **Discussion Point**: -"Indexed merkle tree" is not a very telling name, our normal merkle trees are indexed too. I propose we call them "successor merkle trees" instead since each leaf refer to their successor. The low-nullifiers are are also the predecessor of the nullifier you are inserting, so it seems nice that you prove that the nullifier you are inserting has a predecessor and that the predecessors successor would also be the successor of the nullifier you are inserting. +"Indexed merkle tree" is not a very telling name, as our normal merkle trees are indexed too. I propose we call them "successor merkle trees" instead since each leaf refers to its successor. The low-nullifiers are also the predecessor of the nullifier you are inserting, so it seems nice that you prove that the nullifier you are inserting has a predecessor and that the predecessors successor would also be the successor of the nullifier you are inserting. ::: Another important aspect of state in Aztec is *when* it can be accessed. In most blockchains, state is accessed at the head of the chain, and the state is updated as new blocks are added. However, since private execution relies on proofs generated by the user this would be very impractical - one users transaction would invalidate everyone elses. Instead, private execution relies on historical state, where the user can prove that the state they are accessing is valid, and then emit nullifiers (as mentioned above) to ensure that the state accessed was up to date. The nullifiers being emitted addresses the issue of double-spending, so as long as users are spending each others notes there will be no collisions. From a32054e042d380de2c252497dd6e93c851337535 Mon Sep 17 00:00:00 2001 From: LHerskind Date: Thu, 21 Dec 2023 16:50:04 +0000 Subject: [PATCH 3/5] docs: add private state access section --- yellow-paper/docs/state/index.md | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/yellow-paper/docs/state/index.md b/yellow-paper/docs/state/index.md index 52e795db3605..416da2bbe9b4 100644 --- a/yellow-paper/docs/state/index.md +++ b/yellow-paper/docs/state/index.md @@ -8,18 +8,29 @@ The global state is the set of data that makes up Aztec - it is persisted and on To work around this, we use a two-tree approach for state that can be used privately. Namely we have one (or more) tree(s) where data is added to (sometimes called a data tree), and a second tree where we "nullify" or mark the data as deleted. This allows us to "update" a leaf by adding a new leaf to the date trees, and add the nullifier of the old leaf to the second tree (the nullifier tree). That way we can show that the new leaf is the "active" one, and that the old leaf is "deleted". -When dealing with private data, only the hash of the data is stored in the leaf in our data tree and we must setup a derivation mechanism that ensures nullifiers can be computed deterministically from the pre-image (the data that was hashed). This way, no-one can tell what data is stored in the leaf (unless they already know it), and therefore won't be able to derive the nullifier and tell if the leaf is active or deleted. Whenever a user wants to "delete" or "update" data, they can prove that they know the data and that it is still active (its nullifier is not in the nullifier tree). -Simultaneously, this ensures that a user cannot "double-spend" data by nullifying it again, because it would fail the non-membership proof (this relies on the nullifier being deterministic from the data for security). +When dealing with private data, only the hash of the data is stored in the leaf in our data tree and we must setup a derivation mechanism that ensures nullifiers can be computed deterministically from the pre-image (the data that was hashed). This way, no-one can tell what data is stored in the leaf (unless they already know it), and therefore won't be able to derive the nullifier and tell if the leaf is active or deleted. -This ability to efficiently prove non-membership is one of the extra requirements we have for some parts of our state. To support the requirements most efficiently, we use two families of Merkle trees: +Convincing someone that a piece of data is active can then be done by proving its membership in the data tree, and that it is not deleted by proving its non-membership in the nullifier tree. This ability to efficiently prove non-membership is one of the extra requirements we have for some parts of our state. To support the requirements most efficiently, we use two families of Merkle trees: - The [Append-only Merkle tree](./tree_impls.md#append-only-merkle-trees), which supports efficient membership proofs, - The [Indexed Merkle tree](./tree_impls.md#indexed-merkle-trees), which supports efficient membership and non-membership proofs but increase cost of adding leafs. -:::warning **Discussion Point**: -"Indexed merkle tree" is not a very telling name, as our normal merkle trees are indexed too. I propose we call them "successor merkle trees" instead since each leaf refers to its successor. The low-nullifiers are also the predecessor of the nullifier you are inserting, so it seems nice that you prove that the nullifier you are inserting has a predecessor and that the predecessors successor would also be the successor of the nullifier you are inserting. -::: +### Private State Access + +Whenever a user is to read of use data, they must then convince the "rollup" that the their data is active. As mentioned above, they must prove that the data is in the data tree (membership proof) and that it is still active (non-membership proof). However, there are nuances to this approach! + +One important aspect is *when* state can be accessed. In most blockchains, state is always accessed at the head of the chain and changes are only made by the sequencer as new blocks are added. However, since private execution relies on proofs generated by the user this would be very impractical - one users transaction would invalidate everyone elses. + +While proving inclusion in the data tree can be done using historical state, the non-membership in the nullifier tree cannot. Membership can be proven since we are using a append-only tree, so anything that was there in the past, must still be in the tree now. However, this don't work for the non-membership proof as it would only prove that the data was active at the time the proof was generated, not that it is still active! Which would allow a user to create multiple transactions spending the same data and then send them all at once - double-spending! + +To solve this, we need to perform the non-membership proofs at the head of the state - which only the sequencer knows! Meaning that instead of the user proving that the data is not in the nullifier tree, they provide the nullifiers as part of their transaction, and the sequencer is then to prove non-membership **AND** insert them into the nullifier tree. This way, if multiple transactions are sent with the same nullifier, only one of them will be included in the block as the others will fail the non-membership proof. + +**Why does it need to insert the nullifier if I'm reading?** Why can't it just prove that the nullifier is not in the tree? Well, this is a privacy concern. If you just make the non-membership proof, you are leaking that you are reading data for nullifier $x$, so if you read that data again at a later point, $x$ is seen by the sequencer again and it can infer that it is the same actor reading data. By emitting the nullifier the read is indistinguishable from a write, and the sequencer cannot tell what is happening and there will be no repetitions. -Another important aspect of state in Aztec is *when* it can be accessed. In most blockchains, state is accessed at the head of the chain, and the state is updated as new blocks are added. However, since private execution relies on proofs generated by the user this would be very impractical - one users transaction would invalidate everyone elses. Instead, private execution relies on historical state, where the user can prove that the state they are accessing is valid, and then emit nullifiers (as mentioned above) to ensure that the state accessed was up to date. The nullifiers being emitted addresses the issue of double-spending, so as long as users are spending each others notes there will be no collisions. +This however also means, that whenever data is only to be read, a new note with the same data must be inserted into the data tree. This note have new randomness, so anyone watching will be unable to tell if it is the same data inserted, or if it is new data. This is good for privacy, but comes at an additional cost. + +A side-effect of this also means that if multiple users are "sharing" their notes, any one of them reading the data will cause the note to be updated, so pending transaction that require the note will fail. + +## State Categories Below is a short description of the state catagories (trees) and why they have the type they have. - [**Note Hashes**](./note_hash_tree.md): A set of hashes (commitments) of the individual blobs of contract data (we call these blobs of data notes). New notes can be created and their hashes inserted through contract execution. We need to support efficient membership proofs as any read will require one to prove validity. The set is represented as an [Append-only Merkle tree](./tree_impls.md#append-only-merkle-trees), storing the note hashes as leafs. @@ -175,3 +186,9 @@ State *-- ContractTree : contract_tree import DocCardList from '@theme/DocCardList'; + +--- + +:::warning **Discussion Point**: +"Indexed merkle tree" is not a very telling name, as our normal merkle trees are indexed too. I propose we call them "successor merkle trees" instead since each leaf refers to its successor. The low-nullifiers are also the predecessor of the nullifier you are inserting, so it seems nice that you prove that the nullifier you are inserting has a predecessor and that the predecessors successor would also be the successor of the nullifier you are inserting. +::: \ No newline at end of file From d77ddee99e9a484b96cca9eae4ad4829c0c4ec61 Mon Sep 17 00:00:00 2001 From: Lasse Herskind <16536249+LHerskind@users.noreply.github.com> Date: Fri, 22 Dec 2023 11:14:02 +0100 Subject: [PATCH 4/5] Apply suggestions from code review Co-authored-by: brunny-eth <96834997+brunny-eth@users.noreply.github.com> Co-authored-by: esau <152162806+sklppy88@users.noreply.github.com> --- yellow-paper/docs/state/index.md | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/yellow-paper/docs/state/index.md b/yellow-paper/docs/state/index.md index 416da2bbe9b4..0736226bf96e 100644 --- a/yellow-paper/docs/state/index.md +++ b/yellow-paper/docs/state/index.md @@ -4,7 +4,13 @@ sidebar_position: 10 --- # State -The global state is the set of data that makes up Aztec - it is persisted and only updates when new blocks are added to the chain. The state consists of multiple different categories of data with varying requirements. Common for all of the categories is that they need strong integrity guarantees and efficient membership proofs. Like for most other blockchains, this can be enforced by structuring the data as leafs in Merkle trees. However, unlike most other blockchains, our contract state cannot use a Merkle tree as a key-value store for each contracts data. The reason being that we have both private and public state. While public state could be stored in a key-value tree, private state cannot - as doing so would leak information whenever the private state is updated, even if encrypted. + +The global state is the set of data that makes up Aztec - it is persistent and only updates when new blocks are added to the chain. + +The state consists of multiple different categories of data with varying requirements. What all of the categories have in common is that they need strong integrity guarantees and efficient membership proofs. Like most other blockchains, this can be enforced by structuring the data as leafs in Merkle trees. + +However, unlike most other blockchains, our contract state cannot use a Merkle tree as a key-value store for each contracts data. The reason for this is that we have both private and public state; while public state could be stored in a key-value tree, private state cannot, as doing so would leak information whenever the private state is updated, even if encrypted. + To work around this, we use a two-tree approach for state that can be used privately. Namely we have one (or more) tree(s) where data is added to (sometimes called a data tree), and a second tree where we "nullify" or mark the data as deleted. This allows us to "update" a leaf by adding a new leaf to the date trees, and add the nullifier of the old leaf to the second tree (the nullifier tree). That way we can show that the new leaf is the "active" one, and that the old leaf is "deleted". @@ -12,17 +18,25 @@ When dealing with private data, only the hash of the data is stored in the leaf Convincing someone that a piece of data is active can then be done by proving its membership in the data tree, and that it is not deleted by proving its non-membership in the nullifier tree. This ability to efficiently prove non-membership is one of the extra requirements we have for some parts of our state. To support the requirements most efficiently, we use two families of Merkle trees: - The [Append-only Merkle tree](./tree_impls.md#append-only-merkle-trees), which supports efficient membership proofs, -- The [Indexed Merkle tree](./tree_impls.md#indexed-merkle-trees), which supports efficient membership and non-membership proofs but increase cost of adding leafs. +- The [Indexed Merkle tree](./tree_impls.md#indexed-merkle-trees), which supports efficient membership and non-membership proofs but increases the cost of adding leafs. ### Private State Access Whenever a user is to read of use data, they must then convince the "rollup" that the their data is active. As mentioned above, they must prove that the data is in the data tree (membership proof) and that it is still active (non-membership proof). However, there are nuances to this approach! -One important aspect is *when* state can be accessed. In most blockchains, state is always accessed at the head of the chain and changes are only made by the sequencer as new blocks are added. However, since private execution relies on proofs generated by the user this would be very impractical - one users transaction would invalidate everyone elses. +One important aspect to consider is *when* state can be accessed. In most blockchains, state is always accessed at the head of the chain and changes are only made by the sequencer as new blocks are added. + +However, since private execution relies on proofs generated by the user, this would be very impractical - one users transaction could invalidate everyone elses. + + +While proving inclusion in the data tree can be done using historical state, the non-membership proof in the nullifier tree cannot. + +Membership can be proven using historical state because we are using an append-only tree, so anything that was there in the past must still be in the append-only tree now. + +However, this doesn't work for the non-membership proof, as it can only prove that the data was active at the time the proof was generated, not that it is still active today! This would allow a user to create multiple transactions spending the same data and then send those transactions all at once, creating a double spend. -While proving inclusion in the data tree can be done using historical state, the non-membership in the nullifier tree cannot. Membership can be proven since we are using a append-only tree, so anything that was there in the past, must still be in the tree now. However, this don't work for the non-membership proof as it would only prove that the data was active at the time the proof was generated, not that it is still active! Which would allow a user to create multiple transactions spending the same data and then send them all at once - double-spending! -To solve this, we need to perform the non-membership proofs at the head of the state - which only the sequencer knows! Meaning that instead of the user proving that the data is not in the nullifier tree, they provide the nullifiers as part of their transaction, and the sequencer is then to prove non-membership **AND** insert them into the nullifier tree. This way, if multiple transactions are sent with the same nullifier, only one of them will be included in the block as the others will fail the non-membership proof. +To solve this, we need to perform the non-membership proofs at the head of the chain, which only the sequencer knows! This means that instead of the user proving that the data is not in the nullifier tree, they provide the nullifiers as part of their transaction, and the sequencer then proves non-membership **AND** inserts them into the nullifier tree. This way, if multiple transactions are sent with the same nullifier, only one of them will be included in the block as the others will fail the non-membership proof. **Why does it need to insert the nullifier if I'm reading?** Why can't it just prove that the nullifier is not in the tree? Well, this is a privacy concern. If you just make the non-membership proof, you are leaking that you are reading data for nullifier $x$, so if you read that data again at a later point, $x$ is seen by the sequencer again and it can infer that it is the same actor reading data. By emitting the nullifier the read is indistinguishable from a write, and the sequencer cannot tell what is happening and there will be no repetitions. From 4ab8db0f7a745bed4fd4c32797497deae9d76d44 Mon Sep 17 00:00:00 2001 From: LHerskind Date: Fri, 22 Dec 2023 10:16:50 +0000 Subject: [PATCH 5/5] chore: minor typos --- yellow-paper/docs/state/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yellow-paper/docs/state/index.md b/yellow-paper/docs/state/index.md index 0736226bf96e..51bbde05b3b5 100644 --- a/yellow-paper/docs/state/index.md +++ b/yellow-paper/docs/state/index.md @@ -22,7 +22,7 @@ Convincing someone that a piece of data is active can then be done by proving it ### Private State Access -Whenever a user is to read of use data, they must then convince the "rollup" that the their data is active. As mentioned above, they must prove that the data is in the data tree (membership proof) and that it is still active (non-membership proof). However, there are nuances to this approach! +Whenever a user is to read or use data, they must then convince the "rollup" that the their data is active. As mentioned above, they must prove that the data is in the data tree (membership proof) and that it is still active (non-membership proof). However, there are nuances to this approach! One important aspect to consider is *when* state can be accessed. In most blockchains, state is always accessed at the head of the chain and changes are only made by the sequencer as new blocks are added. @@ -36,7 +36,7 @@ Membership can be proven using historical state because we are using an append-o However, this doesn't work for the non-membership proof, as it can only prove that the data was active at the time the proof was generated, not that it is still active today! This would allow a user to create multiple transactions spending the same data and then send those transactions all at once, creating a double spend. -To solve this, we need to perform the non-membership proofs at the head of the chain, which only the sequencer knows! This means that instead of the user proving that the data is not in the nullifier tree, they provide the nullifiers as part of their transaction, and the sequencer then proves non-membership **AND** inserts them into the nullifier tree. This way, if multiple transactions are sent with the same nullifier, only one of them will be included in the block as the others will fail the non-membership proof. +To solve this, we need to perform the non-membership proofs at the head of the chain, which only the sequencer knows! This means that instead of the user proving that the nullifier of the data is not in the nullifier tree, they provide the nullifier as part of their transaction, and the sequencer then proves non-membership **AND** inserts it into the nullifier tree. This way, if multiple transactions include the same nullifier, only one of them will be included in the block as the others will fail the non-membership proof. **Why does it need to insert the nullifier if I'm reading?** Why can't it just prove that the nullifier is not in the tree? Well, this is a privacy concern. If you just make the non-membership proof, you are leaking that you are reading data for nullifier $x$, so if you read that data again at a later point, $x$ is seen by the sequencer again and it can infer that it is the same actor reading data. By emitting the nullifier the read is indistinguishable from a write, and the sequencer cannot tell what is happening and there will be no repetitions.