Skip to content
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

[on hold] Disposable Address using Payment ID #1345

Closed
wants to merge 12 commits into from

Conversation

kenshi84
Copy link
Contributor

@kenshi84 kenshi84 commented Nov 16, 2016

A new idea about sub-addresses was devised which also includes disposable addresses, so this PR should be halted in the meantime.
monero-project/research-lab#7

MRL-0006 draft

This is an implementation of the one-time address idea discussed earlier in Reddit. My personal motivation primarily comes from the fact that I must tell my wallet address (A,B) to ShapeShift every time I buy XMR there.

To prevent ShapeShift from knowing the total amount of XMR I bought through them, I implemented a scheme where ShapeShift is given a one-time address (C,D)=(hA,hB) along with a 256bit payment ID k where h=hash(a,k). The two addresses (A,B) and (C,D) cannot be linked without knowing the secret viewkey a. When a wallet (or a watch-only wallet (a,B)) processes a new transaction having a 256bit payment ID, it performs the output key derivation also against (C,D) to see if the fund belongs to it where the corresponding secret view & spend keys are (ha,hb).

To reduce the additional computational cost incurred in processing new transactions, the wallet generates a one-time address with random k such that the following equation holds: (hash(a,k)-k) mod 256 == 0 which is found by brute force. This trick allows the wallet to ignore all payment IDs not satisfying the equation, reducing the performance impact to roughly 1/256 or 0.4% of the original cost with the naive implementation.

One-time integrated address

One nice thing about the above scheme is that we don't need to ask ShapeShift to upgrade their software or anything, since the sender's procedure is exactly the same as before (i.e. transfer with 256bit payment ID). However, a downside is that it is cumbersome and error-prone to have to specify the 256bit payment ID separately.

To solve this problem, I took the same approach as the integrated address where a 64bit payment ID is integrated into the address. The integrated address itself cannot be used as-is, though, because the 64bit payment ID is stored in the transaction after being encrypted with the shared secret rA=aR. In our case, the sender would use the shared secret rC which equals to hash(a,k)aR to encrypt k; i.e., the receiver needs to know k in order to decrypt the encrypted k, which is impossible. Therefore, in order for our scheme to work, the integrated 64bit payment ID k should be stored in the transaction unencrypted. This necessitates the introduction of a new type of address similar to the integrated address, where the sender puts k into the transaction data without encryption. When a wallet sees a new transaction with a 64bit payment ID, it treats the value as potentially the unencrypted k and performs the additional key derivation step as described above.

Two new commands onetime_address_classic and onetime_address are introduced. As is the case with the current practice, it's definitely discouraged to reuse the same one-time address (i.e. the same unencrypted payment ID) which obviously leads to linkages among transactions.

Example

[wallet 49Nppc]: address
49Nppc2PHjDhbrhwW8UmtVGAW5RPKKBX1cbYEUASk6qdGVoZVaM1hot86ZPnKiVhb5E2svSDc2zLobHVTKkscczhSkYbERz
[wallet 49Nppc]: integrated_address
Random payment ID: <6cf06151b36851e7>
Matching integrated address: 4K5VqQqstzjhbrhwW8UmtVGAW5RPKKBX1cbYEUASk6qdGVoZVaM1hot86ZPnKiVhb5E2svSDc2zLobHVTKkscczhfD1omn2xS5WT718GZ4
[wallet 49Nppc]: onetime_address_classic
One-time address: 
432u5kgFw8X9p6KwUYbxhB255Qjad7Cj9JSdx1FVgkzq1efmiruMw19JrcADDGFpJFbqKiTYbmKsp1KeHGaVRKWVPREHoZC
Payment ID: 
<6685e62a0906106b072a844d4147362e6df380ba1601d7f6f03bbcdfcfb8c784>
IMPORTANT: If the sender forgets to attach the payment ID, you will N
OT see the fund!
[wallet 49Nppc]: onetime_address
One-time integrated address: 
4UEteoX8YitA4AKwMRN6hr4Tb8V6JM6BFXM86Sv9bk2eRLYtn4xkMkF47L92HcJFTXQVE1dUzQ5aRDoJBPXhm3PWbiYNg6hbn2gLE7XnUB
Payment ID integrated in the above: <8e0321662140e5aa>

Notice how the real address is included in the integrated address, while one-time addresses are entirely different from the real address.

Known issue (on Mac OS X 10.11.6)

If you create a watch-only wallet of an address which received funds using one-time addresses and do import_key_images, the wallet almost always crashes with an annoying error "incorrect checksum for freed object - object was probably modified after being freed." Funny thing is that it occasionally succeeds in importing key images (maybe 5% chance) on testnet (no hope with mainnet). I have absolutely no idea how to fix this.
Bug fixed (26 Dec 2016)


ToDo list:

  • agree on design choices
    -- whether to abandon onetime_address_classic or notabandoned
    -- whether to generate pIDs randomly or deterministicallyrandomly
    -- terminology (one-time address vs disposable address vs ...)use disposable
  • replace scalar multiplication by scalar addition (suggested by Luigi)done
  • cold wallet & offline signingbug fixed
  • mechanism to prevent outputs received by one-time addresses from being spent togetherfuture work (seems highly related to the 'churning' concept proposed by knaccc)
    -- http://termbin.com/639f
  • make it work with RPCdone
  • make it work with GUI
  • unit tests?
  • testing period on testnet

@dnaleor
Copy link

dnaleor commented Nov 16, 2016

@kenshi84, The issue I see is that these paymentID's aren't deterministic, so you need to backup them in case of a wallet crash. Rescanning is difficult etc.

What about making the k's determistic?

k0 = hash(a,00000000)
k1 = hash(a,00000001)
k2 = hash(a,00000002)
...

When you need to refresh your wallet from block 0, your wallet is on the lookout for transactions withpaymentID k0. When that one is encountered, the wallet checks for k1 in the following blocks until k1 is encountered, etc

This would mean that you still only need the viewkey to be able to see all incoming transactions.

edit: this would solve the computational cost for checking transactions as well.

@kenshi84
Copy link
Contributor Author

@dnaleor There's no need to worry about backing up used payment IDs, because they are stored in the blockchain.

@dnaleor
Copy link

dnaleor commented Nov 16, 2016

@kenshi84 I know they are stored in the blockchain. I was talking about the wallet that needs to backup the paymentID's. If you don't do that, how is your wallet supposed to know that a certain transaction is sent to a one time address belonging to the your wallet when you need to refresh is from scratch?

I have no idea if the computation you described (hash(a,k)-k) mod 256 == 0) is very fast while scanning? But I guess that using a deterministic paymentID makes scanning a lot faster. I could be wrong though.

@kenshi84
Copy link
Contributor Author

@dnaleor Restoring your wallet from scratch is really easy, believe me:) Basically, every time you see a new tx with 256bit pID k, you only need to do another key derivation attempt using the potential onetime tracking key (ha,hB) to see if any output key belongs to you or not. The naive way is to do this for all pIDs, and the trick with the equation essentially limits the kind of pIDs that this walled will be possibly using. You can just try this commit and see for yourself.

@JollyMort
Copy link
Contributor

JollyMort commented Nov 16, 2016

The only thing that worries me is possibility of pID being omitted by accident. How do you recover the funds then?

Rec: "Here's my address and pID, I will deliver the goods as soon as it's cleared"
Sen: "K, I sent you the funds 1 week ago, where are my goods?"
Rec: "No, I didn't get anything, are you sure you included the pID?"
Sen: "Oh fuck, sorry, I made a mistake. Anyway, here's the TX key to prove that I did send!"

If Rec has his record of which pID he gave to Sen, he can check and recover the funds. However, if he lost his records, then what? Using @dnaleor 's deterministic scheme, he could try out first 1000 k's or so against the TX key. Once recovered, he should immediately send it to himself with the correct pID specified, to avoid having to go through the same process if he's later recovering from seed and make the next scan easier.

But, if it's not deterministic, it's impossible to guess the k because you're then bruteforcing 256bits space.

@kenshi84
Copy link
Contributor Author

kenshi84 commented Nov 16, 2016

If you use @dnaleor's idea of k[i]=hash(a,i), then a wallet somehow needs to keep track of which i's have been used so far to avoid reuse. Surely this can be implemented, but it seems to require a bit more of coding. We'll see if the community finds this definitely needed.

Another direction for improvement would be to integrate k into the one-time address, just like integrated_address. But such k (could be 64bit) shouldn't be encrypted and the fact that this transfer uses the one-time mode must be passed to the sender, necessitating the introduction of a new address format and sender's client upgrade.

@dnaleor
Copy link

dnaleor commented Nov 17, 2016

@kenshi84 & @JollyMort , what about making deterministic paymentID's [k[i]=hash(a,i)] that fit the original condition created by kenshi [(hash(a,k)-k) mod 256 == 0]?

This solves the issue of an omitted paymentID by using a deterministisc paymentID, and keeps the option for just scanning the blockchain based on the mod256 condition (if that is in fact faster).

Anyway, I really like your work, kenshi. This is basically a "meta stealth address scheme" you came up with ;)

@@ -209,6 +209,8 @@ namespace tools

tx_construction_data construction_data;

bool is_onetime;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm worried about potential inconsistencies in the code outside simplewallet that uses wallet2::commit_tx(ptx), specifically:

src/wallet/api/pending_transaction.cpp
src/wallet/wallet_rpc_server.cpp

I've never touched the RPC server etc., so I have no idea what side effects I might have caused.

@kenshi84
Copy link
Contributor Author

@dnaleor No, whether it satisfies the equation or not is irrelevant here. I guess with the new version where the payment ID is integrated, we don't need to worry about forgetting to put the 256bit payment ID.

@dnaleor
Copy link

dnaleor commented Nov 17, 2016

@kenshi84

I guess with the new version where the payment ID is integrated, we don't need to worry about forgetting to put the 256bit payment ID.

Well, in theory yes. But I can imagine someone doing a bad wallet implementation and sending the XMR to the address without the attached paymentID...

Maybe an edge case and worth the risk, but still a trade off.

@kenshi84
Copy link
Contributor Author

@dnaleor At least I'm personally happy with this implementation; if someone thinks a better solution is needed, he's gonna just implement it himself :)

@fluffypony
Copy link
Contributor

@kenshi84 any chance you could do a formal write-up and submit it to the Monero Research Lab repo for us to publish? It would go as MRL-0006, and the current WIP that is MRL-0006 would be moved out to 7 or something.

@kenshi84
Copy link
Contributor Author

@fluffypony Sure, I'd be honored to be able to publish at MRL:)

@@ -504,7 +504,7 @@ namespace cryptonote
if (parse_tx_extra(tx.extra, tx_extra_fields))
{
tx_extra_nonce extra_nonce;
if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce))
if (!is_onetime && find_tx_extra_field_by_type(tx_extra_fields, extra_nonce))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note: this prevents the encryption for the one-time mode.

Copy link
Collaborator

@moneromooo-monero moneromooo-monero left a comment

Choose a reason for hiding this comment

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

Looks mostly nice. See comments inside. Please rebase/squash commits. Misc things are missing (RPC, mostly), so I'll probably have to add that before this gets merged. The way key images are selected needs further in depth review to ensure we can't double spend. Thanks for the patch!

@@ -1040,8 +1040,6 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
}
crypto::secret_key viewkey = *reinterpret_cast<const crypto::secret_key*>(viewkey_data.data());

m_wallet_file=m_generate_from_view_key;
Copy link
Collaborator

Choose a reason for hiding this comment

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

why remove this ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because the same thing appears at #L1011. Isn't this line redundant?

Copy link
Collaborator

Choose a reason for hiding this comment

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

It looks like it is.

@@ -2227,6 +2226,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
else while (!ptx_vector.empty())
{
auto & ptx = ptx_vector.back();
ptx.is_onetime = is_onetime;
Copy link
Collaborator

Choose a reason for hiding this comment

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

That seems wrong. The wallet tx creation should set this if it creates such a tx. The flag is passed to it already.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Could be. I did this in order to avoid decrypting payment ID in wallet2::get_payment_id() when in the one-time mode, but actually I don't really know how wallet2::commit_tx() works. In that function, payment_id seems to be used only in wallet2::add_unconfirmed_tx(), but what's the purpose of this function?

Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be irrelevant to get_payment_id. Functions like create_transactions_2 fill up the ptx set, and should set the is_onetime from the passed parameter. This is done just before in the timeline, so get_payment_id does not need to change.
add_unconfirmed_tx saves data about an outgoing tx that cannot be recovered from the blockchain (for examaple, destination addresses). It could also save short payment_id, since this is not decryptable after the fact by the sender (except if the sender is the receiver).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, I see what you mean. Then I guess I need to put ptx.is_onetime = is_onetime; into the end of wallet2::transfer<T>() and wallet2::transfer_selected() and wallet2::transfer_selected_rct(), correct?

BTW, overloads to wallet2::transfer() without args cryptonote::transaction tx and pending_tx ptx seem to be not used anywhere. Aren't they redundant?

@@ -2664,6 +2665,7 @@ bool simple_wallet::sweep_all(const std::vector<std::string> &args_)
else while (!ptx_vector.empty())
{
auto & ptx = ptx_vector.back();
ptx.is_onetime = is_onetime;
Copy link
Collaborator

Choose a reason for hiding this comment

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

same comment as above

crypto::secret_key_mult_public_key(*onetime_h, reinterpret_cast<const crypto::public_key&>(derivation), reinterpret_cast<crypto::public_key&>(derivation2));
check_acc_out_precomp(spend_public_key2, o, derivation2, i, received_bool, money_transfered, error);
if (received_bool)
received = 2;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd rather see two booleans (received, and onetime, eg), makes things a lot more understandable

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok

Copy link
Contributor Author

@kenshi84 kenshi84 Nov 20, 2016

Choose a reason for hiding this comment

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

I'm surprised that boost::bind produces error when the number of function arguments is more than 9. Seriously? :/
As a workaround, I'll introduce a new struct wallet2::is_received_info containing two bools.

// check if the following equation holds: (H(viewkey, pID) - pID) mod 256 == 0
if (pID_info.payment_id32.data[0] == pID_info.onetime_h_real.data[0])
pID_info.onetime_h = &pID_info.onetime_h_real;
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does not set has_* to false if nothing found in extra

Copy link
Contributor Author

Choose a reason for hiding this comment

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

has_* are set to false by default, see L626

needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier);
} while (ptx.fee < needed_fee);
} while (ptx.fee < needed_fee);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please don't add arbitrary reformatting

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sorry

}
}
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

his should probably init to null_hash if nothing found, just to be clean

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK

}
}
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should also initialize to null_hash id not found

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok

}
}
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

init to null_hash if nothing found

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok

@@ -99,7 +99,7 @@ int main(int argc, char *argv[]) {
vector<char> data;
ec_scalar expected, actual;
get(input, data, expected);
hash_to_scalar(data.data(), data.size(), actual);
::hash_to_scalar(data.data(), data.size(), actual);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Did this error out if you did not add :: ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, because I defined the function crypto::hash_to_scalar() in crypto.h and crypto-tests.h has a global scope function ::hash_to_scalar(), so the compiler gets confused which to use due to using namespace crypto.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I see it. Very strange.

void crypto_ops::copy(const ec_scalar &src, ec_scalar &dst) {
std::copy(src.data, src.data + 32, dst.data);
}
/*
* generate public and secret keys from a random 256-bit integer
Copy link
Contributor Author

@kenshi84 kenshi84 Nov 19, 2016

Choose a reason for hiding this comment

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

I did this because I thought simply assigning ec_point from one variable to another wouldn't copy the char data[32], but now I realized it does. So this should be unnecessary, it can be simply written as dst = src. Correct?

I'm so used to using std::vector or std::array and didn't familiarize myself enough with raw arrays... (I'm surprised because raw arrays themselves cannot be copied directly.)

Copy link
Collaborator

Choose a reason for hiding this comment

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

dst = src would be fine, yes. IIRC, copying of structs/classes defaults to memcpy, which is correct in this case.

@kenshi84
Copy link
Contributor Author

kenshi84 commented Nov 20, 2016

@moneromooo-monero:

The way key images are selected needs further in depth review to ensure we can't double spend.

I'd be very surprised if anyone can double spend by making any changes to the code; would it be possible at all to cheat the key image checking scheme of CryptoNote? I'm just curious.

@moneromooo-monero
Copy link
Collaborator

I don't think the Cryptonote one can be cheated. Your changes adds a second key image, though, and a few places where the original key image is replaced by a second one. This is what needs looking at carefully to ensure both key images can't be used in different cases.

@kenshi84
Copy link
Contributor Author

kenshi84 commented Nov 21, 2016

I see.
My code's intention is that in the original code, there's always an if or THROW_WALLET_EXCEPTION_IF checking after every call to generate_key_image_helper of the obtained in_ephemeral.pub against some known target output public key, so I'd replace the in_ephemeral and img (or ki) with the second one obtained from the one-time scheme if the first checking fails.
But I'm just a random person fiddling with the code, so professional reviewing would be definitely needed:)

check_acc_out_precomp(spend_public_key2, o, derivation2, i, received.onetime, money_transfered, error);
}
}
//----------------------------------------------------------------------------------------------------
static uint64_t decodeRct(const rct::rctSig & rv, const crypto::public_key pub, const crypto::secret_key &sec, unsigned int i, rct::key & mask)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Minor note: shouldn't the second arg be const reference as const crypto::public_key& pub?

Copy link
Collaborator

Choose a reason for hiding this comment

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

It should.

@kenshi84 kenshi84 changed the title monero-wallet-cli: one-time address using unencrypted payment ID monero-wallet-cli: One-time Address using Payment ID Nov 24, 2016
@kenshi84
Copy link
Contributor Author

As a simple safeguard against the possible mistake of the sender not attaching the payment ID to the transaction, we could make the wallet save every payment ID issued by every onetime_address_classic command to some file and allow it to rescan the blockchain doing the one-time key derivation with the payment ID that the sender forgot to attach, salvaging the lost fund.
Is such a functionality highly needed?

@anonymous459
Copy link

Wow it's an ultimate super awesome feature!!! I find it as a core privacy feature, which would make monero completely anonymous. I would love to start using it as soon as possible.

My c++ knowleadge is very limited. Tried to build it as per description on my ubuntu 16.04 box (amd64) and it failed with error:

[ 61%] Building CXX object tests/core_tests/CMakeFiles/coretests.dir/block_reward.cpp.o
In file included from /home/mint/monero/tests/core_tests/double_spend.h:152:0,
                 from /home/mint/monero/tests/core_tests/chaingen_tests_list.h:38,
                 from /home/mint/monero/tests/core_tests/block_reward.cpp:32:
/home/mint/monero/tests/core_tests/double_spend.inl: In member function ‘bool gen_double_spend_in_tx<txs_keeped_by_block>::generate(std::vector<boost::variant<cryptonote::block, cryptonote::transaction, cryptonote::account_base, callback_entry, serialized_object<cryptonote::block>, serialized_object<cryptonote::transaction>, event_visitor_settings> >&) const’:
/home/mint/monero/tests/core_tests/double_spend.inl:147:99: error: cannot convert ‘cryptonote::transaction’ to ‘bool’ for argument ‘5’ to ‘bool cryptonote::construct_tx(const cryptonote::account_keys&, const std::vector<cryptonote::tx_source_entry>&, const std::vector<cryptonote::tx_destination_entry>&, std::vector<unsigned char>, bool, cryptonote::transaction&, uint64_t)’
   if (!construct_tx(bob_account.get_keys(), sources, destinations, std::vector<uint8_t>(), tx_1, 0))
                                                                                                   ^
tests/core_tests/CMakeFiles/coretests.dir/build.make:62: recipe for target 'tests/core_tests/CMakeFiles/coretests.dir/block_reward.cpp.o' failed
make[3]: *** [tests/core_tests/CMakeFiles/coretests.dir/block_reward.cpp.o] Error 1
make[3]: Leaving directory '/home/mint/monero/build/release'
CMakeFiles/Makefile2:2081: recipe for target 'tests/core_tests/CMakeFiles/coretests.dir/all' failed
make[2]: *** [tests/core_tests/CMakeFiles/coretests.dir/all] Error 2
make[2]: Leaving directory '/home/mint/monero/build/release'
Makefile:138: recipe for target 'all' failed
make[1]: *** [all] Error 2
make[1]: Leaving directory '/home/mint/monero/build/release'
Makefile:54: recipe for target 'release-test' failed
make: *** [release-test] Error 2

I can also see that only monero-static-ubuntu-arm7 passes buildbot tests here on github pull request.

Somebody please contribute and make this feature work on ubuntu-amd64? That would be a huge step forwards in terms of privacy!

Thank you in advance! Really appreciate your work!

@ghost
Copy link

ghost commented Dec 14, 2016

@kenshi84 And what if somebody deletes that file by mistake? I think it would be better to prevent the user from sending this form of transaction without a payment ID

@kenshi84
Copy link
Contributor Author

@NanoAkron

I think it would be better to prevent the user from sending this form of transaction without a payment ID

I don't get what you mean. With onetime_address_classic, the sender simply has the standard monero address + 256bit pID, and nothing can stop him from omitting the payment ID when transferring fund.

@ghost
Copy link

ghost commented Dec 15, 2016

Couldn't you enforce 'Hey user, I see you've not entered a Payment ID. onetime_address_classic needs a Payment ID, please enter one now or one will be randomly generated for you'?

This prevents us from requiring the user to store yet another file, and from debugging problems related to it in future.

@kenshi84
Copy link
Contributor Author

@NanoAkron

Couldn't you enforce 'Hey user, I see you've not entered a Payment ID. onetime_address_classic needs a Payment ID, please enter one now or one will be randomly generated for you'?

In this case, "user" refers to the sender, I suppose? onetime_address_classic is used by the recipient and the standard address + pID are sent to the sender. So the sender has no way knowing whether the given address+pID is one-time mode or not.

@ghost
Copy link

ghost commented Dec 15, 2016

Ah.

Does the sender's wallet keep the pIDs of integrated addresses to which it sends? If it does so, could onetime_address_classic create only integrated addresses?

@kenshi84
Copy link
Contributor Author

kenshi84 commented Feb 9, 2017

I came up with a way to combine disposable addresses with sub-addresses by using scalar multiplication as was used before @luigi1111's suggestion. For a sub-address (C,D,n), we can define a disposable address (E,F,v,k) where k is the unencrypted 64bit payment ID satisfying Hs(a || k)%256 == k%256 and

h = Hs(a || k)
v = h*n
E = v*A = h*C
s = Hs(a || h)
S = s*G
F = D + S

The sender cannot find a link between (C,D,n) and (E,F,v,k).
The sender can relate (C,D,n) to (E,F,v,k) by first computing h = v/n and then checking E = h*C. So this scheme would be most useful when (C,D,n) is kept secret.

The sender's procedure of constructing a transaction is the same as the one for transferring to a sub-address where E, F and v correspond to C, D and n, respectively, except that k is attached to the transaction without encryption.

When the receiver checks if a new output key P belongs to him or not, he first looks for P - Hs(r*A)*G in the hash table. If not found, and if the transaction has a 64bit pID k that satisfies the equation, he then additionally looks for P - Hs(r*A)*G - S in the hash table.

@moneromooo-monero
Copy link
Collaborator

Ah, I remember that. What I meant is that, if reusing disposable addresses is so bad, then the wallet should save those when used, then refuse to send a second time to one that's already used. When restoring a wallet, since that list will be destroyed, one can do a similar thing by collecting all the payment ids for outgoing txes from this account (since that one is not encrypted in this case). That last one has a bit more false positive rate, but probably negligible.

@kenshi84 kenshi84 changed the title [on hold] Disposable Address using Payment ID [ready for testing] Disposable Address using Payment ID Feb 9, 2017
@kenshi84
Copy link
Contributor Author

kenshi84 commented Feb 9, 2017

@moneromooo-monero

What I meant is that, if reusing disposable addresses is so bad, then the wallet should save those when used, then refuse to send a second time to one that's already used.

So your concern is about the sender transferring to the same disposable address more than once, correct? This scenario seems less concerning to me because IMO the use of the disposable address scheme makes sense only when the payment is really one-time and the sender doesn't know who the recipient is (e.g. ShapeShift, ransomware collecting money). I expect no reason for the sender to pay to the same address more than once.

The other way of reuse on the recipient's side, i.e., the recipient accidentally generating an identical disposable address more than once, is unlikely to happen in practice unless the recipient is using a faulty wallet implementation with bad PRNG.

So I don't really see a real need for such a reuse checking feature. What do you and other people think?

@kenshi84
Copy link
Contributor Author

kenshi84 commented Feb 9, 2017

@fluffypony Done updating MRL-0006

@knaccc
Copy link

knaccc commented Feb 10, 2017

If vendors start using disposable addresses as if they are subaddresses, then this is a huge privacy problem. I suggest the wallet is modified to prevent users from paying the same disposable address twice. This would safeguard users' privacy and deter vendors from abusing disposable addresses.

Imagine if someone scraped all of the disposable addresses that vendors were publishing. Then if Alice pays Bob on more than one occasion, and Bob uses different outputs received from Alice on more than one occasion to pay a vendor Charlie, then Alice can easily see that Bob is a customer of Charlie if Charlie had published a disposable address for payment by all his customers.

@kenshi84
Copy link
Contributor Author

@knaccc

I suggest the wallet is modified to prevent users from paying the same disposable address twice. This would safeguard users' privacy and deter vendors from abusing disposable addresses.

I agree. So each time Bob pays to Charlie's disposable address, that address gets recorded into Bob's wallet cache file so that Bob's second attempt to pay to the same address will be detected and rejected by the wallet. This record won't be available for another wallet restored from the seed, though.

then Alice can easily see that Bob is a customer of Charlie

The real problem I see is that not just Alice but everybody can link all of Bob's payments to Charlie using the same disposable address due to the unencrypted payment ID.

@kenshi84 kenshi84 changed the title [ready for testing] Disposable Address using Payment ID [on hold] Disposable Address using Payment ID Feb 10, 2017
@knaccc
Copy link

knaccc commented Feb 10, 2017

I see what you're saying - someone could ignore the warnings and publish a disposable address for accepting payments on the basis that they're not expecting any individual user to be hit with a warning because it may be considered unlikely any one person will attempt to pay them twice. Hopefully it will be enough of a deterrent that they will lose out if someone actually does attempt to make a second payment to them.

@Gingeropolous
Copy link
Collaborator

Gingeropolous commented Feb 10, 2017 via email

@kenshi84
Copy link
Contributor Author

kenshi84 commented Feb 10, 2017

@knaccc

Hopefully it will be enough of a deterrent that they will lose out if someone actually does attempt to make a second payment to them.

TBH it's difficult for me to see how the scenario you described would be relevant; why would a vender want to use disposable addresses to receive payments from customers, when the main purpose of the scheme is to hide who the recipient (i.e. the vendor) is from the sender (i.e. customers)? Also, even if the vendor decided for whatever reasons to receive payments by disposable addresses, assuming such addresses would be generated on the per-purchase basis like Bitcoin, why would a customer pay to the same address more than once? I really can't think of any realistic scenario. Integrated addresses would be perfectly suitable here.

@Gingeropolous

IMO, I'm wary of things that depend on the user maintaining things that could impact the privacy of other members of the network.

First of all, such reuse would not happen normally, unless the user deliberately tries to do so. As long as the user pays to a disposable address freshly generated by the recipient, there's no worries. Second, even if the reuse did happen, for not too many times, it only means that those transactions will have the same 64bit payment ID that may suggest a link among them with certain probability, and most likely this won't affect privacy of other members in the network. Note that the space of 64bit data is not too huge and coincidental collisions do occur over a long term. Finally, although I really don't see a serious need, it'll be fairly straightforward (i.e. it's on my ToDo list) to implement a feature in the wallet to export and import various data associated with the wallet's past transactions for easy backup and maintenance, including the disposable addresses it paid to previously.

Heck, we even inform users to delete their wallet caches and use the .keys file to restore their wallets if its not working well.

I consider deleting the wallet cache as the last resort, since the cache contains quite some precious information like tx private keys, destination addresses, tx notes, address book, etc. This is why I worked previously to replace the unportable serializer with the portable one so that the user can continue to use the same cache file after new releases and across different platforms.

Hopefully we stop permitting cleartext payment ID's for this exact reason.

You mean the 256bit long payment ID that doesn't get encrypted, right? The 64bit short payment ID needs to be permitted for this disposable address scheme to work.

@knaccc
Copy link

knaccc commented Feb 10, 2017

why would a vender want to use disposable addresses to receive payments from customers, when the main purpose of the scheme is to hide who the recipient (i.e. the vendor) is from the sender (i.e. customers)? Also, even if the vendor decided for whatever reasons to receive payments by disposable addresses, assuming such addresses would be generated on the per-purchase basis like Bitcoin, why would a customer pay to the same address more than once?

The most frequent scenario I can think of is that someone creates a web site and doesn't want their main wallet address to be listed as the donation address. They can't be bothered to create a whole new wallet so they just put a disposable address there.

In other cases, I expect that people will not read the instructions, and assume that disposable addresses are just like bitcoin subaddresses, and use them in the same way without thinking.

@kenshi84
Copy link
Contributor Author

@moneromooo-monero
I added a simple checking for transferring to the same disposable address multiple times. Reviewing is appreciated!

@kenshi84
Copy link
Contributor Author

Closing in favor of PR #1753

@kenshi84 kenshi84 closed this Feb 21, 2017
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.

None yet

9 participants