UTXO is complicated but definitely a very good solution as it completely eliminates replays (while still allowing re-attachments).
While UTXO would be the best solution, it probably isn’t for the current situation, in which we can’t have such enormous breaking changes in the mainnet, without a big overhaul of client libs etc. So basically this idea falls flat on the feasibility to be able to implement it in a timely fashion.
“Nonce” works (Etherium is the living proof) but they require to store the last used count for every spent address, even across snapshots. Unfortunately, this sounds like the current SpentAddresses. There are ways to discard (and thus allow replay attacks) once the balance is below a threshold value, but this sounds like very difficult UX for example.
I think the
counter (nonce) approach is the most sensible one for the current situation. While it is true that keeping an additional counter per ledger entry - and therefore the entire entry indefinitely, even if the balance is zero - is similar to storing spent addresses, in praxis it will still lead to less addresses which have to persisted overall, because Ed25519 signed transfers no longer automatically mean that a new address has to be persisted (you also made that point on Discord Wolfgang).
Of course, as before, people can still create dust by simply issuing transactions which deposit 1i on X addresses, so nothing really changes in that regard.
Signing everything should work, as only the exact same bundle can be replayed with gets filtered out. Again, in this case re-attachments can only be performed by the issuer, which could be a very reasonable compromise as this is anyway the case in 99% and especially in combination with the Conflict White Flag , re-attachment are hardly necessary.
Approach 4 from my PoV doesn’t work, because you can’t really sign every field of a transaction.
Issuing a bundle consists of following steps:
- Create input and output transactions without any signature
- Compute the bundle hash out of the essence data (address, value, obsolete tag, timestamp, current index and last index) of all transactions within the bundle
- Create a signature on each input transaction signing the bundle hash
- Execute tip-selection on a node to get trunk and branch
- Do Proof-of-Work which computes a PoW nonce which leads the transaction hash to fulfill the MWM. In this step, we start from the head transaction (last index) and chain the bundle transaction through the trunk field with each other. (the tail transaction (index zero) is PoWed the last)
- Broadcast the bundle
The idea behind signing everything within a transaction is to ensure, that the same transaction wouldn’t be applied twice (?), as it would result to the same transaction hash, however, even if trying signing everything, one still couldn’t sign the nonce, because it is the last step of the bundle creation procedure, meaning we can’t sign something which isn’t computed yet. Therefore one could still reattach the bundle, even if not in possession of the private key.
Perhaps however, one can switch out steps 3 with 5, so that instead of actually creating a PoW nonce after signing, we sign as the last step (
signing-within-PoW) (which means that creating the signature becomes part of the PoW process):
- Execute tip-selection
- Starting at the head transaction: we set the trunk and branch accordingly, then:
- mutate the nonce
- generate an Ed25519 signature of all the fields within the transaction
- check whether MWM is fulfilled (if not, repeat step 1-2)
- Now repeat the same for the remaining transactions in order to get the finalized bundle
At the end of this process, we have a bundle, which in its entirety is signed, so reattaching it wouldn’t work, because an adversary can’t create a valid signature. Additionally, reattaching the origin bundle doesn’t work, because reattaching in this case would just mean rebroadcasting the same exact bundle and would stop at the first node, which already received the first broadcasted instance.
Verifying the bundle would be different than with WOTS signatures, because instead of just grabbing the essence data of out every transaction within the bundle, every transaction contains a signature:
- Compute the bundle hash out of the essence data of every transaction within the bundle
- If the bundle hash doesn’t correspond to what is within all the transactions, the bundle is already invalid.
- Verify the signatures within each transaction. (Note that within Ed25519, the address is a hash of the public key and the public key is embedded in the signature). Theoretically the public key has to only reside in one transaction within the bundle.
OR actually (I’m writing text as I’m thinking)
Instead of signing every transaction individually, the tail transaction can sign the hashes of all other transactions within the bundle, and we apply the
signing-within-PoW only for the tail transaction. Note that
signing-within-PoW ensures that the issuer actually did PoW.
However, I’m sure that signing as part of the PoW process, is probably computationally too expensive to make sense.
Anyway…@wollac, did you have an idea in mind how signing the entire bundle would/should have worked?