-
Notifications
You must be signed in to change notification settings - Fork 79
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
Ring Binning #84
Comments
This proposal has a lot of potential to fix many of the UX problems of Monero. If constructed carefully it could also get rid of the 10 confirmation spending requirement, allow transaction mempool replaceability, and fee bumping techniques. To make the above proposal work, the signature message structure needs to be changed. Adopting some of the notation for MLSAG message construction from the hardware wallet paper the current signature message structure
In the code
The transaction prefix hash is what needs to be changed. Instead of The transaction ID construction currently uses the same |
Probably a stupid question: if both transactions need to be constructed before the first one is sent, how does that fix the UX issue in Monero regarding the 10 confirmation spending requirement? Couldn't the user just construct one transaction with multiple outputs? |
@boogerlad I think the idea is if you spend an output added to the chain in the recent 10 blocks, then if the chain gets reorged, your tx can survive if the other tx also survives. The output you spend would be 'floating', so miners can adjust the floating offset when there is a reorg. |
I see. Can step 2 and 3 be swapped? |
Sure they can, the steps are just demonstrating tx chaining (making tx when output you are spending doesn't exist in chain yet). |
Clever construction. Just one thought about maintaining transaction indistinguishability and fungibility: if we allow any transaction to have a floating ring member, then the protocol should enforce that every transaction has a floating ring member. Then it won't stand out when people do use the feature. |
I find the concept of a hash-based bin selection interesting, because assuming the algorithm is reproducible across CPUs(!), the transaction size should be much smaller than listing each ring member. Or did I misunderstand that part of the proposal?
If attempting to spend immediately, then this also leaks the real spend? If so, then this is certainly a "non-starter" if a user has to choose between privacy and immediate spending. This hurts the on-chain privacy of everyone. |
Yes it is hash-based and reproducible across CPUs.
@vtnerd If all tx have a floating index in the same range (relative to chain height), then observers won't necessarily know if floating index in last 10 blocks is due to a fast spend or randomness. The key to this is defining a floating output selection zone (constants |
love the binning and chaining. I'm pondering the specific use-case of the 1 floating ring member. IMO, an ideal monero protocol would be able to survive a complete netsplit - i.e., 2 subnetworks form that end up completely isolated from each other. Then, at some other time, the two networks become reconnected. Afaiu, it should be theoretically possible for the two subnetworks blockchains to reform into a single chain in a massive re-org. For instance, in bitcoin I think this is possible - the txs in the alt chain would just be dumped to the txpool, and they get mined back into the mainchain. From what I'm understanding from this proposal, the 1 floating ring member wouldn't accommodate such an event. I mean, I get it, the single floating ring member is designed for a specific use case of tx-chaining and not really the possible but improbable netsplit. However, I'm always a fan of making the protocol more robust. cockroach factor. E.g., can monero be designed to survive nuclear war? |
@Gingeropolous the 'Reorg' section discusses netsplits. You can't have both a binning strategy (to minimize reference storage) and recover from arbitrarily large netsplits, but you can recover from small reorgs. |
Relevant article on a real-world use-case that would benefit from this today: https://comit.network/blog/2021/07/02/transaction-presigning/
|
"in most cases" is not a good phrase in the given context. The problem is that the hash-based approach should require some information leakage about the real spend, correct? And technically this occurs in all contexts afaik. In other words, with this approach the signer has to "work-backwards" from the real-spend and put some information so that the verifier is guaranteed to use it in the ring. The signer must do this because a purely hash-based approach will never "select" the desired output. So instead, we have this scheme where the signer tries to obfuscate the real spend, but in so doing is leaking more information than the current approach. This scheme is just too complicated and is going to have subtle leaks to be acceptable. |
I'm not sure what you mean by this. The scheme described here only leaks:
|
There is more information about the real spend than the current approach. Or rather, there is more subtle metadata about the real-spend in these floating values than the current dead-simple approach. The problem stems from the hash-based approach for ring selection - it forces the signer to commit to some value with a relationship to the real spend.
Which hurts the privacy of others typically because this output cannot be used in a ring past or present. Monero has been moving away from optional features for this reason, particularly with ring-selection. The size of the ring isn't even signer selectable for instance. |
Metadata leakage about the real spend, in the context of a floating output, is mostly independent of how ring members are selected (the use of hashes here is irrelevant because everything important is randomized). If we had pure random selection over a gamma distribution, then the floating index would still leak information. Maybe even the same amount of information, because with large rings you can more accurately extrapolate the 'distribution upper bound' (i.e. the max index when selecting from the gamma distribution). The problem is all ring members must be known when constructing a transaction. Practically speaking, this means all non-spends must exist on-chain already. If all your decoys are selected from on-chain, then the chain height when you selected ring members cannot be greatly obfuscated. Floating outputs that are too far away from that chain height cannot be hidden. Do you have a suggestion how tx chaining can be done better? Or are you arguing tx chaining should not be supported at all? |
You need to elaborate on all cases where the real spend is leaked. So far it appears when tx-chaining is used, it definitely leaks the real spend to miners and anyone watching the mempool. I don't see how this is an acceptable tradeoff if Monero's privacy can be weakened simply by using a feature to use outputs quicker. And as I already stated, this hurts the privacy of everyone on-chain because it reduces the anonymity set. What I'm also pointing out, and you appear to be dodging through unclear language, is that even in the normal case metadata is leaked. This actually easiest to see in the verification algorithm. The signer does not control the seed for the randomizer function, but still has to get the verifier to select the "real" output in a "bin". So there's a connection between the real-spend and these offset values (that are stored on-chain), that is not present in the current approach
Yes, I understand the problem you are trying to solve.
I have no reason to "not support" tx chaining (i.e. the question is non-sense, of course I would want something like tx chaining in Monero). However, tx chaining should not be added if it reduces privacy. I don't see how its possible to have both, unless the tx size is increased dramatically. |
"warning: if floating_index - binning_upper_bound > BINNING_UPPER_BOUND_SELECTION_WIDTH + MIN_ALLOWED_WIDTH_FLOATING, then an input is guaranteed to be flagged as floating=real & real=chained" Aside from this
This argument is fine with me and definitely needs to be discussed. I don't have a strong opinion either way - tx chaining was requested by other people. However, I'd like to disentangle it from more technical criticisms of the scheme.
Rather than dodging it, these sections are WIP and I have not gotten around to explaining why it works. I agree if the offsets leak any metadata then the scheme is broken. The scheme is supposed to:
If each of those is uniformly distributed, then no information is leaked about which bin member is the real spend. In other words, it is equally probable that the offsets map any of the hash-generated things onto the real bin/bin member.
I don't appreciate the antagonistic pedantics. Everything 'added' to Monero is 'supported' by Monero, so arguing that tx chaining shouldn't be 'added' means you are arguing it shouldn't be 'supported' (in my view of the meaning of these words....). |
I doubt this claim is accurate, but I'm going to need something closer to pseudo-code/Python before going further - the real-spend is the only one where the offsets were manipulated to work and these offsets are recorded permanently in the blockchain.
Your original statement was "Or are you arguing tx chaining should not be supported at all?", and I was stating that I can be against your approach, but in-favor of the feature in general. Perhaps there is no way to add tx-chaining without reducing privacy (for others) or increasing tx size, in which case the feature should not be supported. That's typically how the Monero community has done feature selection in the past at least. If the ring-selection algorithm can filter out definitely leaked real-outputs, this changes the argument because other participants are less affected by tx-chaining (it lowers potential anonymity set size but not the ring security). I believe this is possible, but likely complicates the ring-selection even further. |
As a quick follow-up: everytime a ring member is identified as a real-spend, it weakens the privacy of other rings where it was used as a "dummy". This was why ring size 1 was banned, but this case is different if all participants can see the "leak" during transaction construction. It's still on the edge of what the Monero community has supported in the past as this creates two distinct sets of Monero transactions, whereas all of the analytic researchers are trying to eliminate anything different in transactions. Their ideal is all uniform cryptographic data. |
I had similar thoughts. My take was if most txs that need to use floating outputs can be 'hidden' among normal txs, then a floating offset style may be worthwhile. If most txs using floating outputs cannot be hidden (e.g. because floating outputs are mostly used for tx-chaining, and the delay between tx signing and floating output being added to chain is too big in most of those cases), then floating offsets are no better than a 1-member ring, so we might as well support two input types (pure binning, and 1-member rings) if we want floating outputs. When the proposal gets more fleshed out I will be sure to discuss this aspect of the topic, thank you for bringing it up. |
In this scenario, the privacy of Monero has arguably dropped considerably. We've allowed people a technique to "shoot themselves in the foot" (Monero is private, unless you do this one thing!), and the total anonymity set has dropped weakening any future gains from Triptych. The more the discussion continues, I don't see how I could support the floating offsets idea (when it is a defacto 1-member ring). In fact, why not just allow tx chaining with 1 member rings? Its less complicated and more clear on whats actually happening. Which...
If this approach is taken, "pure binning" would need a separate discussion for its inclusion because it is no longer required for tx-chaining to work. Although you've suggested as such with the title "Tx Chaining and Ring Binning". |
The problem from my pov is, for example, that all tx trying to get around the 10-block lock time would have to use the 1-member rings. But at least some of those tx could be obfuscated among slower spends using this proposal, so there is an opportunity cost. While outputs spent by 1-member rings can be blackballed, they can only be blackballed after they have been spent. Rings created before then can be polluted. Plus, you can just as easily blackball floating outputs from this proposal that are unequivocally real-spends.
So, if I were to advocate 1-member rings over this proposal, I would have to think that the pollution of ring signatures with heuristically non-neutral outputs is a greater cost (combined with the chain cost of useless ring signatures created for unequivocal real-spends) than the benefit of allowing those outputs (plus a set of outputs that are heuristically neutral) to be spent in ring signatures. I don't have enough information to judge this trade-off.
Floating offsets are very easy to chop out of this proposal if ultimately not desired. |
Then why not just use 1-member rings in these cases. Its simpler for all parts of the code, and there's no hiding the weakened privacy/security in obscure algorithms.
Can you clarify this point? A proposal where the real-spend is known initially but is later hidden isn't acceptable because it creates "land-mines" in the output selection process. It gives chain-analysis companies that are constantly monitoring the mempool an advantage over Monero wallets that are not constantly monitoring the mempool. So to repeat - if a real-spend can be determined after leaving the node of origin, then the 1-member ring approach should be used. Its simpler and is effectively the same thing. The community dropped such rings previously though, which is ultimately why I have been critical of this part of the idea.
Ok, but the binning approach still requires much scrutiny as I suspect it also leaks metadata. |
In this proposal, a floating output with an index way higher than the binning upper bound is unequivocally a real spend (unless the tx author is messing around or there is a wonky implementation). However, floating outputs close to the upper bound might be real spends or decoys. If those floating outputs are heuristically neutral, then there exist no heuristics that, when used, increase the observer's chance of correctly guessing if one of them is a true spend. There is no way to guarantee that no such heuristic exists, so for the sake of argument we can assume that at least some proportion of floating outputs may not be heuristically neutral (i.e. guessing that they are the true spend has These hypothetical floating outputs with heuristic weaknesses would not exist in the 1-member ring scenario, so the fact they would be able to pollute ring signatures is a cost that must be added to the analysis (even if in practice it is insignificant). Note: Even pure ring signatures over a gamma distribution might not be heuristically neutral if the true spend distribution does not match the gamma distribution. An analyst who knows the true spend distribution has a heuristic advantage for detecting true spends.
This is not possible here.
I don't have strong opinions about the best approach. My goal is to finish the proposal, explain it as best I can, and adjust it according to other people's discussion. Perhaps talking to those who are invested in floating outputs (be it via this proposal or simple 1-member rings) would be more productive toward reaching a final consensus @TheCharlatan. The reason I did not propose 1-member rings originally was two-fold:
|
Why "ring binning"? Ignore "tx chaining" for second - would this design still be recommended without that use case? Does it result in smaller transaction sizes, etc.? Because it certainly does not improve on the heuristics of a bad RNG for the gamma distribution stage. Edit: Above you mentioned O(1) scaling, but this scheme does not appear to be that. So I'm primarily looking for a comparison of your actual approach instead of an abstract case.
This portion of my response is about "tx chaining" (spending outputs currently in the mempool or less than 10 blocks). Can you list the scenarios where this is possible. Let me help you - is the transaction being broadcast multiple times or is the miner manipulating the transaction? Because if there are re-broadcasts by the wallet this gives away some metadata to people watching the mempool (it was a quick spend). OR if the miners can do it, what magic allows them to adjust the indexes without knowing the real spend (they would have to know the real spend to set the indexes)? Just walk through the transaction chaining scenario a bit more. I'm insisting on specifics here because I suspect once its implemented if people knew how it worked they would reject it (1-member rings achieve the same thing without the obfuscation and complicated algorithm).
This portion of my response is about "tx-chaining" 1-member rings are dead simply compared to this approach. And I'm not supporting 1-member rings but they allow transaction chaining easily. |
Yes it would still be recommended.
Miners can adjust the floating offset, they don't need to know which output is the real spend. Check out the 'Reorgs' section of the proposal for a full discussion. A ring with 128 ring members looks like this: The floating member is referenced like
Every tx input has a floating member. Most of those will be decoys. Tx authors with decoy floating members must select the decoys from a range of blocks above the Tx authors won't always know in advance if their real-spend floating member will fall into the 'decoy range'. This is because the final index of that member may be unknown. |
The source paper has less variables and the selection algorithm is vastly more simple. Can you explain why the changes from the paper? It appears to be for the reorg/chaining case. This also means the claims of the paper no longer apply and analysis of your altered approach needs to be done. The approach in the paper is simpler than what you have here.
Ok, your alteration to the paper is more clear now. You incorrectly answered my question above though. Nodes constantly watching the mempool have an advantage. The recipient always knows when transaction chaining has been enabled (the floating offset is the real spend), people watching the mempool can assume its the real spend when I now understand why your answers have been a bit all over the place (from my perspective), there isn't a guaranteed way to avoid some of these transactions after-the-fact. I'm still advocating for 1-member rings instead of your proposal for this reason. Also a reminder to anyone bothering to read this: the paper supplied above has a different algorithm and scheme that is simpler, and I would support that scheme over this one (although I haven't done enough reading to say whether the claims in that paper are accurate). |
This response confuses me. Decoy floating members can and should be selected from the most recent 10 blocks. Why wouldn't they be? It's true that the presence of a floating output in the last 10 blocks may be a heuristic that increases the likelihood of it being a real spend, but it is not any 'guaranteed' information. This does not contradict my earlier comments. How would a recipient know anything more than observers about inputs to a tx? EDIT: ah, if the recipient knows they are receiving an output from a chained tx, then they will know the floating output is a real-spend. It's true that nodes watching the mempool have a slight advantage. They witness all reorgs, so they will have some slight timing information about transactions that a later observer won't have. I had not considered this (not that my failure to think of something warrants your aggressive attitude).
You seem to be assuming most people would use the blackball tool. Maybe @SamsungGalaxyPlayer can give some insight on how many people actually use it in practice. |
I think I have a bit of a clearer explanation for the subtle leak @vtnerd talked about here. I don't yet see a perfect way around it with a simple client-side binning approach I've been trying to think through either. It's easier to see it if you assume 100 bin members. You'll have a "jar of marbles" so to speak that revolve around the center. So the bin center would be clearly deducible. If your real output is used as a bin center, then it's fairly trivial there would be no benefit to binning. An observer could just eliminate any outputs that aren't bin centers. If you take your real output, and try to select a new bin center using the real output, the new bin center is still statistically more likely to be closer to the real output (can qualify this claim further with some kind of proof). Therefore, the outputs that are closer to the bin center are still more likely to be real than the outputs further away. At this point, I'm trying to reason through if it's possible to avoid this leak by fixing the bin size to 2, and going with an approach that doesn't scale to >2 bin members. But with >2 bin members, I think the above should help make it a bit clearer a leak is introduced with an approach along these lines. Perhaps the Moser paper's approach for fixing bins may be the best way to go after all. |
Unless I am missing something, selecting a bin center at random from around the real spend means the |
Yep, nevermind I believe you are right @UkoeHB -- that was me tripping up. Fairly simple python script that should support your claim and show I was wrong: import random
import statistics
BIN_WIDTH = 100
BIN_RADIUS = int(BIN_WIDTH / 2)
REAL_OUTPUT_INDEX = 150
NUM_SIMULATIONS = 100000
BIN_MEMBERS = 50
init_bin = range(REAL_OUTPUT_INDEX - BIN_RADIUS, REAL_OUTPUT_INDEX + BIN_RADIUS)
real_is_closer_than_bin_members = 0
real_is_further_than_bin_members = 0
deltas = []
for i in range(NUM_SIMULATIONS):
bin_center = random.choice(init_bin)
final_bin = range(bin_center - BIN_RADIUS, bin_center + BIN_RADIUS)
bin_member_distances = []
for j in range(BIN_MEMBERS):
bin_member = random.choice(final_bin)
# on average, expect this distance to be BIN_WIDTH / 4
distance_from_bin_center = abs(bin_member - bin_center)
bin_member_distances.append(distance_from_bin_center)
avg_bin_member_distance_from_bin_center = sum(bin_member_distances) / len(bin_member_distances)
median_bin_member_distance = statistics.median(bin_member_distances)
# on average, expect this to be BIN_WIDTH / 4
real_distance_from_bin_center = abs(REAL_OUTPUT_INDEX - bin_center)
if real_distance_from_bin_center < median_bin_member_distance:
real_is_closer_than_bin_members += 1
elif real_distance_from_bin_center > median_bin_member_distance:
real_is_further_than_bin_members += 1
# my initial claim was that this would be negative with significance
real_and_bin_member_delta = real_distance_from_bin_center - avg_bin_member_distance_from_bin_center
deltas.append(real_and_bin_member_delta)
# but not the case
print("Delta should be close to 0 to show initial claim was wrong:", sum(deltas) / len(deltas))
# my claim was the real would tend to be closer to the bin center than other bin members, not the case with significance
print("Real is closer to bin center than other bin members: ", real_is_closer_than_bin_members, "times")
print("Real is further than bin center than other bin members:", real_is_further_than_bin_members, "times") EDIT: another sanity check from a different angle: import random
BIN_WIDTH = 100
BIN_RADIUS = int(BIN_WIDTH / 2)
REAL_OUTPUT_INDEX = 150
NUM_SIMULATIONS = 1000000
BIN_MEMBERS = 20
init_bin = range(REAL_OUTPUT_INDEX - BIN_RADIUS, REAL_OUTPUT_INDEX + BIN_RADIUS)
# is the real's distance from bin center uniformly distributed?
real_output_bin_member_distance_index_counts = {}
for i in range(BIN_MEMBERS + 1):
real_output_bin_member_distance_index_counts[i] = 0
for i in range(NUM_SIMULATIONS):
bin_center = random.choice(init_bin)
final_bin = range(bin_center - BIN_RADIUS, bin_center + BIN_RADIUS)
bin_member_distances = []
selected_bin_members = { REAL_OUTPUT_INDEX: True }
real_distance_from_bin_center = abs(REAL_OUTPUT_INDEX - bin_center)
bin_member_distances.append(real_distance_from_bin_center)
for j in range(BIN_MEMBERS):
bin_member = random.choice(final_bin)
# no duplicates
while bin_member in selected_bin_members:
bin_member = random.choice(final_bin)
selected_bin_members[bin_member] = True
distance_from_bin_center = abs(bin_member - bin_center)
bin_member_distances.append(distance_from_bin_center)
bin_member_distances.sort()
real_output_bin_member_index = bin_member_distances.index(real_distance_from_bin_center)
# sometimes there will be duplicate distances, and index() will always choose the closer one.
# to avoid this, check the next elem in the bin_member_distances array. if it's the same, then
# 50% of the time, just bump the real_output_bin_member_index by 1
if real_output_bin_member_index < len(bin_member_distances) - 1:
if real_distance_from_bin_center == bin_member_distances[real_output_bin_member_index + 1]:
if random.choice(range(2)) == 1:
real_output_bin_member_index += 1
real_output_bin_member_distance_index_counts[real_output_bin_member_index] += 1
# expect roughly equivalent counts for each index
for i in range(BIN_MEMBERS + 1):
print("Idx:", i, " Count:", real_output_bin_member_distance_index_counts[i]) EDIT 2: A more visual way of seeing the problem:
Start with x:
Now here are all plausible bin centers:
Knowing the indexes of (Edited again for clarity.) |
Question on this part:
Am I following right that the bin width is likely to shrink if you're at the edge? If your real output is the max output allowed, your theoretical bin center could be between the real output and the |
The bin center selection zone is shrunken/cropped. There isn't any other way to do it, because upper/lower bounds are just that - the boundaries of your data set, and because bin width is fixed - the bin center must be within +/-
Yep |
I think there may be an issue at the edge. Am I missing something here?
Real output 0 has 0 bin center 1/3 times, real output 1 has 0 bin center 1/4 times, real output 2 has 0 bin center 1/5 times. Assuming you have roughly the same number of 0's, 1's, and 2's, then you would expect a higher % of the 0 bin centers to be real output 0. Therefore, if you know the bin center is 0 (or in real terms, if you know the bin center is the closest possible bin center to the upper bound), your best guess for the real is output 0, next best guess is output 1, etc. What am I missing? |
I don't think you are missing anything, that is correct (and unavoidable). On the other hand, you will also have a slightly disproportionate 'piling up' of decoys bins at the boundaries. I think if the true spend distribution matches the bin selection distribution, then these two effects cancel each other out (from a high-level statistical pov; if you have special timing knowledge about an output, then binning is less effective at the boundaries of the data set). |
I think I have a way to avoid it in the wallet-side algorithm (at least for the tip of the chain), though I'm not sure it's possible to apply here: using fixed bins, similar to how the Moser paper suggests. I.e. you know a group of outputs must fall into a particular bin. You could say |
I think that can be applied here. Just define the binning upper bound, pre-define bins relative to the binning upper bound ( |
so transaction chaining is out? is this something that Seraphis can allow? |
@r4v3r23 yes, Seraphis allows transaction chaining. The current RingCT protocol could technically do tx chaining with a LOT of code work and protocol changes, but the real spends in chained tx would always be the 'newest' ring member, which is an unpleasant and perhaps not-worthwhile heuristic. |
would tx chaining allow for spending unconfirmed outputs and remove the 10-block confirmation lock when receiving funds? |
Tx chaining lets you make a partial tx that spends outputs that aren't in the chain. However, the 10-block lock time must remain in place. Here's what you can do (Alice and Bob are friends):
|
I think TX chaining and spending of outputs younger than 10 blocks could be still done with binning if the youngest bin referenced outputs by hash instead of by index. This would increase output sizes (for example by 128 bytes for In the example given by @UkoeHB Alice could draw some decoys from block X, so Bob will not know which exact output is being spent. |
This is basically the floating output idea this issue originally proposed. I think it is too flawed to pursue. |
@tevador @UkoeHB from the latest getmonero.org Seraphis write-up:
is it possible to remove the 10-block lock time in practice without harming privacy across the board by revealing the real output in "trusted" transactions? can this at least remove the lock time on unconfirmed change since its essentially a self-spend with not other party involved? just trying to get a feel for how this would change UX when transacting |
Publicly revealing the spent output weakens all rings that have used that output as a decoy, so that would be a significant hit to the overall privacy of Monero. |
right so in practice the 10-block limit stays |
Have any problems been found with this deterministic ring selection algorithm? @UkoeHB's current Seraphis code seems to encode the bin centers explicitly by index rather than generating them from a seed. There are several advantages of selecting the bins deterministically:
|
Disadvantages:
If the bin loci are deterministic, but a sub-range of the selection zone is unknown, then you have to brute force rings that only sit within the known range. Since selection distributions greatly favor recent blocks, it may be prohibitively expensive to find viable rings to get a lock time.
It cannot be signed by ownership proofs, because that would greatly weaken tx chaining. For example, I want to make multisig txs where the decoy references are selected moments before submitting each tx (this is deferred membership proofs, which is the precursor to tx chaining), in order to make multisig txs more indistinguishable from regular txs. That timing can't be known in advance (when building the signatures). For tx chaining, if |
No. You have to defer making the membership proof until the lock time has elapsed and all outputs in the specified range are known. This is the main point of a time lock field. Consensus would reject transactions with
Valid issue, but not insurmountable. Assuming the multisig participants cooperate, they can estimate the required lock time when the signature will be completed. I guess it's a matter of deciding if we need cheap time locks that are actually usable or the ability to pre-sign a transaction without locking it. |
Ah yes, my mistake.
If you aren't online around the right time, then it becomes a brute force problem again. If we want a cleartext |
This has the problem of leaking that the tx was time-locked.
More costly to store and verify. But I digress, this discussion would be more suitable for the time locks issue. I just wanted to point out that ring selection could be used as a proxy time lock. |
note: tx chaining was removed after discussion (July 18, 2020)
Table Of Contents
Abstract
Reference on-chain outputs for ring membership deterministically to minimize storage, and introduce a binning strategy to mitigate failures of the decoy selection distribution (e.g. gamma distribution).
Motivation
Monero is planning to implement a next-gen protocol with sublinear scaling that should allow ring sizes on the order of 2^6 to 2^8 (64 to 256) (either Triptych or some alternative). Referencing each ring member individually is inefficient, so 'deterministic' references are beneficial. 'Deterministic' means being able to recover an arbitrary number of on-chain indices from a small tuple of variables that include some kind of entropy.
As discussed in An Empirical Analysis of Traceability in the Monero Blockchain, selecting ring decoys directly from a distribution that spans the entire ledger is not perfect. If an observer has special timing knowledge about a given transaction, and/or knows that the selection distribution does not match the true spend distribution, then they can gain a significant advantage when trying to guess the true spends in that transaction.
That problem can be mitigated by selecting 'clumps' of decoys from the ledger-spanning selection distribution. A 'clump' (or 'bin') of decoys is a small set of decoys that are located very close to each other on the ledger. This way, even if an observer can guess the age of a transaction's true spend to within a few hours or less, the true spend will still be hidden among some decoys.
It isn't ideal to only select decoys that are close to the real spend. Even if some observers have special timing information about transactions, other observers may not. It is therefore useful to combine clumping/binning with selection from the ledger-wide distribution (recommended by Foundations of Ring Sampling).
This proposal describes a deterministic ring member referencing strategy where clumps/bins of decoys are selected from a ledger-wide distribution. Privacy considerations are discussed where appropriate.
Algorithm summary
To set the stage, I will briefly summarize the ring member referencing algorithm here (glossing over all the details, which can be found in the actual algorithm below). Each output spent by a transaction will have its own ring member reference tuple (set of variables to recover its set of ring members from).
binning_upper_bound
, which is the index of the highest output in the ledger that can be referenced by this input. Defining this is necessary so interacting with the ledger-wide selection distribution is deterministic.[true_spend_index - bin_radius, true_spend_index + bin_radius]
, which is equal to the width that bins will have.binning_upper_bound
) to a uniform distribution with a CDF. Its value in the uniform distribution is denotedmapped_real_spend_bin_center
.n
points in the uniform distribution.n'
at random from thosen
points. Definebin_rotator = mapped_real_spend_bin_center - n' mod uniform_distribution.size()
.n
points:n += bin_rotator mod uniform_distribution.size()
. Note thatn'
will now equalmapped_real_spend_bin_center
.n
points from the uniform distribution back into the ledger-wide selection distribution with a reverse CDF. All pointsmapped_n
are 'bin centers', the centers of each of the bins in the final ring member reference set. If bins should have only one member each (bin_radius = 0
), thenmapped_n
can be treated directly as ring member references and you don't need to proceed further.mapped_n
, use a hash function and public entropy to generatem
bin members from the range[mapped_n - bin_radius, mapped_n + bin_radius]
.m'
. Definebin_member_rotator = real_spend_index - m' mod 2*bin_radius + 1
.m
points in all bins:m = [(m - [mapped_real_spend_bin_center - bin_radius]) + bin_member_rotator mod 2*bin_radius + 1] + [mapped_real_spend_bin_center - bin_radius]
. Note thatm'
will now equalreal_spend_index
.binning_upper_bound
,bin_rotator
, andbin_member_rotator
. In practice, onlybin_rotator
andbin_member_rotator
need to be unique between transaction inputs.Binning strategy
For a given tx, reference all its inputs' ring members with the following strategy inspired by How to Squeeze a Crowd.
Bin configuration details
Each transaction has:
binning_upper_bound
(varint): An on-chain index that sets the upper bound for the range of outputs that can be members of bins. There is one of these per transaction.Each input has:
bin_rotator
(unsigned 8-byte integer): Rotates pseudo-randomly generated bin centers around the bin selection distribution.bin_member_rotator
(varint): Rotates pseudo-randomly generated bin members within all bins.Instead of public entropy, we will use a pseudo-random seed computed from parts of the transaction.
Bins
Define the number of bins for each input.
NUM_BINS = floor(sqrt(RING_SIZE))
. [[[TODO: is logarithmic bin division better? other ideas? discussion is ongoing, however there is general consensus that in practice NUM_BINS should be >= current ring size (11)]]]RING_SIZE
: A constant defined by the Monero protocol.Given
uneven_bins = RING_SIZE mod NUM_BINS
, define the number of bin members in each bin.bin_center
(discussed below), sort the bins. Letindex(bin, bins)
give the index ofbin
in vectorbins
.Rationale/Discussion
sqrt()
for deciding the number of bins to balance between selecting bins (ledger-scope) and bin membership (local-scope).Binning algorithm: tx construction
Define the bin configuration details and obtain the list of ring members for each input.
Constants and inputs
Rationale/Discussion
max_index
must be 'spendable', meaning a transaction that references that output won't be rejected if immediately submitted to the network. Monero has a 10-blockDEFAULT_SPENDABLE_AGE
that disallows adding a transaction to the chain if the tx references an output from the previous 10 blocks.DEFAULT_SPENDABLE_AGE
is not wide enough to protect their transaction from reorgs, then they should setmax_index
to an output from a sufficiently low block.Ring seed
Compute a pseudo-random number for generating all the bins and bin members for the transaction's inputs. We use a pseudo-random number in place of public entropy.
Rationale/Discussion
input_seed = H("input_seed", input.key_image)
instead ofring_seed
(i.e. a unique seed per input, that is constant between tx attempts), and also use the samebinning_upper_bound
for each attempt.ring_entropy
and recording it explicitly in transactions. We don't see any meaningful advantage to that approach.Binning upper bound
Select the binning upper bound, which is the maximum index that can be referenced by a bin. The same upper bound will be used for all inputs.
Rationale/Discussion
binning_upper_bound
to reduce storage in transactions. Beyond that, if there were unique upper bounds per input, then observers could use the average binning upper bound of multi-input transactions to more accurately estimate the time those tx were constructed.BINNING_UPPER_BOUND_SELECTION_WIDTH
is the region to randomly select a binning upper bound from. Selecting it from a range of blocks instead of using a fixed distance frommax_index
makes it harder for observers to analyze precisely when a transaction was constructed.binning_upper_bound
instead ofmax_index
. In other words, the algorithmic minimum fee for the block containing the output with indexbinning_upper_bound
should be used to set the transaction's fee. This mitigates observers' ability to use the transaction fee to estimate when a transaction was constructed.Selecting bins
Deterministically select the bins for input
input_index
.Rationale/Discussion
bin_rotator
to deduce which bin has the true spend unless the ledger-wide selection distribution is flawed.Selecting bin members
Deterministically select the bin members for input
input_index
.Rationale/Discussion
bin_member_rotator
to infer anything about which bin contains the true spend, nor which bin member in any given bin is more/less likely to be the true spend.Full ring member set
Binning algorithm: tx validation
Recover the ring members of each input in a transaction
tx
from the bin configuration details.Rationale/Discussion
Modifications for small ring sizes
This algorithm can be adjusted so all bins only have one member each, which can applied as an optimization/upgrade to Monero's current transaction protocol. Doing so would reduce input reference bytes from
(num_inputs x ring_size x varint)
to(varint + num_inputs x (u64 + varint))
.NUM_BINS = RING_SIZE
BIN_RADIUS = 0
get_num_bin_members()
will only return1
, and that each bin center will equal its lone bin member.bin_member_rotator
from bin configuration details.Tx changes
Modify the transaction input structure and the form of the message signed by transaction inputs.
txin_to_key
struct should contain a vector of commitments to outputs (i.e. hashes of each output).txin_to_key
struct should also contain the valuesbin_rotator
andbin_member_rotator
introduced by this proposal.binning_upper_bound
from this proposal:Tx validation
When you encounter a tx to validate, recover the ring members for each input from the bin configuration details defined in this proposal.
Blockchain and tx hashes
Rationale
Why not use the binning strategy from this paper?
Backward compatibility
This proposal changes the structure of tx and tx validation rules, so it requires a hard fork. It is well-suited to being implemented in the same hard fork that rolls out Triptych (or a similar next-gen tx protocol with large ring sizes), but is also compatible with Monero's current transaction protocol.
License
MIT license
References
The text was updated successfully, but these errors were encountered: