Skip to content

Conversation

@PastaPastaPasta
Copy link
Member

@PastaPastaPasta PastaPastaPasta commented Aug 17, 2020

The first commit simply uses "GetRealOutpointPrivateSendRounds" in a few behind the scenes places where I saw it

The second commit:

Add "ps_salt" value to walletdb
This value is used to deterministically pick a random number of rounds to mix, between N and N + GetRandomRounds. A salt is needed in addition to the inputs hash to ensure that an attacker learns nothing from looking at the blockchain.

Third Commit:
Implement Random Round Mixing

This implements "Random Round Mixing." Previously, attempted attacks on PrivateSend assumed that all inputs had been mixed for the same number of rounds. Normally assuming 2,4,8 or 16.

While none of these attacks have been successful, they still teach what we can do to make our system more robust, and one of those ways is to implement "Random Round Mixing".

Under the previous system, inputs were mixed up until N rounds (configured by user). At this point, the input was considered mixed, and could be private-sent. Under this new system, an input will be mixed to N rounds like prior. However, at this point, Sha256d(input, salt) will be calculated (note: this likely could be a more efficient hash function than double sha256, but that can be done in another PR / version if needed). If (hash % 2 == 0), then the input will be mixed for one more round.
This results in an exponential decay where if you mix a set of inputs, half of those inputs will be mixed for N rounds, 1/4 will be mixed N+1, 1/8 will be mixed N+2, etc. This current implementation caps it at N+3. This results in mixing an average of N+0.875 rounds. If you removed the cap, you would mix on average N+1 rounds.

I tested this by starting three wallets mixing, and the distribution (while not perfect) was exactly how I would expect with about 50%, 25%, 12.5%, 12.5%. This also fixes #3525

This value is used to deterministically pick a random number of rounds to mix, between N and N + GetRandomRounds. A salt is needed in addition to the inputs hash to ensure that an attacker learns nothing from looking at the blockchain.

Signed-off-by: pasta <[email protected]>
This implements "Random Round Mixing." Previously, attempted attacks on PrivateSend assumed that all inputs had been mixed for the same number of rounds. Noramlly assuming 2,4,8 or 16.

While none of these attacks have been successful, they still teach what we can do to make our system more robust, and one of those ways is to implement "Random Round Mixing".

Under the previous system, inputs were mixed up until N rounds (configured by user). At this point, the input was considered mixed, and could be private-sent. Under this new system, an input will be mixed to N rounds like prior. However, at this point, Sha256d(input, salt) will be calculated (note: this likely could be a more efficient hash function than double sha256, but that can be done in another PR / version if needed). If (hash % 2 == 0), then the input will be mixed again.
This results in an exponential decay where if you mix a set of inputs, half of those inputs will be mixed for N rounds, 1/4 will be mixed N+1, 1/8 will be mixed N+2, etc. This current implementation caps it at N+2. This results in mixing an average of N+0.875 rounds. If you removed the cap, you would mix on average N+1 rounds.

Signed-off-by: pasta <[email protected]>
@PastaPastaPasta PastaPastaPasta changed the title Implement Random Round Mixing [PrivateSend] Implement Random Round Mixing Aug 17, 2020
@PastaPastaPasta PastaPastaPasta changed the title [PrivateSend] Implement Random Round Mixing privatesend: Implement Random Round Mixing Aug 17, 2020
@UdjinM6
Copy link

UdjinM6 commented Aug 21, 2020

Pls see https://github.com/UdjinM6/dash/commits/pr3661

@PastaPastaPasta
Copy link
Member Author

Added your commits, plus some modifications @UdjinM6

@PastaPastaPasta PastaPastaPasta added this to the 16 milestone Aug 23, 2020
@xdustinface
Copy link

Nice 👍 Didn't test it yet but looks good at a first glance.

(note: this likely could be a more efficient hash function than double sha256, but that can be done in another PR / version if needed). If (hash % 2 == 0), then the input will be mixed for one more

What about at least adding c1b7027 to this PR?

Another thought i had which i didn't implemented yet because i don't think its absolutely needed is: We could dump the salt to the dump-file in dumpwallet rpc and write it back to the wallet in importwallet rpc. However, without having it in the dump/import process i guess nothing bad would happen, just some more mixing after the import (which probably can then still happen if you import to a "new" salt to a non empty wallet with already mixed funds...), other thoughts?

@PastaPastaPasta
Copy link
Member Author

Nice +1 Didn't test it yet but looks good at a first glance.

(note: this likely could be a more efficient hash function than double sha256, but that can be done in another PR / version if needed). If (hash % 2 == 0), then the input will be mixed for one more

What about at least adding c1b7027 to this PR?

Another thought i had which i didn't implemented yet because i don't think its absolutely needed is: We could dump the salt to the dump-file in dumpwallet rpc and write it back to the wallet in importwallet rpc. However, without having it in the dump/import process i guess nothing bad would happen, just some more mixing after the import (which probably can then still happen if you import to a "new" salt to a non empty wallet with already mixed funds...), other thoughts?

I didn't use single Sha256 cause it adds ~7 LOC and makes it a bit less readable imo, and I don't know if that's actually valuable... Maybe in the future we could modify the Hash function to take a "HashType" but we'd probably want to wait until we diverge from BTC codebase to do that. Or we could add a HashSha256S function like the existing Hash function, so that the code is more readable

@UdjinM6
Copy link

UdjinM6 commented Aug 24, 2020

Good point on dumpwallet 👍 but I'm not sure if parsing dump file for "ps_salt" in importwallet rpc is a good idea - it might break some custom 3rd-party parsers if there are any. Maybe we could just add yet another comment line in the header of the dump file (like we do for mnemonic etc. of hd wallets already) and also allow users to change ps salt on the fly via some new rpc (e.g. setprivatesendsalt) instead. This way you could use the same salt in any wallet, including a newly generated/imported one (could this be helpful for tests?). You could even drop the old salt and force your wallet to generate a new one by specifying the null hash (or simply "0").

Could also add "ps_salt" entry in getprivatesendinfo rpc output I guess.

@xdustinface
Copy link

I didn't use single Sha256 cause it adds ~7 LOC and makes it a bit less readable imo, and I don't know if that's actually valuable..

So you prefer to have some less lines of code (which aren't hard to read in any way imo) and therefor run SHA256 twice instead of just running once which basically cuts execution time for hashing in IsFullyMixed by half? 🙈

Good point on dumpwallet 👍 but I'm not sure if parsing dump file for "ps_salt" in importwallet rpc is a good idea - it might break some custom 3rd-party parsers if there are any. Maybe we could just add yet another comment line in the header of the dump file (like we do for mnemonic etc. of hd wallets already) and also allow users to change ps salt on the fly via some new rpc (e.g. setprivatesendsalt) instead. This way you could use the same salt in any wallet, including a newly generated/imported one (could this be helpful for tests?). You could even drop the old salt and force your wallet to generate a new one by specifying the null hash (or simply "0").

Could also add "ps_salt" entry in getprivatesendinfo rpc output I guess.

Oh right, adding it as comment there + RPC sounds good imo. I think i like the idea of having setprivatesendsalt 👍 Not sure though if adding it to getprivatesendinfo is the best.. maybe also have a getprivatesendsalt instead to require a explicit call of it as it's kind of sensible data, no?

@UdjinM6
Copy link

UdjinM6 commented Aug 26, 2020

@PastaPastaPasta re hashing in IsFullyMixed: how about e093978a7ebf4f7a08a60d74e21614bd67a6932a?

Uses just 1 sha256 instead of 3 (1 in SerializeHash + 2 in Hash)
@PastaPastaPasta
Copy link
Member Author

@PastaPastaPasta re hashing in IsFullyMixed: how about e093978?

Done

UdjinM6
UdjinM6 previously approved these changes Aug 28, 2020
Copy link

@UdjinM6 UdjinM6 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK

Signed-off-by: pasta <[email protected]>
Copy link

@xdustinface xdustinface left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK

Copy link

@UdjinM6 UdjinM6 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re-ACK

@UdjinM6 UdjinM6 merged commit 533b2fe into dashpay:develop Aug 30, 2020
@PastaPastaPasta PastaPastaPasta deleted the randomize-ps-rounds branch September 8, 2020 18:01
PastaPastaPasta added a commit to PastaPastaPasta/dash that referenced this pull request Sep 12, 2020
* Use GetRealOut... instead of Capped

Signed-off-by: pasta <[email protected]>

* Add "ps_salt" value to walletdb

This value is used to deterministically pick a random number of rounds to mix, between N and N + GetRandomRounds. A salt is needed in addition to the inputs hash to ensure that an attacker learns nothing from looking at the blockchain.

Signed-off-by: pasta <[email protected]>

* Implement Random Round Mixing

This implements "Random Round Mixing." Previously, attempted attacks on PrivateSend assumed that all inputs had been mixed for the same number of rounds. Noramlly assuming 2,4,8 or 16.

While none of these attacks have been successful, they still teach what we can do to make our system more robust, and one of those ways is to implement "Random Round Mixing".

Under the previous system, inputs were mixed up until N rounds (configured by user). At this point, the input was considered mixed, and could be private-sent. Under this new system, an input will be mixed to N rounds like prior. However, at this point, Sha256d(input, salt) will be calculated (note: this likely could be a more efficient hash function than double sha256, but that can be done in another PR / version if needed). If (hash % 2 == 0), then the input will be mixed again.
This results in an exponential decay where if you mix a set of inputs, half of those inputs will be mixed for N rounds, 1/4 will be mixed N+1, 1/8 will be mixed N+2, etc. This current implementation caps it at N+2. This results in mixing an average of N+0.875 rounds. If you removed the cap, you would mix on average N+1 rounds.

Signed-off-by: pasta <[email protected]>

* Make PS salt a private member of CWallet, tweak the way it's initialized

* Introduce `CWallet::IsFullyMixed` and use it everywhere instead of comparing rounds directly to ensure consistency between coin selection logic, balance calculations and gui

* Tweak `GetRealOutpointPrivateSendRounds` to respect random rounds

* Tweak IsFullyMixed to make decision on a per-outpoint basis instead of a per-tx one

* make a comment doxygen

Signed-off-by: pasta <[email protected]>

* Rename GetPrivateSendSalt InitPrivateSendSalt, since it is not a getter

Signed-off-by: pasta <[email protected]>

* move the comment below GetRounds call

Signed-off-by: pasta <[email protected]>

* don't use GetCappedOutpointPrivateSendRounds when printing to RPC

Signed-off-by: pasta <[email protected]>

* Simplify hashing in IsFullyMixed

Uses just 1 sha256 instead of 3 (1 in SerializeHash + 2 in Hash)

* undo comment change

Signed-off-by: pasta <[email protected]>

Co-authored-by: UdjinM6 <[email protected]>
UdjinM6 added a commit that referenced this pull request Sep 13, 2020
gades pushed a commit to cosanta/cosanta-core that referenced this pull request Mar 5, 2022
* Use GetRealOut... instead of Capped

Signed-off-by: pasta <[email protected]>

* Add "ps_salt" value to walletdb

This value is used to deterministically pick a random number of rounds to mix, between N and N + GetRandomRounds. A salt is needed in addition to the inputs hash to ensure that an attacker learns nothing from looking at the blockchain.

Signed-off-by: pasta <[email protected]>

* Implement Random Round Mixing

This implements "Random Round Mixing." Previously, attempted attacks on PrivateSend assumed that all inputs had been mixed for the same number of rounds. Noramlly assuming 2,4,8 or 16.

While none of these attacks have been successful, they still teach what we can do to make our system more robust, and one of those ways is to implement "Random Round Mixing".

Under the previous system, inputs were mixed up until N rounds (configured by user). At this point, the input was considered mixed, and could be private-sent. Under this new system, an input will be mixed to N rounds like prior. However, at this point, Sha256d(input, salt) will be calculated (note: this likely could be a more efficient hash function than double sha256, but that can be done in another PR / version if needed). If (hash % 2 == 0), then the input will be mixed again.
This results in an exponential decay where if you mix a set of inputs, half of those inputs will be mixed for N rounds, 1/4 will be mixed N+1, 1/8 will be mixed N+2, etc. This current implementation caps it at N+2. This results in mixing an average of N+0.875 rounds. If you removed the cap, you would mix on average N+1 rounds.

Signed-off-by: pasta <[email protected]>

* Make PS salt a private member of CWallet, tweak the way it's initialized

* Introduce `CWallet::IsFullyMixed` and use it everywhere instead of comparing rounds directly to ensure consistency between coin selection logic, balance calculations and gui

* Tweak `GetRealOutpointPrivateSendRounds` to respect random rounds

* Tweak IsFullyMixed to make decision on a per-outpoint basis instead of a per-tx one

* make a comment doxygen

Signed-off-by: pasta <[email protected]>

* Rename GetPrivateSendSalt InitPrivateSendSalt, since it is not a getter

Signed-off-by: pasta <[email protected]>

* move the comment below GetRounds call

Signed-off-by: pasta <[email protected]>

* don't use GetCappedOutpointPrivateSendRounds when printing to RPC

Signed-off-by: pasta <[email protected]>

* Simplify hashing in IsFullyMixed

Uses just 1 sha256 instead of 3 (1 in SerializeHash + 2 in Hash)

* undo comment change

Signed-off-by: pasta <[email protected]>

Co-authored-by: UdjinM6 <[email protected]>
PastaPastaPasta added a commit that referenced this pull request Jul 20, 2024
5943c93 feat: introduce `coinjoinsalt` RPC to allow manipulating per-wallet salt (Kittywhiskers Van Gogh)
4fa3d39 wallet: refactor `InitCoinJoinSalt()` to allow for setting a custom salt (Kittywhiskers Van Gogh)

Pull request description:

  ## Motivation

  CoinJoin utilizes a salt to randomize the number of rounds used in a mixing session (introduced in [dash#3661](#3661)). This was done to frustrate attempts at deobfuscating CoinJoin mixing transactions but also has the effect of deciding the mixing threshold at which a denomination is considered "sufficiently mixed", which in turn is tied this salt, that is in turn, tied to the wallet.

  With wallets that utilized keypool generation, this was perfectly acceptable as each key was unrelated to the other and any meaningful attempts to backup/restore a wallet would entail backing up the entire wallet database, which includes the salt. Meaning, on restore, the wallet would show CoinJoin balances as reported earlier.

  With the default activation of HD wallets in legacy wallets (and the introduction of descriptor wallets that construct HD wallets by default), addresses are deterministically generated and backups no longer _require_ a backup of the wallet database wholesale.

  Users who export their mnemonic and import them into a new wallet will find themselves with a different reported CoinJoin balance (see below).

  https://github.com/dashpay/dash/assets/63189531/dccf1b17-55af-423d-8c36-adea6163e060

  ## Demo

  **Based on [`c00a3665`](kwvg@c00a366578354810908e4034c5a73ab3782f2cc6)**

  https://github.com/dashpay/dash/assets/63189531/c60c10e3-e414-46af-a64e-60605a4e6d07

  ## Checklist:

  - [x] I have performed a self-review of my own code
  - [x] I have commented my code, particularly in hard-to-understand areas
  - [x] I have added or updated relevant unit/integration/functional/e2e tests
  - [x] I have made corresponding changes to the documentation
  - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_

ACKs for top commit:
  UdjinM6:
    ACK 5943c93

Tree-SHA512: 8e5bdd18ecce4c14683fbeb1812b0b683a5a5b38405653655c3a14b1ab32a22b244b198087876cd53ca12447c2027432afe4259ef3043be7fddf640911d998f0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants