Joseandro Luiz
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # RAMLookup: Simulating random-access memory in zero-knowledge proofs ###### Author: Matthew Ryan Recent developments in zero-knowledge (ZK) proof systems have brought private, verifiable computing closer than ever to traditional computing, unlocking even more potential applications and bringing more flexibility to developers. The introduction of 'lookup arguments' has been a key piece of this puzzle, and the ecosystem has seen accelerating progress. In this post we outline why lookup arguments are useful, some history of the development of lookup arguments, and describe our new RAMLookup protocol for faithfully simulating Random-Access Memory (RAM) in zero-knowledge. ## Lookup arguments ### Background ZK proof systems differ from traditional computers in a fundamental way: instead of using bits and bytes, with the familiar logic gates (`AND`,`OR`,`XOR`, `NOT`, etc.), ZK proofs (ZKPs) use 'finite field elements' and the arithmetic operations `ADD` and `MUL`, along with `ASSERT_EQUAL`. This means that some things that are very efficient for tranditional computers, like checking that the result of an addition fits in 8 bits, or XOR-ing 2 8-bit numbers, are significantly more expensive in ZKPs. The goal of **lookup arguments** is to address some of these shortcomings. As we will explore, there is even more potential to move closer to traditional computing, which we at O(1) Labs have been working to unlock. ### What is a lookup argument, and how does it help? A lookup argument is a small cryptographic part of a larger ZK proof system which, given a list of values `Table`, lets you check that some other values in the proof match one of those values in `Table`. In the abstract, you can think of this like a function `assert_in_table` that you can include in a ZKP, which behaves like: ```js function assert_in_table(table, x) { for (entry in table) { if (entry == x) { return; } } throw Error("Not in table"); } ``` For the simplest example above -- checking that the result of an addition fits in 8 bits -- we can fill our `Table` with the numbers `0`, `1`, ..., `255` and use `assert_in_table(Table, x)` to confirm that our value `x` does indeed fit into 8 bits. In the literature, such a check is called a 'range check'. Notice that in this example the number of entries in our `Table` corresponds to $2^8$. #### Simulating functions with lookup tables A more interesting example is `XOR`ing 2 8-bit numbers. What we would like to do is look up three values at once: the `left_input`, the `right_input` and the `output`. If we can look up multiple values in the same 'entry' in the table, then we could create a `Table` with entries corresponding to each of the possible inputs and outputs of XOR that we want to support. ``` (0, 0, 0) (0, 1, 1) (0, 2, 2) ... (1, 0, 1) (1, 1, 0) ... (255, 254, 1) (255, 255, 0) ``` | left_input | right_input | output | | -------- | -------- | -------- | | 0 | 0 | 0 | | 0 | 1 | 1 | | 0 | 2 | 2 | | ... | ... | ... | | 1 | 0 | 1 | | 1 | 1 | 0 | | ... | ... | ... | | 255 | 254 | 1 | | 255 | 255 | 0 | Since lookup arguments only operate on a single value, we need a clever trick to combine them into a single value. In principle, we can just combine the inputs and outputs by adding them together: ``` value_to_lookup = something_1 * left_input + something_2 * right_input + something_3 * output ``` Which is efficient to do in the snark. However, we need to make sure that the prover can't cheat and use different values! For example, if we choose: ``` something_1 = 1 something_2 = 2^8 something_3 = 2^16 ``` Then, a valid lookup of `(15, 1, 14)` representing `15 XOR 1 = 14` will result in a lookup of value `15 + 1 * 256 + 14 * 65536 = 917775`; but a malicious prover could choose the values `(271, 0, 14)` which also encodes the value `271 + 0 * 256 + 14 * 65536 = 917775`. In this scenario, the prover would be able to prove an invalid `XOR` operation as valid since `271 XOR 0` equals to `271`, not `14`. Instead, we use the Fiat-Shamir trick, by making the prover commit to the values that they want to look up before they find out what the `something_i` values are. We can hash those commitments to generate a random number `random_mixer` and set: ``` something_1 = random_mixer^0 something_2 = random_mixer^1 something_3 = random_mixer^2 ``` Since we choose the value `random_mixer` after the values to look up have already been decided, a malicious prover cannot choose values outside of the table that will result in a value from inside the table! This has the effect of changing `assert_in_table` to: ```js function assert_in_table(table, x) { var value_to_lookup = 0; var mixer = 1; for (value in x) { value_to_lookup += mixer * value; mixer *= random_mixer; } for (entry in table) { if (entry == value_to_lookup) { return; } } throw Error("Not in table"); } ``` Where looking up a single value is `assert_in_table(Table, [x])` (like we did for the range check example), and looking up our 3 values for `XOR` looks like `assert_in_table(Table, [left_input, right_input, output])`. We can do the same trick with the values in the table, generating: ``` Table[i] = random_mixer^0 * FirstTableEntry[i] + random_mixer^1 * SecondTableEntry[i] + random_mixer^2 * ThirdTableEntry[i] ``` And then `assert_in_table` can be used to simulate XOR using our precomputed table above. #### Using multiple lookup tables in a single lookup argument We've shown that you can use a lookup table to perform a 'range check' *or* to simulate some function. However, it would be helpful -- and make the proof smaller -- if we could use the same lookup argument to perform the lookups that we need for both of these different lookups. Considering our examples, we have 2 tables: 8-bit range check and `XOR`. What we would really like to do is add a way to combine them into a single table, and give the lookups (calls to `assert_in_table`) in the proof a way to specify which table out of the combined table they would like. If we naively join the tables together, adding `0` for values that we aren't going to use, we get the width-3 table ``` FirstTableEntry | SecondTableEntry | ThirdTableEntry ----------------+------------------+---------------- /* 8-bit range check part */ 0 | 0 | 0 1 | 0 | 0 2 | 0 | 0 ... | ... | ... 255 | 0 | 0 /* XOR part */ 0 | 0 | 0 0 | 1 | 1 ... | ... | ... 1 | 0 | 1 1 | 1 | 0 ... | ... | ... 255 | 254 | 1 255 | 254 | 0 ``` This works perfectly for our range-check lookup, but now we have an issue with XOR: a malicious prover could run `assert_in_table(Table, [3, 0, 0])` and claim that `3 XOR 0 = 0` using the value from the range-check table! Luckily, the solution is simple: we can give each individual table an ID, treating it as an column in the table, and use the same trick as before: ``` Table ID | FirstTableEntry | SecondTableEntry | ThirdTableEntry ---------+-----------------+------------------+---------------- /* 8-bit range check part */ 0 | 0 | 0 | 0 0 | 1 | 0 | 0 0 | 2 | 0 | 0 ... | ... | ... | ... 0 | 255 | 0 | 0 /* XOR part */ 1 | 0 | 0 | 0 1 | 0 | 1 | 1 ... | ... | ... | ... 1 | 1 | 0 | 1 1 | 1 | 1 | 0 ... | ... | ... | ... 1 | 255 | 254 | 1 1 | 255 | 254 | 0 ``` Now, our `assert_in_table` looks like: ```js function assert_in_table(table, table_id, x) { var value_to_lookup = table_id; var mixer = random_mixer; for (value in x) { value_to_lookup += mixer * value; mixer *= random_mixer; } for (entry in table) { if (entry == value_to_lookup) { return; } } throw Error("Not in table"); } ``` so that our new `assert_in_table(Table, ID, [a, b, c])` is the same as doing `assert_in_table(Table, [ID, a, b, c])` with the old `assert_in_table`. Finally, we have all the pieces to convert complex or expensive operations into cheaper lookups, allowing us to create smaller, more efficient ZKPs. ### Okay but... how does it actually work? As we mentioned above, ZKPs only have 3 building blocks: `ADD`, `MUL`, and `ASSERT_EQUAL`. Using just these operations, we want to find a way to compare the values looked up in the circuit to the values that we're allowed to look up. A nice way to reason about lookup arguments is to think about them as a pair of mathematical 'bags of numbers'. Into `bag_1`, we want to put all of the values that we look up, and into `bag_2` we want to put all of the values that we are *allowed* to look up, determined by the table. Different lookup arguments behave in different ways, but they all share 4 basic operations: * `empty_bag` returns an empty bag, so that we can start from a known state; * `add_to_bag` adds a value to the bag for every value in the table, or for every step of the ZKP that uses a lookup; * `same_bags_value` is a fixed value; * `compare_bags` takes 2 bags and returns `same_bags_value` if the two bags contain the same values. In the protocols that we will explore, instead of tracking the bags individually, we track an 'accumulator' `acc` as we proceed through the proof, which contains the value of `compare_bags` after every step. Therefore we have: ``` bag_1[0] = empty_bag bag_2[0] = empty_bag ... /* Add some things to each bag */ compare_bags(bag_1[n], bag_2[n]) = same_bags_value ``` where `n+1` is total number of bags, with `bag_1[n]` and `bag_2[n]` being the final state of `bag_1` and `bag_2` respectively. Reducing this to the accumulator: ``` acc[0] = same_bags_value acc[i] = compare_bags(bag_1[i], bag_2[i]) acc[n] = same_bags_value ``` ## The plonk permutation argument In the sense described above, the 'permutation argument' described in the plonk paper is not a *true* lookup argument: it asserts that values from different parts of the ZKP are equal to *each other*, but it doesn't allow for a lookup table that the values must come from. Nonetheless, the permutation argument was an important step in the development of lookup arguments in general. ### The general idea The permutation argument starts with the idea that every value in a ZKP can be laid out in a 2D grid, like so: ``` y x | 1 | 2 | 3 | 4 | ... | k ------+-----+-----+-----+-----+-----+----- 1 | ? | ? | ? | ? | ... | ? 2 | ? | ? | ? | ? | ... | ? 3 | ? | ? | ? | ? | ... | ? 4 | ? | ? | ? | ? | ... | ? ... | ... | ... | ... | ... | ... | ... n | ? | ? | ? | ? | ... | ? ``` If we associate every value with its coordinates in the grid, we can turn this into a long list ``` (1, 1, ?) (1, 2, ?) ... (1, k, ?) (2, 1, ?) ... (n, k, ?) ``` that we can iterate over to run the permutation argument. At this stage, we can decide which values we want to be equal, and group the coordinates into groups that all must have the same value. For example, if we want the values at `(1, 1)`, `(2, 2)` and `(4, 3)` to be equal, we create the list `[(1, 1), (2, 2), (4, 3)]`; if we don't need the value at `(1, 2)` to equal any others, then we can put it in its own group `[(1, 2)]`. We should now have every coordinate at exactly one position of exactly one list. We can now take each list `l`, copy it, and rotate it by 1 position to the left to get `l_shifted`. For example: ``` l = [(1, 1), (2, 2), (4, 3)] l_shifted = [(2, 2), (4, 3), (1, 1)] ``` Now, the idea is that we put each value into `bag_1` with its coordinates, and we put that value into `bag_2` with the value from `l_shifted` that matches its coordinates. In terms of our example, this looks like: ``` add_to_bag(bag_1, (1, 1, x_1_1)) /* add x_1_1 at its coordinate */ add_to_bag(bag_1, (2, 2, x_2_2)) /* add x_2_2 at its coordinate */ add_to_bag(bag_1, (4, 3, x_4_3)) /* add x_4_3 at its coordinate */ add_to_bag(bag_2, (2, 2, x_1_1)) /* add x_1_1 as if it was at (2, 2) */ add_to_bag(bag_2, (4, 3, x_2_2)) /* add x_2_2 as if it was at (4, 3) */ add_to_bag(bag_2, (1, 1, x_4_3)) /* add x_4_3 as if it was at (1, 1) */ ``` and then the argument can only succeed if `x_1_1 = x_2_2 = x_4_3`, otherwise the bags will have different values. ### As a 'lookup argument' The permutation argument is a 'multiplicitive' argument, which means that we multiply together a value related to every element in each bag, and we compare the bags by dividing them. In terms of our operations above, the protocol is essentially: ``` empty_bag = 1 add_to_bag(bag, (x, y, value)) = bag * (value + mixer_1 * x + mixer_2 * y + mixer_3) same_bags_value = 1 compare_bags(bag1, bag2) = bag1 / bag2 ``` We use 3 `mixer` values as part of the protocol, which can be chosen using Fiat-Shamir as described above to stop a malicious prover from cheating. Importantly, using random values also makes sure that the probability of multiplying by 0 is negligible -- about `1/2^255` for Kimchi proofs. ### Rolling it out in Kimchi One quirk of Kimchi's proof system is that we cannot multiply more than 8 variables (or, more technically, polynomials) in a single constraint. Similar constraints apply to other proof systems, e.g. pairing-based ZKPs, where this number is even lower at 2! This guides us to our first design decision: at each step of the ZKP, we include the first 7 'columns' of variables in the permutation argument. As constraints, we have: ``` /* At step 1 */ acc[0] = 1 /* At steps 1 to n */ acc[i+1] = acc[i] * (column[0][i] + mixer_1 * 0 + mixer_2 * i + mixer_3) * (column[1][i] + mixer_1 * 1 + mixer_2 * i + mixer_3) * (column[2][i] + mixer_1 * 2 + mixer_2 * i + mixer_3) * (column[3][i] + mixer_1 * 3 + mixer_2 * i + mixer_3) * (column[4][i] + mixer_1 * 4 + mixer_2 * i + mixer_3) * (column[5][i] + mixer_1 * 5 + mixer_2 * i + mixer_3) * (column[6][i] + mixer_1 * 6 + mixer_2 * i + mixer_3) / ( (column[0][i] + mixer_1 * permut_x[0][i] + mixer_2 * permut_y[0][i] + mixer_3) * (column[1][i] + mixer_1 * permut_x[1][i] + mixer_2 * permut_y[1][i] + mixer_3) * (column[2][i] + mixer_1 * permut_x[2][i] + mixer_2 * permut_y[2][i] + mixer_3) * (column[3][i] + mixer_1 * permut_x[3][i] + mixer_2 * permut_y[3][i] + mixer_3) * (column[4][i] + mixer_1 * permut_x[4][i] + mixer_2 * permut_y[4][i] + mixer_3) * (column[5][i] + mixer_1 * permut_x[5][i] + mixer_2 * permut_y[5][i] + mixer_3) * (column[6][i] + mixer_1 * permut_x[6][i] + mixer_2 * permut_y[6][i] + mixer_3) ) /* At step n */ acc[n+1] = 1 ``` However, notice that we also never said that we could do divisions -- a common factor between ZK proof systems. Instead, we tweak our second constraint so that it uses only multiplications: ``` acc[i+1] * (column[0][i] + mixer_1 * permut_x[0][i] + mixer_2 * permut_y[0][i] + mixer_3) * (column[1][i] + mixer_1 * permut_x[1][i] + mixer_2 * permut_y[1][i] + mixer_3) * (column[2][i] + mixer_1 * permut_x[2][i] + mixer_2 * permut_y[2][i] + mixer_3) * (column[3][i] + mixer_1 * permut_x[3][i] + mixer_2 * permut_y[3][i] + mixer_3) * (column[4][i] + mixer_1 * permut_x[4][i] + mixer_2 * permut_y[4][i] + mixer_3) * (column[5][i] + mixer_1 * permut_x[5][i] + mixer_2 * permut_y[5][i] + mixer_3) * (column[6][i] + mixer_1 * permut_x[6][i] + mixer_2 * permut_y[6][i] + mixer_3) = acc[i] * (column[0][i] + mixer_1 * 0 + mixer_2 * i + mixer_3) * (column[1][i] + mixer_1 * 1 + mixer_2 * i + mixer_3) * (column[2][i] + mixer_1 * 2 + mixer_2 * i + mixer_3) * (column[3][i] + mixer_1 * 3 + mixer_2 * i + mixer_3) * (column[4][i] + mixer_1 * 4 + mixer_2 * i + mixer_3) * (column[5][i] + mixer_1 * 5 + mixer_2 * i + mixer_3) * (column[6][i] + mixer_1 * 6 + mixer_2 * i + mixer_3) ``` NB: We have switched to 0-based indexing to match the Kimchi implementation more closely; the protocol works equivalently with 1-based indexing, as in the explainer above! TODO: Costs in terms of columns ## The plookup argument This is our first 'real' lookup argument, which allows us to 'load' a table into the proof from which we can look up values. This represented a seismic shift in ZKPs, opening the potential for simulating other architectures cheaply with the function model, and starting progress towards the more conventional 'memory' model of traditional computing. ### The general idea The plookup argument uses the structure of the lookup table to make guarantees about which values 'come from' the table vs which values have been 'looked up' as part of the ZKP. Since the values form a list, we can use the difference between each value and the next to detect whether they the value is repeated. In effect, we place the pair `(Table[i], Table[i+1])` into the bag. For example, consider our 8-bit range check table from before, with the final value repeated: ``` Entry | Difference ------+----------- 0 | 1 1 | 1 2 | 1 ... | ... 254 | 1 255 | 0 255 | - ``` If we lookup several values in the table, and encode these by repeating the corresponding entries in their original position, we can see that we preserve the values in the bag, and only introduce new values whose difference is 0: ``` Entry | Difference ------+----------- 0 | 0 /* Lookup of 0 */ 0 | 0 /* Lookup of 0 */ 0 | 1 1 | 1 2 | 0 /* Lookup of 2 */ 2 | 0 /* Lookup of 2 */ 2 | 0 /* Lookup of 2 */ 2 | 1 ... | ... 254 | 0 /* Lookup of 254 */ 254 | 1 255 | 0 255 | - ``` Once we have built our 'witness' for the ZKP, we know which values we have been looked up, so we can build a list like this second table which includes them in the correct order (according to the original table's order). We will call this list `SortedTable`. Therefore, in `bag_1` we can insert: * `(Table[i], Table[i+1])` for each `i`, and * `(lookup_value, 0)` for each `lookup_value` looked up in a step of the ZKP. Correspondingly, in `bag_2` we can insert: * `(SortedTable[i], SortedTable[i+1])` for each `i`. Once again, the argument can only work if the values in `bag_1` exactly match the values in `bag_2`, so we know that: * every value `Table[i]` has an equal value `SortedTable[j]`, where `Table[i+1] = SortedTable[j+1]` * every `lookup_value` has a unique `j` such that `SortedTable[j] = SortedTable[j+1] = lookup_value`. ### As a lookup argument Once again, the plookup argument is a 'multiplicitive' argument. In terms of our operations above, the protocol is essentially: ``` empty_bag = 1 add_to_bag(bag, (value, difference)) = bag * (value * (1 + mixer_1) + difference * mixer_1) same_bags_value = 1 compare_bags(bag1, bag2) = bag1 / bag2 ``` As before, we use Fiat-Shamir to select `mixer_1` after the prover has committed to their lookups, to prevent cheating. The `add_to_bag` function here is a little more interesting: when we are adding to the bag from one of the tables -- either `Table` or `SortedTable` -- we can cancel out the `-value` in the difference; otherwise, when it is 0, we can factor out the `1 + mixer_1` term. Concretely: ``` add_to_bag(bag, (tbl[i], tbl[i+1])) = bag * (tbl[i] + mixer_1 * tbl[i+1]) add_to_bag(bag, (value, 0)) = bag * (1 + mixer_1) * value ``` ### Rolling it out in Kimchi Due to the same quirk as before, we are limited in the number of lookups that any constraint can integrate in Kimchi. Due to some other overheads, we chose to support a maximum of 4 lookups at each step. The constraints look suspiciously similar to the permutation argument's, except we use a placeholder `lookup_value[i][j]` variable that gets replaced with a concrete expression depending on what the current step of the ZKP is doing. Note also that we split `SortedTable` into several chunks, to allow us to run the argument all at once. ``` /* At step 1 */ acc[0] = 1 /* At steps 1 to n */ acc[i+1] = acc[i] * (lookup_value[0][i] + (1 + mixer_1)) * (lookup_value[1][i] + (1 + mixer_1)) * (lookup_value[2][i] + (1 + mixer_1)) * (lookup_value[3][i] + (1 + mixer_1)) * (Table[i] + mixer_1 * Table[i+1]) / ( (SortedTable[0][i] + mixer_1 * SortedTable[0][i+1]) * (SortedTable[1][i] + mixer_1 * SortedTable[1][i+1]) * (SortedTable[2][i] + mixer_1 * SortedTable[2][i+1]) * (SortedTable[3][i] + mixer_1 * SortedTable[3][i+1]) * (SortedTable[4][i] + mixer_1 * SortedTable[4][i+1]) ) /* At step n */ acc[n+1] = 1 ``` Once again, we tweak the accumulator constraint so that it can be expressed in terms of multiplication: ``` acc[i+1] * (SortedTable[0][i] + mixer_1 * SortedTable[0][i+1]) * (SortedTable[1][i] + mixer_1 * SortedTable[1][i+1]) * (SortedTable[2][i] + mixer_1 * SortedTable[2][i+1]) * (SortedTable[3][i] + mixer_1 * SortedTable[3][i+1]) * (SortedTable[4][i] + mixer_1 * SortedTable[4][i+1]) ) = acc[i] * (lookup_value[0][i] + (1 + mixer_1)) * (lookup_value[1][i] + (1 + mixer_1)) * (lookup_value[2][i] + (1 + mixer_1)) * (lookup_value[3][i] + (1 + mixer_1)) * (Table[i] + mixer_1 * Table[i+1]) ``` NB: We have switched to 0-based indexing to match the Kimchi implementation more closely; the protocol works equivalently with 1-based indexing, as in the explainer above! TODO: Costs in terms of columns ## The MVLookup argument MVLookup is a recent development in lookup arguments, which switches to an 'additive' argument rather than a multiplicitive one. As we shall explore below, the additive nature of this argument brings us the possibility of removing elements from our bags as well as inserting them, which brings us still closer to our goal of a read-write memory primitive. ### The general idea In some senses, this is the simplest of the arguments we have looked at so far. Every value contributes `1/(mixer + lookup_value)`, and we can scale each of the lookup table's values's contributions by the number of times they are looked up. This gives us the protocol: ``` 1/(mixer_1 + lookup_value[0]) + 1/(mixer_1 + lookup_value[1]) + 1/(mixer_1 + lookup_value[2]) + ... + 1/(mixer_1 + lookup_value[k]) = num_uses[0]/(mixer_1 + Table[0]) + num_uses[1]/(mixer_1 + Table[1]) + num_uses[2]/(mixer_1 + Table[2]) + ... + num_uses[n]/(mixer_1 + Table[n]) ``` The use of inversion (i.e. `1/something`) along with addition ensures that any change to *any* lookup value will affect *all* of the other additive terms in an unpredictable way: multiplying through by all of the denominators gives us`(mixer_1 + lookup_value)` as a term multiplied by all of the additive terms except its own one. ### As a lookup argument In terms of our operations from before, the protocol is essentially: ``` empty_bag = 0 add_to_bag(bag, (value, difference)) = bag + 1 / (mixer_1 + value) same_bags_value = 0 compare_bags(bag1, bag2) = bag1 - bag2 ``` Once again, we ensure that the values looked-up can't be chosen maliciously by using Fiat-Shamir to select `mixer_1` after the lookups have already been committed to by the prover. As noted above: instead of repeatedly adding a single value from the table using `add_to_bag`, we can use multiplication to perform as many (or no) additions as needed, in a single step. ### Rolling it out in Kimchi Since this argument is additive, we have a lot more flexibility in Kimchi, since it appears that we don't have the same cost in computing the accumulator. ``` /* At step 1 */ acc[0] = 1 /* At steps 1 to n */ acc[i+1] = acc[i] + 1 / (mixer_1 + lookup_value[0][i]) + 1 / (mixer_1 + lookup_value[1][i]) + 1 / (mixer_1 + lookup_value[2][i]) + ... + 1 / (mixer_1 + lookup_value[k][i]) - NumUses[i] / (mixer_1 + Table[i]) /* At step n */ acc[n+1] = 1 ``` However, we *still* don't have a native inversion / division operation, so we have to do some multiplications. Happily, even with this design constraint, we can look up more values at a much lower cost, by using some intermediate variables to help with these inversions: ``` inversion_sum[i][0] = 1 / (mixer_1 + lookup_value[0][i]) + 1 / (mixer_1 + lookup_value[1][i]) + 1 / (mixer_1 + lookup_value[2][i]) + ... + 1 / (mixer_1 + lookup_value[5][i]) inversion_sum[i][1] = 1 / (mixer_1 + lookup_value[6][i]) + 1 / (mixer_1 + lookup_value[7][i]) + 1 / (mixer_1 + lookup_value[8][i]) + ... + 1 / (mixer_1 + lookup_value[11][i]) ... inversion_sum[i][k] = 1 / (mixer_1 + lookup_value[6k][i]) + 1 / (mixer_1 + lookup_value[6k+1][i]) + 1 / (mixer_1 + lookup_value[6k+2][i]) + ... + 1 / (mixer_1 + lookup_value[6k+5][i]) acc[i+1] = acc[i] + inversion_sum[i][0] + inversion_sum[i][1] + ... + inversion_sum[i][k] ``` Multiplying through by the denominators, we get: ``` inversion_sum[i][j] * (mixer_1 + lookup_value[6j][i]) * (mixer_1 + lookup_value[6j+1][i]) * (mixer_1 + lookup_value[6j+2][i]) * (mixer_1 + lookup_value[6j+3][i]) * (mixer_1 + lookup_value[6j+4][i]) * (mixer_1 + lookup_value[6j+5][i]) = (mixer_1 + lookup_value[6j+1][i]) * (mixer_1 + lookup_value[6j+2][i]) * (mixer_1 + lookup_value[6j+3][i]) * (mixer_1 + lookup_value[6j+4][i]) * (mixer_1 + lookup_value[6j+5][i]) + (mixer_1 + lookup_value[6j][i]) * (mixer_1 + lookup_value[6j+2][i]) * (mixer_1 + lookup_value[6j+3][i]) * (mixer_1 + lookup_value[6j+4][i]) * (mixer_1 + lookup_value[6j+5][i]) + (mixer_1 + lookup_value[6j][i]) * (mixer_1 + lookup_value[6j+1][i]) * (mixer_1 + lookup_value[6j+3][i]) * (mixer_1 + lookup_value[6j+4][i]) * (mixer_1 + lookup_value[6j+5][i]) + (mixer_1 + lookup_value[6j][i]) * (mixer_1 + lookup_value[6j+1][i]) * (mixer_1 + lookup_value[6j+2][i]) * (mixer_1 + lookup_value[6j+4][i]) * (mixer_1 + lookup_value[6j+5][i]) + (mixer_1 + lookup_value[6j][i]) * (mixer_1 + lookup_value[6j+1][i]) * (mixer_1 + lookup_value[6j+2][i]) * (mixer_1 + lookup_value[6j+3][i]) * (mixer_1 + lookup_value[6j+5][i]) + (mixer_1 + lookup_value[6j][i]) * (mixer_1 + lookup_value[6j+1][i]) * (mixer_1 + lookup_value[6j+2][i]) * (mixer_1 + lookup_value[6j+3][i]) * (mixer_1 + lookup_value[6j+4][i]) ``` Notice that, as opposed to plookup where we had to create an extra column of `SortedTable` for every value that we look up in each step of the ZKP, here we only pay with an extra column after 6 more column! TODO: Costs in terms of columns ## RAMLookup: Adapting RAMLookup for random access memory TODO

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully