-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
Proposal/Request: Update Supplementary Transaction Content #6456
Comments
When you say "Sorted TLV", do you mean recursively "Sorted TLV", whatever TLV means? |
Does it matter how it gets sorted? Not sure I understand the question. |
If I attach a list of tiktok videos as an item in tx-extra, do I have to sort these videos by TLV? If the answer is no, then your "Sorted TLV" is non recursive. If yes, then it would be recursive. |
Sorted TLV says nothing about the contents of values, so I suppose non-recursive is correct. |
Great work, love the view tags. The Janus mitigation described above requires a full-spend wallet rather than a view-only wallet. I think the design goal for Janus mitigation should be for it to work with view-only wallets. If a 48-byte schnorr signature was used instead of a Janus key, then for 2-out txs the extra storage required for Janus mitigation would increase from 32 bytes to 48 bytes per tx. For n-out transactions where n>=3, the extra storage required for Janus mitigation would increase from 32 bytes per tx to 48 bytes per output. Since the vast majority of txs are 2-out, I think the extra 16 bytes in that situation is justified. I also think that the extra storage is justified for n>=3-out txs, since they are relatively rare. |
This seems a questionable justification. First off, I don't understand why 2 bytes would be the proper comparison, as the logical choice would be 4 or 8 bytes, as that's the amount of data likely to yield the optimal execution on most architectures (1 byte addressing, while technically possible, only makes sense in architectures without out of order execution; otherwise, you end up with false dependencies). Second, I find your phrasing odd: 2**8 vs 2**16 is a huge difference, 256x, to be precise. A 256x optimization is not to be taken lightly, and, at 4 or 8 bytes, the optimization would be even more massive. So, the justification here, to me, reads less like a justification, and more like a rather abridged, and somewhat dubious, statement of known information. A proper justification would address the time-space tradeoff: Why is the extra space so valuable that it shouldn't be used to dramatically speed up the computation? Even at 8 bytes, would the bloat be so bad? Isn't the entire blockchain doomed if an extra 8 bytes per transaction hobble it? After all, there's this active PR, which seems to considerably increase space usage, as compared to the proposal here. |
The EC variable base scalarmult operation required for each output is so computationally expensive (a modern CPU can only do in the order of thousands per second) that any 1 vs 4/8 byte scanning optimizations would not make any significant difference. I agree with @UkoeHB 's choice of 1 byte per view tag. |
Which scalarmult is used differently in the case of a 8 bytes or 1 byte viewtag? |
2 bytes is just an example. The bottleneck for scanning is EC operations, and going from 1 byte to 2 bytes only reduces EC ops by around 0.4%. i.e. given 256 outputs, with 1 byte you do 255 scalar-mult-key ops, and then 1 normal scan; with 65536 outputs, with 2 bytes you do 65535 scalar-mult-key ops, and 1 normal scan. But the fraction of 1scan/65535smk is only ~0.004x smaller than 1scan/255smk. So, you don't gain much on average. As @knaccc said, optimal execution isn't really a concern since EC ops are so much more expensive than data-handling optimization. EDIT: I agree with you more bytes would be justified if the 255/256 cases were super fast, so scan times were dominated by the remaining 1/256th case. However, view tags must be based on a shared secret for privacy (i.e. so no one else can compute the view tag and narrow down which outputs we might own), and shared secrets require an EC operation (unless we get into RSA territory, which seems like a dead end) EDIT2: while a normal scan requires around 3 EC operations. @fuwa0529
There is no difference. It's just a question of how many bytes you truncate off the hash. The hash output is 32 bytes, but with view tag you only save some of those bytes. |
@UkoeHB I see. The original MRL proposal does a fine job of explaining the savings, I just found the 1 vs 2 byte justification here lacking, as it elides the dominant factor: Adding a view tag reduces EC ops by 2/3 per negative. This cost savings ratio is the crucial component of the calculation, as it ultimately determines the appropriate scale of the false positive ratio (e.g. no savings makes the whole thing worthless; 10x savings would make for a ~3.4% reduction by adding an extra byte; etc). So, discussing the false positive ratio without explicit mention of the savings ratio seemed to miss the core issue. My point in mentioning 4 or even 8 bytes was simply that considering extremes helps best illustrate the function's behavior: Even scaling the tag to 8 bytes saves < 1% at a 2/3 savings factor, so of course 2 bytes is a non-starter. Put differently, when you can reject the maximum gain from expanding the tag as inconsequential, you have proven expansion is unwarranted; rejecting the minimum gain is less compelling. That said, I probably shouldn't have mentioned the pipelining issue, it just distracted from my aim. |
@iamamyth I updated the justification based on what you said. Thanks Note that the comment '1 byte is a standard unit of memory' lets us select 1 byte rather than say 7 bits or 9 bits, without any hassle. |
I'm very strongly in favor of this proposal, since the current transaction format is super leaky and long overdue for an overhaul. This proposal eliminates a variety of fungibility defects in one fell swoop, and will permanently shut down several transaction linkability heuristics. Misc thoughts and questions:
|
|
See this issue for timing data relating to view tags. |
I dunno. I would put forth that monero is digital money. There is really only one usecase of money - the storage and transfer of value. And the thing doing this storage and transfer must be fungible, thus, private. If monero isn't private (due to tx_extra letting people introduce oracle bamboo shoots that program your money to make it farm some yams), then its not money. Basically, let monero be money. If people want to program their money, they can make something else that can interface with monero, right? There's no need for the programming of the money to be on the same chain as the money. |
"If people want to program their money, they can make something else that can interface with monero, right?" Technically the transaction public key, Janus mitigation, and view tag are all 'extra programming' for Monero. An alternate wallet could choose to implement their own completely different addressing scheme. However, tx pub keys are ubiquitous and useful. We don't know the future... what if something just as useful as tx pub keys gets invented? Without the tx extra, and assuming a hard fork is not possible or doesn't have enough support, that new feature may be infeasible. |
Additional context on the fungibility implications of leaving the Thanks a lot to the Noncesense Research Lab for the excellent work! |
@UkoeHB , where do view tags stand? |
@Gingeropolous afaik no one is working on them, or this proposal, right now. |
I was asked recently:
Easiest to implement:
Highest impact (imo):
Consensus:
|
Proposal/Request: Update Supplementary Transaction Content
Monero is constantly being improved, which is fun and frankly amazing. Discussion and research has been ongoing over the past months (see MRL Issues #61, #62, #71, and #73) on some relatively minor transaction details, and at last there seems to be a clean path forward. Ideally all the changes proposed here would be rolled out in the same hard fork.
UPDATE 1 (April 17, 2020): Added 48-byte Schnorr proof as an alternate Janus mitigation.
UPDATE 2 (April 17, 2020): Added change_tag option for 48-byte Schnorr method.
UPDATE 3 (April 18, 2020): Added key points from my discussion with @knaccc about which 48-byte Schnorr Janus mitigation approach is better (change address designation vs 6-byte change_tag). Reduced change tag from 8 bytes to 6 bytes.
UPDATE 4 (April 23, 2020): Converted change tag into blinding factor for encrypted amount, reducing additional bytes required to 0. Deprecated change address based on this improvement (go to edit log to view previous iterations of this proposal).
UPDATE 5 (May 21, 2020): Added 'implementation guideline' at the end, since there is a lot going on in this proposal.
UPDATE 6 (May 24, 2020): Reworked proposal to improve readability, and removed all 'options'/ambiguity (e.g. removed Janus base key method).
UPDATE 7 (May 26, 2020): Improved Janus proof so transactions with >2-outputs only need 32 bytes per output instead of 48 (still need a 48-byte proof for 2-out tx).
UPDATE 8 (May 26, 2020): Replaced Janus proofs with encoded transaction private keys, so 2-out tx only need 32 bytes, and >2-out tx need 32 bytes per output (simplified version of UPDATE 7). Moved 2-out tx change output discovery mechanism to a hidden tx private key, returning amount encoding to its normal form. Reordered proposal so view tags (part 2) are introduced before the Janus mitigation (part 3), since the latter now relies on the former.
UPDATE 8 (June 4, 2020): Noted an alternative to 'hidden tx private keys' for identifying change outputs in 2-out tx. Namely, we could use a change-out-specific derivation-to-scalar.
UPDATE 9 (January 22, 2021): Noted that Janus mitigation can be reduced to 16 bytes if transaction private keys are derived from a 16-byte nonce.
Introduction
tl;dr
a. There must be a change output in each 2-out tx. A special 'hidden' transaction public key is used to differentiate change and non-change outputs in 2-out transactions. For efficiency, this relies on a unique construction for view tags in 2-out tx.
tx_supplement
section to transactions (should contain: tx pub keys, Janus proofs, view tags)Introduction Part 1: Extra Field
A thorny issue for Monero is the extra field, which is a completely unregulated part of the transaction structure. Anyone can put anything they want in the extra field. There are two important arguments about this field.
From my point of view, both arguments are too strong to abandon either of them. We must do our best within those constraints.
Now, we can create a simple, useful abstraction. Any 'legitimate' use of the extra field can be considered a 'feature' (e.g. random bytes with no purpose are not a legitimate feature). Multiple 'features' can coexist within the extra field. Indeed, right now we have the 'transaction public key', 'additional transaction public keys', 'encrypted payment ID', and 'extra nonce' all living together in the extra field.
A transaction is composed of a 'base configuration', the bare minimum to make a valid transaction that the vast majority of wallets can understand, and beyond that a 'set of features', which are non-essential but we can assume must be implemented in a compatible way with other wallets/services using those features (otherwise what's the point?).
How do we harmonize with argument 1? Every single possible implementation of 'base' + 'a given set of features' should be indistinguishable. As protocol designers, we must assume that if variation is permitted by the protocol, then all those variations will appear in the wild. In other words, our job here is to constrain transaction content so the most an observer can know is whether or not a given feature exists in a transaction. We should also avoid giving special treatment to different features, i.e. enforce a consistent and straightforward ruleset for all potential features. NOTE: Let's assume (to head off complaints) that variations in how a feature is implemented are considered different features (we certainly can't take responsibility for those variations). See Proposal Part 1: Sorted TLV In Extra Field below for my solution to this design goal.
Introduction Part 2: View Tags
Scanning the blockchain for owned outputs is constrained by how long it takes to check each individual output. Checking an output means performing several elliptic curve computations, which are relatively slow. There is a cheap, simple way to reduce scan times for new outputs by reducing the number of EC ops required (on average). It would add one byte per output to the tx structure, and should be deployed with a hard fork to streamline adoption. See Proposal Part 2: View Tags and Part 4: Expand/Reorganize TX Structure.
Introduction Part 3: Transaction Public Keys
We want Monero transactions to be as similar to each other as possible. Currently the number of tx public keys in a transaction leaks information about its recipients. If there are multiple tx pub keys it means at least one recipient is a subaddress. Due to intricacy around how tx pub keys are made, not all transactions with subaddresses have more than 1 tx pub key, which thankfully makes the exact anonymity set for subaddress recipients very ambiguous (only when a transaction has 2 non-change outputs, where at least one of them is to a subaddress, will there be more than 1 tx pub key). Nevertheless, it would be better if no information was leaked (see also MRL Issue #71).
The vast majority of transactions have two outputs, where one of them is a change output sending leftover Moneroj back to the tx author (or a dummy output with 0 amount). Luckily, 2-out tx with a change (or dummy) output only require one tx pub key, so we can easily enforce one tx pub key for all 2-out tx. Among the remaining tx with >2-out, some only require one tx pub key while some require one per output. However, in the name of indistinguishability, I propose to enforce one tx pub key per output for all >2-out tx. See Proposal Part 4: Expand/Reorganize TX Structure. Note: A small minority of 2-out tx with no change output would become 3-out tx. [credit: @knaccc]
Janus mitigation
As many people know, the Janus attack is an obscure yet potentially real attack on the idea subaddresses can't be associated with the normal address they were derived from. I won't belabor it here, see @SarangNoether's writeup.
The mitigation I chose for this proposal is (after many iterations and different ideas) to encode the transaction private key for each tx pub key and record it in transactions. It requires one 16-byte (or 32-byte) encoded value per tx pub key, and adds steps after owned outputs are identified. Multiplying the tx pub key by the inverse of its private key will reveal the base key used, which is enough to identify Janus attacks. See Proposal Part 3: Janus Mitigation.
But wait, if the tx private key is given to recipients, how can we allow non-change recipients in 2-out tx to know the tx private key for both outputs (since there is only 1 tx pub key)? The workaround is to make a hidden transaction public key for change outputs that can be derived deterministically from the tx author's private view key and the 'visible' tx pub key's encoded private key. Normally this method would significantly slow down scanning for owned outputs, but thanks to the efficiency gains from view tags the effect is hardly noticeable.
Introduction Part 4: Organization
Ubiquitous and non-optional parts of a transaction should be part of the transaction structure (not the 'extra' field). This means tx pub keys should move from the tx extra to a new 'tx_supplement' in the tx structure. Subaddress support is also non-optional, so I believe the encoded tx private keys should also go there. Likewise with view tags. Usually we would leave the tx pub keys in the tx extra since moving them invites technical debt, but this large proposal which touches several different parts of the transaction structure is a good opportunity to clean things up. See Proposal Part 4: Expand/Reorganize TX Structure.
Note: Technically a wallet could make transactions without tx pub keys, but I guarantee all other wallets wouldn't be able to identify outputs received from that oddball wallet. Using tx pub keys is a wallet consensus sufficiently non-optional to canonize (from my point of view).
Proposal
Proposal Part 1: Sorted TLV In Extra Field
Originally proposed in MRL Issue #61, which includes pseudo-code demonstrating how to implement it. Functionally the extra field would behave the same as it does now, since we already default to TLV format (it's just not enforced). The original proposal recommends adding restricted tags only useable via hardfork, but I am no longer in favor of that idea.
An example 'feature' could be @moneromooo's encrypted memo field.
Cost: No effect on transaction sizes, minimal increase in transaction verification time (especially since I'm also proposing to remove things from the extra field), and a small-to-moderate size development project. The biggest challenge would likely be organizing backward compatibility for wallets reading older tx. The cost to non-core wallet implementations is tiny, since they just have to add sorting (TLV is ubiquitous).
Note: this github Issue proposes deprecating the extra field completely.
Proposal Part 2: View Tags
Originally proposed in MRL Issue #73. This slightly changes how transaction authors build transactions, and how recipients search for owned outputs. For more of this terminology/notation, see Zero to Monero Second Edition especially Chapter 4.
Transaction author:
k^v*R_t
for outputt
, compute the so-called 'derivation to scalar':dv_to_s_t = H(k^v*R_t,t)
. Note: they have to computedv_to_s_t
to make the one-time addressK^o_t
, so there is no real need to compute it more than once.t
:view_tag_t = first_byte_of(H("view_tag",dv_to_s_t))
Potential recipient of output
t
:R_t
, compute the nominal derivation to scalar:dv_to_s_t_nom = H(k^v*R_t,t)
view_tag_t_nom = first_byte_of(H("view_tag",dv_to_s_t_nom))
view_tag_t_nom ?= view_tag_t
i.e. if it's the same as the view tag stored for outputt
in the tx.K^s_nom = K^o_t - dv_to_s_t_nom*G
K^s_nom
corresponds to their normal spend key, or one of their subaddresses' spend keys. If it does, yay they own the output and can spend it (after doing the Janus test)!For all non-owned outputs, the recipient will only get to step 4 about once out of every 256 outputs, and therefore only performs the EC computations in 4 for 1/256 unowned outputs.
Justify 1 byte design choice for the view tag: bytes are the basic unit of memory, and the difference in scan times between 1 byte and even 32 byte view tags is insignificant (implying no amount of increase in view tag size will have a meaningful impact; a view tag scan is 1 EC operation per tx pub key, while a full scan is ~3 EC ops, so with 1 byte tags there are 255 + 3 EC ops for every 256 unowned outputs on average, and going to 32 byte tags only reduces it to 256 EC ops for 256 unowned outputs).
Cost: There would be 1 byte more per output. A small development project.
Note: It would be nice to have a timing profile of output scanning for evalutating the impact of adding view tags, and more generally understanding what contributes to scan times.
Note2: What about wallets that fail to implement view tags correctly? Clearly wallets can still use the current method of scanning for outputs, and ignore view tags altogether. However, if the core wallet and other large wallets rely on view tags, then any other wallet that doesn't comply will incur a lot of complaints from users who send outputs to other wallets that don't recognize them (this means other wallets should retain the ability to slow-scan just in case). Wallet implementers will be naturally incentivized to build tx with functional view tags. Keep in mind a similar objection could be raised about transaction public keys, which aren't validated or validate-able or even strictly necessary to make useable transactions, yet are ubiquitous and do not cause problems.
Proposal Part 3: Janus Mitigation
Method 1: Janus base key (deprecated option)
Layed out and developed in MRL Issue #62.
Method 2: 48-byte Schnorr proof (deprecated option)
Originally ideated by @knaccc.
Method 3a: 16-byte (or 32-byte) encoded tx private key
Since we will be enforcing a unique tx pub key for each output (except change outputs in 2-out tx), it is now conceivable to inform recipients about the transaction private key for their owned outputs. Previously (e.g. currently), knowing the tx private key for transactions with just one tx pub key (most tx) would allow someone to figure out all the corresponding recipients and amounts.
Given a sender-receiver shared secret
r_t*K^v
, wherer_t
is the transaction private key for the output at indext
, definer_t
as a hash of a 16-byte noncen_r_t
,r_t = H("tx_private_key", n_r_t)
, then encoden_r_t
for the recipient as follows {thanks to @tevador for his idea to use a 16 byte nonce to seed the tx private key, so Janus mitigation is more efficient - if 16 bytes is deemed too little randomness, then a 32 byte tx private key can be generated as normal and added to transactions in encrypted form instead of the 16-byte nonce}n_r_t_encoded = n_r_t XOR_16 H("encoded_tx_private_key", r_t*K^v, t)
If a recipient encounters a one-time address/tx pub key pair
(K^o_t, R_t)
and identifies it as output owned by address (K^v_i
,K^s_i
).r_t_nom = H("tx_private_key", n_r_t_nom)
R_t_nom = r_t_nom * K^s_i
R_t_nom == R_t
thenR_t
was constructed correctly, otherwise a Janus attack was executed.Method 3b: change outputs in a 2-out tx
As stated, 2-out tx will have only one tx pub key, with one encoded tx nonce Rather than use the same tx nonce for both the change and non-change outputs, we will derive a hidden tx nonce from the encoded tx nonce and compute a hidden tx pub key for making the change output.
n_r_encoded
(there is only one so the index isn't relevant here), compute it using the change address's normal view keyk^v
(i.e. if the change address is a subaddress, use its normal address's view key):n_r_hidden = H_16("change_tx_private_key", k^v, n_r_encoded)
R_hidden = H("tx_private_key", n_r_hidden)*G
R_hidden
for constructing the change output.view_tag_change_t = first_byte_of(H("change_view_tag", k^v, dv_to_s_t))
In the original process tx pub keys were aligned 1:1 with view tags, but now there is only one tx pub key
R
so we will reuse the shared secret. We also add a logic branch for identifying change outputs.k^v*R
.t \in {0,1}
a. Compute the nominal derivation to scalar:
dv_to_s_t_nom = H(k^v*R,t)
b1. Compute the nominal view tag:
view_tag_t_nom = first_byte_of(H("view_tag",dv_to_s_t_nom))
b2. Compute the nominal change view tag:
view_tag_change_t_nom = first_byte_of(H("change_view_tag",k^v,dv_to_s_t_nom))
c. Check if either matches
view_tag_t
(view tag corresponding to outputt
)d1. If the normal tag matches, then compute the nominal spend key:
K^s_nom = K^o_t - dv_to_s_t_nom*G
d2. If the change tag matches, move down to step 3.
e. If
K^s_nom
matches a stored spend key, congrats we own the (non-change) output and can spend it! Now we should perform the Janus test by decoding the transaction noncen_r_encoded
, etc.view_tag_change_t_nom == view_tag_t
, should happen every ~1/256 unowned outputs).a. Compute the nominal hidden tx nonce:
n_r_hidden_nom = H_16("change_tx_private_key", k^v, n_r_encoded)
b. Compute the nominal hidden tx public key:
R_hidden_nom = H("tx_private_key", n_r_hidden_nom)*G
c. Use the hidden tx pub key to check if output
t
is owned. If it is then there is no need to perform a Janus test.Note: By prefixing the change view tag with the private view key, even the non-change recipient who knows
r
won't be able to compute the change view tag. Meanwhile, we continue to usedv_to_s_t_nom
so we don't have to computek^v*R_hidden
(expensive) until after the change view tag is flagged (rare).Note2: An alternative to 'hidden tx private keys for change outputs' would be modifying how one-time addresses are constructed. For example, using a 'change-out derivation to scalar' prefixed with the private view key:
dv_to_s_change = H(k^v, k^v*R,t)
. Whether to use this or the hidden key depends on which is simpler and cleaner to implement.Note3: If encoded tx nonces are implemented then OutProofs would be insufficient to prove creation of a transaction output, since they rely on knowledge of the transaction private key (which tx recipients would now know as well). This means OutProofs would require corresponding SpendProofs that prove we created the transaction itself. OutProofs would still be useful for showing a specific recipient received a certain amount of money. Also, OutProofs would not work for change outputs in 2-out tx, although aiui they don't work for change outputs currently anyway. Instead, make an InProof proving ownership of a change output and a SpendProof proving we created the tx it originated in.
Total cost
Apparently about 95% of modern transactions are 2-out tx, and there are on average 2.2 outputs per transaction. Therefore the average cost per transaction from adding encoded tx private keys is approximately:
.95*16 + 0.05*(2*16) + 0.2*16 = 20 bytes
Proposal Part 4: Expand/Reorganize TX Structure
This part's ideas originated in IRC discussions which I don't have links for. It's pretty straightforward.
tx_supplement
.a. Encoded tx private keys (corresponding 1:1 with outputs, except 2-out tx which have just one)
b. Transaction public keys (corresponding 1:1 with outputs, except 2-out tx which have just one)
c. View tags (corresponding 1:1 with outputs)
Cost: By constraining tx pub keys, some small minority of 2-out tx that don't have change outputs will become 3-out tx, and all >2-out tx that currently have just one tx pub key will now have tx pub keys for all outputs. A medium size development project.
Note: Enforcing 1 tx pub key per output for >2 out tx would likely increase average tx size by around 6-10 bytes, since about 5% of tx have >2 outs, which leads to an overall average of 2.2 outputs, and (0.0532 + 0.232) = 8.
Implementation Guideline
One possible path for implementing all the changes:
a. Enforce 1 tx pub key for 2-out tx, and 1 tx pub key per output for >2-out tx. This would add a check to block verification (HF version dependent), and rework how tx pub keys are constructed for new transactions (no need for backward compatibility). It also adds a scenario for making a dummy output (when there are only two outputs and both are non-change). [HF_ENFORCE_TX_PUB_KEY_QUANTITY]
a. For 2-out tx, use the hidden tx pub key for change outputs, implement 'change view tags', and add logic to 'scanning for owned outputs' for identifying change outputs.
b. Enforce one encoded tx nonce for 2-out tx, and one encoded tx nonce per output for >2-out tx (and one encoded tx nonce per output for miner transactions since there is never a change output).
a. Optional: Perform a timing analysis on transaction scanning to better understand the costs of scanning, and to quantify the benefit of view tags.
a. Optional: Move extra_nonce to tx_supplement to enforce uniformity (I recommend 32 bytes for miner transactions only, and 0 bytes for non-miner tx). Out of scope for this proposal, I would like to make a guideline for coinbase uniformity that doesn't impose a large burden on pool and solo-miner implementations. Might make a separate proposal that includes moving the extra nonce.
b. Optional: Move encrypted payment ID to tx_supplement. As @knaccc pointed out it would be preferable if people used subaddresses instead of integrated addresses, but putting encrypted IDs in the tx_supplement would 'canonize' them and ultimately make them harder to deprecate. I agree this should remain in the tx_extra.
DISCLAIMER
While I present these changes as a package, they don't have to all be implemented. In particular sorted TLV is contentious. Hopefully my argument in favor is clear enough.
The text was updated successfully, but these errors were encountered: